From cad8a3c2dffd661b113c84f803f18d4942b7f77b Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 5 Jul 2024 09:09:28 +0200 Subject: [PATCH] feat: limit component used in strategies (#7542) --- frontend/src/component/common/Limit/Limit.tsx | 5 +- .../FeatureStrategyCreate.test.tsx | 2 + .../FeatureStrategyCreate.tsx | 39 +++++++++++ .../featureStrategyFormTestSetup.ts | 4 ++ .../FeatureStrategyForm.tsx | 9 +++ .../FeatureOverviewEnvironment.test.tsx | 68 +------------------ .../FeatureOverviewEnvironment.tsx | 29 -------- 7 files changed, 59 insertions(+), 97 deletions(-) diff --git a/frontend/src/component/common/Limit/Limit.tsx b/frontend/src/component/common/Limit/Limit.tsx index 7885089ad2..7c66c5750c 100644 --- a/frontend/src/component/common/Limit/Limit.tsx +++ b/frontend/src/component/common/Limit/Limit.tsx @@ -12,6 +12,7 @@ const StyledBox = styled(Box)(({ theme }) => ({ flexDirection: 'column', border: `2px solid ${theme.palette.background.application}`, borderRadius: `${theme.shape.borderRadiusMedium}px`, + width: '100%', })); const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ @@ -32,7 +33,7 @@ const Header = styled(Box)(({ theme }) => ({ gap: theme.spacing(1), alignItems: 'center', fontWeight: 'bold', - borderBottom: `2px solid ${theme.palette.background.application}`, + borderBottom: `1px solid ${theme.palette.background.application}`, padding: theme.spacing(3, 4), fontSize: theme.typography.h2.fontSize, })); @@ -42,7 +43,7 @@ const Footer = styled(Box)(({ theme }) => ({ })); const Main = styled(Box)(({ theme }) => ({ - borderBottom: `2px solid ${theme.palette.background.application}`, + borderBottom: `1px solid ${theme.palette.background.application}`, padding: theme.spacing(3, 4), })); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.test.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.test.tsx index 2b63851e86..b136cf6896 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.test.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.test.tsx @@ -95,6 +95,8 @@ describe('NewFeatureStrategyCreate', () => { const titleEl = await screen.findByText('Gradual rollout'); expect(titleEl).toBeInTheDocument(); + const saveButton = await screen.findByText('Save strategy'); + expect(saveButton).not.toBeDisabled(); const slider = await screen.findByRole('slider', { name: /rollout/i }); expect(slider).toHaveValue('100'); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx index 15de17f8b7..25edd4794c 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx @@ -36,6 +36,25 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useDefaultStrategy } from '../../../project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy'; import { FeatureStrategyForm } from '../FeatureStrategyForm/FeatureStrategyForm'; import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants'; +import { useUiFlag } from 'hooks/useUiFlag'; +import { Limit } from 'component/common/Limit/Limit'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; + +const useStrategyLimit = (strategyCount: number) => { + const resourceLimitsEnabled = useUiFlag('resourceLimits'); + const { uiConfig } = useUiConfig(); + const featureEnvironmentStrategiesLimit = + uiConfig.resourceLimits?.featureEnvironmentStrategies || 100; + const limitReached = + resourceLimitsEnabled && + strategyCount >= featureEnvironmentStrategiesLimit; + + return { + resourceLimitsEnabled, + limit: featureEnvironmentStrategiesLimit, + limitReached, + }; +}; export const FeatureStrategyCreate = () => { const [tab, setTab] = useState(0); @@ -70,6 +89,12 @@ export const FeatureStrategyCreate = () => { const navigate = useNavigate(); const { feature, refetchFeature } = useFeature(projectId, featureId); + const featureEnvironment = feature?.environments.find( + (featureEnvironment) => featureEnvironment.name === environmentId, + ); + const strategyCount = featureEnvironment?.strategies.length || 0; + const { limit, limitReached, resourceLimitsEnabled } = + useStrategyLimit(strategyCount); const ref = useRef(feature); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { refetch: refetchChangeRequests } = @@ -221,6 +246,20 @@ export const FeatureStrategyCreate = () => { editable /> } + Limit={ + + } + /> + } + disabled={limitReached} /> {staleDataNotification} diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/featureStrategyFormTestSetup.ts b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/featureStrategyFormTestSetup.ts index 2132c33352..1ad899d567 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/featureStrategyFormTestSetup.ts +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/featureStrategyFormTestSetup.ts @@ -86,6 +86,10 @@ export const setupUiConfigEndpoint = () => { environment: 'enterprise', flags: { newStrategyConfiguration: true, + resourceLimits: true, + }, + resourceLimits: { + featureEnvironmentStrategies: 2, }, unleashUrl: 'example.com', }); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx index fb796f309b..a8583bb127 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx @@ -66,6 +66,8 @@ interface IFeatureStrategyFormProps { tab: number; setTab: React.Dispatch>; StrategyVariants: JSX.Element; + Limit?: JSX.Element; + disabled?: boolean; } const StyledDividerContent = styled(Box)(({ theme }) => ({ @@ -205,6 +207,8 @@ export const FeatureStrategyForm = ({ tab, setTab, StrategyVariants, + Limit, + disabled, }: IFeatureStrategyFormProps) => { const { trackEvent } = usePlausibleTracker(); const [showProdGuard, setShowProdGuard] = useState(false); @@ -532,6 +536,10 @@ export const FeatureStrategyForm = ({ } /> + + {Limit} + + { - testServerRoute(server, '/api/admin/ui-config', { - flags: { - resourceLimits: true, - }, - resourceLimits: { - featureEnvironmentStrategies: LIMIT, - }, - }); - - testServerRoute( - server, - '/api/admin/projects/default/features/featureWithoutStrategies', - { - environments: [environmentWithoutStrategies], - }, - ); - - testServerRoute( - server, - '/api/admin/projects/default/features/featureWithManyStrategies', - { - environments: [environmentWithManyStrategies], - }, - ); -}; - const strategy = { name: 'default', } as IFeatureStrategy; @@ -46,15 +14,8 @@ const environmentWithoutStrategies = { type: 'production', strategies: [], }; -const environmentWithManyStrategies = { - name: 'production', - enabled: true, - type: 'production', - strategies: [...Array(LIMIT).keys()].map(() => strategy), -}; -test('should allow to add strategy when no strategies', async () => { - setupApi(); +test('should allow to add strategy', async () => { render( { const button = await screen.findByText('Add strategy'); expect(button).toBeEnabled(); }); - -test('should not allow to add strategy when limit reached', async () => { - setupApi(); - render( - - - } - /> - , - { - route: '/projects/default/features/featureWithManyStrategies/strategies/create', - permissions: [{ permission: CREATE_FEATURE_STRATEGY }], - }, - ); - - await waitFor(async () => { - const button = await screen.findByText('Add strategy'); - expect(button).toBeDisabled(); - }); -}); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index a14391e41e..5b6e340dff 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -22,8 +22,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons'; import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage'; import { Badge } from 'component/common/Badge/Badge'; -import { useUiFlag } from 'hooks/useUiFlag'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; interface IFeatureOverviewEnvironmentProps { env: IFeatureEnvironment; @@ -117,19 +115,6 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({ }, })); -const useStrategyLimit = (strategyCount: number) => { - const resourceLimitsEnabled = useUiFlag('resourceLimits'); - const { uiConfig } = useUiConfig(); - const featureEnvironmentStrategiesLimit = - uiConfig.resourceLimits?.featureEnvironmentStrategies || 100; - const limitReached = - resourceLimitsEnabled && - strategyCount >= featureEnvironmentStrategiesLimit; - const limitMessage = `Limit of ${featureEnvironmentStrategiesLimit} strategies reached`; - - return { limitReached, limitMessage }; -}; - const FeatureOverviewEnvironment = ({ env, }: IFeatureOverviewEnvironmentProps) => { @@ -147,10 +132,6 @@ const FeatureOverviewEnvironment = ({ (featureEnvironment) => featureEnvironment.name === env.name, ); - const { limitMessage, limitReached } = useStrategyLimit( - featureEnvironment?.strategies.length || 0, - ); - return (