1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-22 01:16:07 +02:00

refactor: get segment limits from uiConfig (#1047)

* refactor: improve useUiConfig return type

* refactor: get segment limits from uiConfig
This commit is contained in:
olav 2022-06-02 10:58:55 +02:00 committed by GitHub
parent 682921d5bf
commit 006b853f6c
11 changed files with 103 additions and 59 deletions

View File

@ -8,7 +8,7 @@ import {
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList'; import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles'; import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs'; import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
import { STRATEGY_SEGMENTS_LIMIT } from 'utils/segmentLimits'; import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
interface IFeatureStrategySegmentProps { interface IFeatureStrategySegmentProps {
segments: ISegment[]; segments: ISegment[];
@ -19,9 +19,14 @@ export const FeatureStrategySegment = ({
segments: selectedSegments, segments: selectedSegments,
setSegments: setSelectedSegments, setSegments: setSelectedSegments,
}: IFeatureStrategySegmentProps) => { }: IFeatureStrategySegmentProps) => {
const atSegmentsLimit = selectedSegments.length >= STRATEGY_SEGMENTS_LIMIT;
const { segments: allSegments } = useSegments(); const { segments: allSegments } = useSegments();
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const { strategySegmentsLimit } = useSegmentLimits();
const atStrategySegmentsLimit: boolean = Boolean(
strategySegmentsLimit &&
selectedSegments.length >= strategySegmentsLimit
);
if (!allSegments || allSegments.length === 0) { if (!allSegments || allSegments.length === 0) {
return null; return null;
@ -48,13 +53,13 @@ export const FeatureStrategySegment = ({
return ( return (
<> <>
<h3 className={styles.title}>Segmentation</h3> <h3 className={styles.title}>Segmentation</h3>
{atSegmentsLimit && <SegmentDocsStrategyWarning />} {atStrategySegmentsLimit && <SegmentDocsStrategyWarning />}
<p>Add a predefined segment to constrain this feature toggle:</p> <p>Add a predefined segment to constrain this feature toggle:</p>
<AutocompleteBox <AutocompleteBox
label="Select segments" label="Select segments"
options={autocompleteOptions} options={autocompleteOptions}
onChange={onChange} onChange={onChange}
disabled={atSegmentsLimit} disabled={atStrategySegmentsLimit}
/> />
<FeatureStrategySegmentList <FeatureStrategySegmentList
segments={selectedSegments} segments={selectedSegments}

View File

@ -19,7 +19,7 @@ interface IDrawerMenuProps {
admin?: boolean; admin?: boolean;
links: Array<{ links: Array<{
value: string; value: string;
icon: string | ReactNode; icon: ReactNode;
href: string; href: string;
title: string; title: string;
}>; }>;

View File

@ -14,8 +14,8 @@ import { SegmentForm } from '../SegmentForm/SegmentForm';
import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext'; import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext';
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs'; import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds'; import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
export const CreateSegment = () => { export const CreateSegment = () => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
@ -38,8 +38,12 @@ export const CreateSegment = () => {
} = useSegmentForm(); } = useSegmentForm();
const hasValidConstraints = useConstraintsValidation(constraints); const hasValidConstraints = useConstraintsValidation(constraints);
const { segmentValuesLimit } = useSegmentLimits();
const segmentValuesCount = useSegmentValuesCount(constraints); const segmentValuesCount = useSegmentValuesCount(constraints);
const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT;
const overSegmentValuesLimit: boolean = Boolean(
segmentValuesLimit && segmentValuesCount > segmentValuesLimit
);
const formatApiCode = () => { const formatApiCode = () => {
return `curl --location --request POST '${ return `curl --location --request POST '${
@ -96,7 +100,7 @@ export const CreateSegment = () => {
<CreateButton <CreateButton
name="segment" name="segment"
permission={CREATE_SEGMENT} permission={CREATE_SEGMENT}
disabled={!hasValidConstraints || atSegmentValuesLimit} disabled={!hasValidConstraints || overSegmentValuesLimit}
data-testid={SEGMENT_CREATE_BTN_ID} data-testid={SEGMENT_CREATE_BTN_ID}
/> />
</SegmentForm> </SegmentForm>

View File

@ -16,8 +16,8 @@ import { segmentsFormDescription } from 'component/segments/CreateSegment/Create
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs'; import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds'; import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
export const EditSegment = () => { export const EditSegment = () => {
const segmentId = useRequiredPathParam('segmentId'); const segmentId = useRequiredPathParam('segmentId');
@ -46,7 +46,11 @@ export const EditSegment = () => {
const hasValidConstraints = useConstraintsValidation(constraints); const hasValidConstraints = useConstraintsValidation(constraints);
const segmentValuesCount = useSegmentValuesCount(constraints); const segmentValuesCount = useSegmentValuesCount(constraints);
const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT; const { segmentValuesLimit } = useSegmentLimits();
const overSegmentValuesLimit: boolean = Boolean(
segmentValuesLimit && segmentValuesCount > segmentValuesLimit
);
const formatApiCode = () => { const formatApiCode = () => {
return `curl --location --request PUT '${ return `curl --location --request PUT '${
@ -98,7 +102,7 @@ export const EditSegment = () => {
> >
<UpdateButton <UpdateButton
permission={UPDATE_SEGMENT} permission={UPDATE_SEGMENT}
disabled={!hasValidConstraints || atSegmentValuesLimit} disabled={!hasValidConstraints || overSegmentValuesLimit}
data-testid={SEGMENT_SAVE_BTN_ID} data-testid={SEGMENT_SAVE_BTN_ID}
/> />
</SegmentForm> </SegmentForm>

View File

@ -1,9 +1,6 @@
import { Alert } from '@mui/material'; import { Alert } from '@mui/material';
import { useStyles } from 'component/segments/SegmentDocs/SegmentDocs.styles'; import { useStyles } from 'component/segments/SegmentDocs/SegmentDocs.styles';
import { import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
STRATEGY_SEGMENTS_LIMIT,
SEGMENT_VALUES_LIMIT,
} from 'utils/segmentLimits';
export const SegmentDocsWarning = () => { export const SegmentDocsWarning = () => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
@ -25,19 +22,31 @@ export const SegmentDocsWarning = () => {
}; };
export const SegmentDocsValuesWarning = () => { export const SegmentDocsValuesWarning = () => {
const { segmentValuesLimit } = useSegmentLimits();
if (typeof segmentValuesLimit === 'undefined') {
return null;
}
return ( return (
<Alert severity="warning"> <Alert severity="warning">
Segments is an experimental feature available to select users. Segments is an experimental feature available to select users.
Currently, segments are limited to at most {SEGMENT_VALUES_LIMIT}{' '} Currently, segments are limited to at most {segmentValuesLimit}{' '}
values. <SegmentLimitsLink /> values. <SegmentLimitsLink />
</Alert> </Alert>
); );
}; };
export const SegmentDocsValuesError = (props: { values: number }) => { export const SegmentDocsValuesError = (props: { values: number }) => {
const { segmentValuesLimit } = useSegmentLimits();
if (typeof segmentValuesLimit === 'undefined') {
return null;
}
return ( return (
<Alert severity="error"> <Alert severity="error">
Segments are limited to at most {SEGMENT_VALUES_LIMIT} values. This Segments are limited to at most {segmentValuesLimit} values. This
segment currently has {props.values}{' '} segment currently has {props.values}{' '}
{props.values === 1 ? 'value' : 'values'}. {props.values === 1 ? 'value' : 'values'}.
</Alert> </Alert>
@ -45,9 +54,15 @@ export const SegmentDocsValuesError = (props: { values: number }) => {
}; };
export const SegmentDocsStrategyWarning = () => { export const SegmentDocsStrategyWarning = () => {
const { strategySegmentsLimit } = useSegmentLimits();
if (typeof strategySegmentsLimit === 'undefined') {
return null;
}
return ( return (
<Alert severity="warning"> <Alert severity="warning">
Strategies are limited to {STRATEGY_SEGMENTS_LIMIT} segments.{' '} Strategies are limited to {strategySegmentsLimit} segments.{' '}
<SegmentLimitsLink /> <SegmentLimitsLink />
</Alert> </Alert>
); );

View File

@ -28,8 +28,8 @@ import {
SegmentDocsValuesError, SegmentDocsValuesError,
} from 'component/segments/SegmentDocs/SegmentDocs'; } from 'component/segments/SegmentDocs/SegmentDocs';
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
interface ISegmentFormPartTwoProps { interface ISegmentFormPartTwoProps {
constraints: IConstraint[]; constraints: IConstraint[];
@ -52,8 +52,12 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
const { context = [] } = useUnleashContext(); const { context = [] } = useUnleashContext();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const segmentValuesCount = useSegmentValuesCount(constraints); const segmentValuesCount = useSegmentValuesCount(constraints);
const overSegmentValuesLimit = segmentValuesCount > SEGMENT_VALUES_LIMIT;
const modePermission = mode === 'create' ? CREATE_SEGMENT : UPDATE_SEGMENT; const modePermission = mode === 'create' ? CREATE_SEGMENT : UPDATE_SEGMENT;
const { segmentValuesLimit } = useSegmentLimits();
const overSegmentValuesLimit: boolean = Boolean(
segmentValuesLimit && segmentValuesCount > segmentValuesLimit
);
const autocompleteOptions = context.map(c => ({ const autocompleteOptions = context.map(c => ({
value: c.name, value: c.name,

View File

@ -0,0 +1,16 @@
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { IUiConfig } from 'interfaces/uiConfig';
type IUseSegmentLimits = Pick<
IUiConfig,
'segmentValuesLimit' | 'strategySegmentsLimit'
>;
export const useSegmentLimits = (): IUseSegmentLimits => {
const { uiConfig } = useUiConfig();
return {
segmentValuesLimit: uiConfig.segmentValuesLimit,
strategySegmentsLimit: uiConfig.strategySegmentsLimit,
};
};

View File

@ -1,9 +1,9 @@
import { LibraryBooks } from '@mui/icons-material'; import { LibraryBooks } from '@mui/icons-material';
import { IUiConfig } from 'interfaces/uiConfig';
export const defaultValue = { export const defaultValue: IUiConfig = {
name: 'Unleash', name: 'Unleash',
version: '3.x', version: '3.x',
environment: '',
slogan: 'The enterprise ready feature toggle service.', slogan: 'The enterprise ready feature toggle service.',
flags: { flags: {
P: false, P: false,

View File

@ -1,40 +1,27 @@
import useSWR, { mutate, SWRConfiguration } from 'swr'; import useSWR from 'swr';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import { defaultValue } from './defaultValue'; import { defaultValue } from './defaultValue';
import { IUiConfig } from 'interfaces/uiConfig'; import { IUiConfig } from 'interfaces/uiConfig';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { useMemo } from 'react'; import { useMemo, useCallback } from 'react';
const REQUEST_KEY = 'api/admin/ui-config'; interface IUseUIConfigOutput {
uiConfig: IUiConfig;
loading: boolean;
error?: Error;
refetch: () => void;
isOss: () => boolean;
}
const useUiConfig = (options: SWRConfiguration = {}) => { const useUiConfig = (): IUseUIConfigOutput => {
const fetcher = () => { const path = formatApiPath(`api/admin/ui-config`);
const path = formatApiPath(`api/admin/ui-config`); const { data, error, mutate } = useSWR<IUiConfig>(path, fetcher);
return fetch(path, { const isOss = useCallback(() => {
method: 'GET', return !data?.versionInfo?.current?.enterprise;
credentials: 'include', }, [data]);
})
.then(handleErrorResponses('configuration'))
.then(res => res.json());
};
const { data, error } = useSWR<IUiConfig>(REQUEST_KEY, fetcher, options); const uiConfig: IUiConfig = useMemo(() => {
const refetch = () => {
mutate(REQUEST_KEY);
};
const isOss = () => {
if (data?.versionInfo?.current?.enterprise) {
return false;
} else if (!data || !data.versionInfo) {
return false;
}
return true;
};
const uiConfig = useMemo(() => {
return { ...defaultValue, ...data }; return { ...defaultValue, ...data };
}, [data]); }, [data]);
@ -42,9 +29,15 @@ const useUiConfig = (options: SWRConfiguration = {}) => {
uiConfig, uiConfig,
loading: !error && !data, loading: !error && !data,
error, error,
refetch, refetch: mutate,
isOss, isOss,
}; };
}; };
const fetcher = (path: string) => {
return fetch(path)
.then(handleErrorResponses('configuration'))
.then(res => res.json());
};
export default useUiConfig; export default useUiConfig;

View File

@ -1,15 +1,20 @@
import { ReactNode } from 'react';
export interface IUiConfig { export interface IUiConfig {
authenticationType: string; authenticationType?: string;
baseUriPath: string; baseUriPath?: string;
flags: IFlags; flags: IFlags;
name: string; name: string;
slogan: string; slogan: string;
unleashUrl: string; environment?: string;
unleashUrl?: string;
version: string; version: string;
versionInfo: IVersionInfo; versionInfo?: IVersionInfo;
links: ILinks[]; links: ILinks[];
disablePasswordAuth?: boolean; disablePasswordAuth?: boolean;
toast?: IProclamationToast; toast?: IProclamationToast;
segmentValuesLimit?: number;
strategySegmentsLimit?: number;
} }
export interface IProclamationToast { export interface IProclamationToast {
@ -46,7 +51,7 @@ export interface IVersion {
export interface ILinks { export interface ILinks {
value: string; value: string;
icon: string; icon: ReactNode;
href: string; href: string;
title: string; title: string;
} }

View File

@ -1,2 +0,0 @@
export const SEGMENT_VALUES_LIMIT = 100;
export const STRATEGY_SEGMENTS_LIMIT = 5;