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:
parent
682921d5bf
commit
006b853f6c
@ -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}
|
||||||
|
@ -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;
|
||||||
}>;
|
}>;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
@ -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,
|
||||||
|
@ -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;
|
||||||
const useUiConfig = (options: SWRConfiguration = {}) => {
|
loading: boolean;
|
||||||
const fetcher = () => {
|
error?: Error;
|
||||||
const path = formatApiPath(`api/admin/ui-config`);
|
refetch: () => void;
|
||||||
|
isOss: () => boolean;
|
||||||
return fetch(path, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
.then(handleErrorResponses('configuration'))
|
|
||||||
.then(res => res.json());
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, error } = useSWR<IUiConfig>(REQUEST_KEY, fetcher, options);
|
|
||||||
|
|
||||||
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(() => {
|
const useUiConfig = (): IUseUIConfigOutput => {
|
||||||
|
const path = formatApiPath(`api/admin/ui-config`);
|
||||||
|
const { data, error, mutate } = useSWR<IUiConfig>(path, fetcher);
|
||||||
|
|
||||||
|
const isOss = useCallback(() => {
|
||||||
|
return !data?.versionInfo?.current?.enterprise;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const uiConfig: IUiConfig = 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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export const SEGMENT_VALUES_LIMIT = 100;
|
|
||||||
export const STRATEGY_SEGMENTS_LIMIT = 5;
|
|
Loading…
Reference in New Issue
Block a user