From 006b853f6c8a7a7ede0c8a6b3109bea2238a2ca8 Mon Sep 17 00:00:00 2001 From: olav Date: Thu, 2 Jun 2022 10:58:55 +0200 Subject: [PATCH] refactor: get segment limits from uiConfig (#1047) * refactor: improve useUiConfig return type * refactor: get segment limits from uiConfig --- .../FeatureStrategySegment.tsx | 13 +++-- .../menu/Header/DrawerMenu/DrawerMenu.tsx | 2 +- .../segments/CreateSegment/CreateSegment.tsx | 10 ++-- .../segments/EditSegment/EditSegment.tsx | 10 ++-- .../segments/SegmentDocs/SegmentDocs.tsx | 29 +++++++--- .../SegmentFormStepTwo/SegmentFormStepTwo.tsx | 8 ++- .../useSegmentLimits/useSegmentLimits.ts | 16 ++++++ .../api/getters/useUiConfig/defaultValue.ts | 4 +- .../api/getters/useUiConfig/useUiConfig.ts | 53 ++++++++----------- frontend/src/interfaces/uiConfig.ts | 15 ++++-- frontend/src/utils/segmentLimits.ts | 2 - 11 files changed, 103 insertions(+), 59 deletions(-) create mode 100644 frontend/src/hooks/api/getters/useSegmentLimits/useSegmentLimits.ts delete mode 100644 frontend/src/utils/segmentLimits.ts diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx index 6a8f2ebfd0..e84110f346 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx @@ -8,7 +8,7 @@ import { import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList'; import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles'; import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs'; -import { STRATEGY_SEGMENTS_LIMIT } from 'utils/segmentLimits'; +import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; interface IFeatureStrategySegmentProps { segments: ISegment[]; @@ -19,9 +19,14 @@ export const FeatureStrategySegment = ({ segments: selectedSegments, setSegments: setSelectedSegments, }: IFeatureStrategySegmentProps) => { - const atSegmentsLimit = selectedSegments.length >= STRATEGY_SEGMENTS_LIMIT; const { segments: allSegments } = useSegments(); const { classes: styles } = useStyles(); + const { strategySegmentsLimit } = useSegmentLimits(); + + const atStrategySegmentsLimit: boolean = Boolean( + strategySegmentsLimit && + selectedSegments.length >= strategySegmentsLimit + ); if (!allSegments || allSegments.length === 0) { return null; @@ -48,13 +53,13 @@ export const FeatureStrategySegment = ({ return ( <>

Segmentation

- {atSegmentsLimit && } + {atStrategySegmentsLimit && }

Add a predefined segment to constrain this feature toggle:

; diff --git a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx index d53224c917..29dd893f27 100644 --- a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx +++ b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx @@ -14,8 +14,8 @@ import { SegmentForm } from '../SegmentForm/SegmentForm'; import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext'; import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; -import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits'; import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds'; +import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; export const CreateSegment = () => { const { uiConfig } = useUiConfig(); @@ -38,8 +38,12 @@ export const CreateSegment = () => { } = useSegmentForm(); const hasValidConstraints = useConstraintsValidation(constraints); + const { segmentValuesLimit } = useSegmentLimits(); const segmentValuesCount = useSegmentValuesCount(constraints); - const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT; + + const overSegmentValuesLimit: boolean = Boolean( + segmentValuesLimit && segmentValuesCount > segmentValuesLimit + ); const formatApiCode = () => { return `curl --location --request POST '${ @@ -96,7 +100,7 @@ export const CreateSegment = () => { diff --git a/frontend/src/component/segments/EditSegment/EditSegment.tsx b/frontend/src/component/segments/EditSegment/EditSegment.tsx index 7757d30bcf..2a7ba360a4 100644 --- a/frontend/src/component/segments/EditSegment/EditSegment.tsx +++ b/frontend/src/component/segments/EditSegment/EditSegment.tsx @@ -16,8 +16,8 @@ import { segmentsFormDescription } from 'component/segments/CreateSegment/Create import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; -import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits'; import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds'; +import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; export const EditSegment = () => { const segmentId = useRequiredPathParam('segmentId'); @@ -46,7 +46,11 @@ export const EditSegment = () => { const hasValidConstraints = useConstraintsValidation(constraints); const segmentValuesCount = useSegmentValuesCount(constraints); - const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT; + const { segmentValuesLimit } = useSegmentLimits(); + + const overSegmentValuesLimit: boolean = Boolean( + segmentValuesLimit && segmentValuesCount > segmentValuesLimit + ); const formatApiCode = () => { return `curl --location --request PUT '${ @@ -98,7 +102,7 @@ export const EditSegment = () => { > diff --git a/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx b/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx index 211a301907..1bb3b503b5 100644 --- a/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx +++ b/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx @@ -1,9 +1,6 @@ import { Alert } from '@mui/material'; import { useStyles } from 'component/segments/SegmentDocs/SegmentDocs.styles'; -import { - STRATEGY_SEGMENTS_LIMIT, - SEGMENT_VALUES_LIMIT, -} from 'utils/segmentLimits'; +import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; export const SegmentDocsWarning = () => { const { classes: styles } = useStyles(); @@ -25,19 +22,31 @@ export const SegmentDocsWarning = () => { }; export const SegmentDocsValuesWarning = () => { + const { segmentValuesLimit } = useSegmentLimits(); + + if (typeof segmentValuesLimit === 'undefined') { + return null; + } + return ( 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. ); }; export const SegmentDocsValuesError = (props: { values: number }) => { + const { segmentValuesLimit } = useSegmentLimits(); + + if (typeof segmentValuesLimit === 'undefined') { + return null; + } + return ( - 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}{' '} {props.values === 1 ? 'value' : 'values'}. @@ -45,9 +54,15 @@ export const SegmentDocsValuesError = (props: { values: number }) => { }; export const SegmentDocsStrategyWarning = () => { + const { strategySegmentsLimit } = useSegmentLimits(); + + if (typeof strategySegmentsLimit === 'undefined') { + return null; + } + return ( - Strategies are limited to {STRATEGY_SEGMENTS_LIMIT} segments.{' '} + Strategies are limited to {strategySegmentsLimit} segments.{' '} ); diff --git a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx index 6a6274e20d..0799f330f2 100644 --- a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx +++ b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx @@ -28,8 +28,8 @@ import { SegmentDocsValuesError, } from 'component/segments/SegmentDocs/SegmentDocs'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; -import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits'; import AccessContext from 'contexts/AccessContext'; +import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits'; interface ISegmentFormPartTwoProps { constraints: IConstraint[]; @@ -52,8 +52,12 @@ export const SegmentFormStepTwo: React.FC = ({ const { context = [] } = useUnleashContext(); const [open, setOpen] = useState(false); const segmentValuesCount = useSegmentValuesCount(constraints); - const overSegmentValuesLimit = segmentValuesCount > SEGMENT_VALUES_LIMIT; const modePermission = mode === 'create' ? CREATE_SEGMENT : UPDATE_SEGMENT; + const { segmentValuesLimit } = useSegmentLimits(); + + const overSegmentValuesLimit: boolean = Boolean( + segmentValuesLimit && segmentValuesCount > segmentValuesLimit + ); const autocompleteOptions = context.map(c => ({ value: c.name, diff --git a/frontend/src/hooks/api/getters/useSegmentLimits/useSegmentLimits.ts b/frontend/src/hooks/api/getters/useSegmentLimits/useSegmentLimits.ts new file mode 100644 index 0000000000..dd07e6fb55 --- /dev/null +++ b/frontend/src/hooks/api/getters/useSegmentLimits/useSegmentLimits.ts @@ -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, + }; +}; diff --git a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.ts b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.ts index 1ad5b11b71..51d90d5dff 100644 --- a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.ts +++ b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.ts @@ -1,9 +1,9 @@ import { LibraryBooks } from '@mui/icons-material'; +import { IUiConfig } from 'interfaces/uiConfig'; -export const defaultValue = { +export const defaultValue: IUiConfig = { name: 'Unleash', version: '3.x', - environment: '', slogan: 'The enterprise ready feature toggle service.', flags: { P: false, diff --git a/frontend/src/hooks/api/getters/useUiConfig/useUiConfig.ts b/frontend/src/hooks/api/getters/useUiConfig/useUiConfig.ts index e5fa78c01c..9ab1725009 100644 --- a/frontend/src/hooks/api/getters/useUiConfig/useUiConfig.ts +++ b/frontend/src/hooks/api/getters/useUiConfig/useUiConfig.ts @@ -1,40 +1,27 @@ -import useSWR, { mutate, SWRConfiguration } from 'swr'; +import useSWR from 'swr'; import { formatApiPath } from 'utils/formatPath'; import { defaultValue } from './defaultValue'; import { IUiConfig } from 'interfaces/uiConfig'; 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 fetcher = () => { - const path = formatApiPath(`api/admin/ui-config`); +const useUiConfig = (): IUseUIConfigOutput => { + const path = formatApiPath(`api/admin/ui-config`); + const { data, error, mutate } = useSWR(path, fetcher); - return fetch(path, { - method: 'GET', - credentials: 'include', - }) - .then(handleErrorResponses('configuration')) - .then(res => res.json()); - }; + const isOss = useCallback(() => { + return !data?.versionInfo?.current?.enterprise; + }, [data]); - const { data, error } = useSWR(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 uiConfig: IUiConfig = useMemo(() => { return { ...defaultValue, ...data }; }, [data]); @@ -42,9 +29,15 @@ const useUiConfig = (options: SWRConfiguration = {}) => { uiConfig, loading: !error && !data, error, - refetch, + refetch: mutate, isOss, }; }; +const fetcher = (path: string) => { + return fetch(path) + .then(handleErrorResponses('configuration')) + .then(res => res.json()); +}; + export default useUiConfig; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index ea28afda0d..4ca783a355 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -1,15 +1,20 @@ +import { ReactNode } from 'react'; + export interface IUiConfig { - authenticationType: string; - baseUriPath: string; + authenticationType?: string; + baseUriPath?: string; flags: IFlags; name: string; slogan: string; - unleashUrl: string; + environment?: string; + unleashUrl?: string; version: string; - versionInfo: IVersionInfo; + versionInfo?: IVersionInfo; links: ILinks[]; disablePasswordAuth?: boolean; toast?: IProclamationToast; + segmentValuesLimit?: number; + strategySegmentsLimit?: number; } export interface IProclamationToast { @@ -46,7 +51,7 @@ export interface IVersion { export interface ILinks { value: string; - icon: string; + icon: ReactNode; href: string; title: string; } diff --git a/frontend/src/utils/segmentLimits.ts b/frontend/src/utils/segmentLimits.ts deleted file mode 100644 index 9837119c2a..0000000000 --- a/frontend/src/utils/segmentLimits.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SEGMENT_VALUES_LIMIT = 100; -export const STRATEGY_SEGMENTS_LIMIT = 5;