From be518af228cb493f751672441183bc34d757d84e Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 2 Jul 2024 08:14:15 +0200 Subject: [PATCH] feat: use new environment limit in Unleash UI (#7500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR updates the Unleash UI to use the new environment limit. As it turns out, we already had an environment limit in the UI, but it was hardcoded (luckily, its value is the same as the new default value 🥳). In addition to the existing places this limit was used, it also disables the "new environment" button if you've reached the limit. Because this limit already exists, I don't think we need a flag for it. The only change is that you can't click a button (that should be a link!) that takes you to a page you can't do anything on. --- .../CreateEnvironment/CreateEnvironment.tsx | 8 +-- .../CreateEnvironmentButton.test.tsx | 55 +++++++++++++++++++ .../CreateEnvironmentButton.tsx | 13 ++++- .../EnvironmentActionCell.tsx | 8 ++- frontend/src/constants/values.ts | 1 - .../api/getters/useUiConfig/defaultValue.tsx | 1 + .../openapi/models/resourceLimitsSchema.ts | 2 + .../openapi/spec/resource-limits-schema.ts | 2 +- 8 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.test.tsx delete mode 100644 frontend/src/constants/values.ts diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx index 4c15fd72c3..7c842ffac4 100644 --- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx +++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx @@ -16,14 +16,14 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { formatUnknownError } from 'utils/formatUnknownError'; import { GO_BACK } from 'constants/navigate'; -import { ENV_LIMIT } from 'constants/values'; const CreateEnvironment = () => { const { setToastApiError, setToastData } = useToast(); const { uiConfig } = useUiConfig(); + const environmentLimit = uiConfig.resourceLimits.environments; const navigate = useNavigate(); const { environments } = useEnvironments(); - const canCreateMoreEnvs = environments.length < ENV_LIMIT; + const canCreateMoreEnvs = environments.length < environmentLimit; const { createEnvironment, loading } = useEnvironmentApi(); const { refetch } = usePermissions(); const { @@ -114,8 +114,8 @@ const CreateEnvironment = () => {

Currently Unleash does not support more than{' '} - {ENV_LIMIT} environments. If you need more - please reach out. + {environmentLimit} environments. If you need + more please reach out.


diff --git a/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.test.tsx b/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.test.tsx new file mode 100644 index 0000000000..998500931a --- /dev/null +++ b/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.test.tsx @@ -0,0 +1,55 @@ +import { screen, waitFor } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { testServerRoute, testServerSetup } from 'utils/testServer'; +import { CreateEnvironmentButton } from './CreateEnvironmentButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; + +const server = testServerSetup(); + +const setupApi = ({ + environmentCount, + environmentLimit, +}: { environmentCount: number; environmentLimit: number }) => { + testServerRoute(server, '/api/admin/ui-config', { + flags: { + resourceLimits: true, + EEA: true, + }, + resourceLimits: { + environments: environmentLimit, + }, + }); + + testServerRoute(server, '/api/admin/environments', { + environments: Array.from({ length: environmentCount }).map((_, i) => ({ + name: `environment-${i}`, + type: 'production', + enabled: i % 2 === 0, + })), + }); +}; + +test('should allow you to create environments when there are fewer environments than the limit', async () => { + setupApi({ environmentLimit: 5, environmentCount: 2 }); + + render(, { + permissions: [{ permission: ADMIN }], + }); + + await waitFor(async () => { + const button = await screen.findByRole('button'); + expect(button).not.toBeDisabled(); + }); +}); + +test('should not allow you to create environments when you have reached the limit', async () => { + setupApi({ environmentLimit: 5, environmentCount: 5 }); + render(, { + permissions: [{ permission: ADMIN }], + }); + + await waitFor(async () => { + const button = await screen.findByRole('button'); + expect(button).toBeDisabled(); + }); +}); diff --git a/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.tsx b/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.tsx index 186f568f46..ec65a12113 100644 --- a/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.tsx +++ b/frontend/src/component/environments/CreateEnvironmentButton/CreateEnvironmentButton.tsx @@ -3,18 +3,29 @@ import Add from '@mui/icons-material/Add'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useNavigate } from 'react-router-dom'; +import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; export const CreateEnvironmentButton = () => { const { uiConfig } = useUiConfig(); + const { environments } = useEnvironments(); + const environmentLimit = uiConfig.resourceLimits.environments; const navigate = useNavigate(); + const limitReached = environments.length >= environmentLimit; + return ( navigate('/environments/create')} maxWidth='700px' Icon={Add} permission={ADMIN} - disabled={!uiConfig.flags.EEA} + disabled={limitReached || !uiConfig.flags.EEA} > New environment diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx index 860178bdf8..9ddf3833cc 100644 --- a/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx +++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx @@ -11,9 +11,9 @@ import { EnvironmentActionCellPopover } from './EnvironmentActionCellPopover/Env import { EnvironmentCloneModal } from './EnvironmentCloneModal/EnvironmentCloneModal'; import type { IApiToken } from 'hooks/api/getters/useApiTokens/useApiTokens'; import { EnvironmentTokenDialog } from './EnvironmentTokenDialog/EnvironmentTokenDialog'; -import { ENV_LIMIT } from 'constants/values'; import { EnvironmentDeprecateToggleDialog } from './EnvironmentDeprecateToggleDialog/EnvironmentDeprecateToggleDialog'; import { EnvironmentDeleteDialog } from './EnvironmentDeleteDialog/EnvironmentDeleteDialog'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; interface IEnvironmentTableActionsProps { environment: IEnvironment; @@ -23,6 +23,8 @@ export const EnvironmentActionCell = ({ environment, }: IEnvironmentTableActionsProps) => { const navigate = useNavigate(); + const { uiConfig } = useUiConfig(); + const environmentLimit = uiConfig.resourceLimits.environments; const { setToastApiError, setToastData } = useToast(); const { environments, refetchEnvironments } = useEnvironments(); const { refetch: refetchPermissions } = usePermissions(); @@ -82,13 +84,13 @@ export const EnvironmentActionCell = ({ onEdit={() => navigate(`/environments/${environment.name}`)} onDeprecateToggle={() => setDeprecateToggleDialog(true)} onClone={() => { - if (environments.length < ENV_LIMIT) { + if (environments.length < environmentLimit) { setCloneModal(true); } else { setToastData({ type: 'error', title: 'Environment limit reached', - text: `You have reached the maximum number of environments (${ENV_LIMIT}). Please reach out if you need more.`, + text: `You have reached the maximum number of environments (${environmentLimit}). Please reach out if you need more.`, }); } }} diff --git a/frontend/src/constants/values.ts b/frontend/src/constants/values.ts deleted file mode 100644 index 923e044f04..0000000000 --- a/frontend/src/constants/values.ts +++ /dev/null @@ -1 +0,0 @@ -export const ENV_LIMIT = 50; diff --git a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx index 0de32d7b05..2282727fbc 100644 --- a/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx +++ b/frontend/src/hooks/api/getters/useUiConfig/defaultValue.tsx @@ -38,6 +38,7 @@ export const defaultValue: IUiConfig = { actionSetFilterValues: 25, signalTokensPerEndpoint: 5, featureEnvironmentStrategies: 30, + environments: 50, constraintValues: 250, }, }; diff --git a/frontend/src/openapi/models/resourceLimitsSchema.ts b/frontend/src/openapi/models/resourceLimitsSchema.ts index 66acb44e77..fb56bb354c 100644 --- a/frontend/src/openapi/models/resourceLimitsSchema.ts +++ b/frontend/src/openapi/models/resourceLimitsSchema.ts @@ -26,6 +26,8 @@ export interface ResourceLimitsSchema { strategySegments: number; /** The maximum number of feature environment strategies allowed. */ featureEnvironmentStrategies: number; + /** The maximum number of environments allowed. */ + environments: number; /** The maximum number of values for a single constraint. */ constraintValues: number; } diff --git a/src/lib/openapi/spec/resource-limits-schema.ts b/src/lib/openapi/spec/resource-limits-schema.ts index cdb3be1d43..03ff370f9c 100644 --- a/src/lib/openapi/spec/resource-limits-schema.ts +++ b/src/lib/openapi/spec/resource-limits-schema.ts @@ -80,7 +80,7 @@ export const resourceLimitsSchema = { type: 'integer', minimum: 1, example: 50, - description: 'The maximum number active environments allowed.', + description: 'The maximum number of environments allowed.', }, }, components: {},