mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
feat: add limit warning for feature flags (#7556)
This PR adds the Limit component to the feature flag creation form. At the limit:  Approaching the limit:  Below the limit threshold (no change): 
This commit is contained in:
parent
5ed4ccc981
commit
8f8ff13cc5
@ -28,50 +28,66 @@ const setupApi = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
test("should allow you to create feature flags when you're below the global limit", async () => {
|
describe('button states', () => {
|
||||||
setupApi({ flagLimit: 3, flagCount: 2 });
|
test("should allow you to create feature flags when you're below the global limit", async () => {
|
||||||
|
setupApi({ flagLimit: 3, flagCount: 2 });
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path='/projects/:projectId/create-toggle'
|
path='/projects/:projectId/create-toggle'
|
||||||
element={<CreateFeature />}
|
element={<CreateFeature />}
|
||||||
/>
|
/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{
|
{
|
||||||
route: '/projects/default/create-toggle',
|
route: '/projects/default/create-toggle',
|
||||||
permissions: [{ permission: CREATE_FEATURE }],
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(async () => {
|
|
||||||
const button = await screen.findByRole('button', {
|
const button = await screen.findByRole('button', {
|
||||||
name: /create feature flag/i,
|
name: /create feature flag/i,
|
||||||
});
|
});
|
||||||
expect(button).not.toBeDisabled();
|
await waitFor(() => {
|
||||||
});
|
expect(button).not.toBeDisabled();
|
||||||
});
|
|
||||||
|
|
||||||
test("should not allow you to create API tokens when you're at the global limit", async () => {
|
|
||||||
setupApi({ flagLimit: 3, flagCount: 3 });
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Routes>
|
|
||||||
<Route
|
|
||||||
path='/projects/:projectId/create-toggle'
|
|
||||||
element={<CreateFeature />}
|
|
||||||
/>
|
|
||||||
</Routes>,
|
|
||||||
{
|
|
||||||
route: '/projects/default/create-toggle',
|
|
||||||
permissions: [{ permission: CREATE_FEATURE }],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(async () => {
|
|
||||||
const button = await screen.findByRole('button', {
|
|
||||||
name: /create feature flag/i,
|
|
||||||
});
|
});
|
||||||
expect(button).toBeDisabled();
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('limit component', () => {
|
||||||
|
test('should show limit reached info', async () => {
|
||||||
|
setupApi({ flagLimit: 1, flagCount: 1 });
|
||||||
|
render(
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path='/projects/:projectId/create-toggle'
|
||||||
|
element={<CreateFeature />}
|
||||||
|
/>
|
||||||
|
</Routes>,
|
||||||
|
{
|
||||||
|
route: '/projects/default/create-toggle',
|
||||||
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await screen.findByText('You have reached the limit for feature flags');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show approaching limit info', async () => {
|
||||||
|
setupApi({ flagLimit: 10, flagCount: 9 });
|
||||||
|
render(
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path='/projects/:projectId/create-toggle'
|
||||||
|
element={<CreateFeature />}
|
||||||
|
/>
|
||||||
|
</Routes>,
|
||||||
|
{
|
||||||
|
route: '/projects/default/create-toggle',
|
||||||
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await screen.findByText('You are nearing the limit for feature flags');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,7 @@ import useProjectOverview, {
|
|||||||
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { useGlobalFeatureSearch } from '../FeatureToggleList/useGlobalFeatureSearch';
|
import { useGlobalFeatureSearch } from '../FeatureToggleList/useGlobalFeatureSearch';
|
||||||
|
import { Limit } from 'component/common/Limit/Limit';
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
@ -106,6 +107,8 @@ const CreateFeature = () => {
|
|||||||
const { total: totalFlags, loading: loadingTotalFlagCount } =
|
const { total: totalFlags, loading: loadingTotalFlagCount } =
|
||||||
useGlobalFeatureSearch();
|
useGlobalFeatureSearch();
|
||||||
|
|
||||||
|
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||||
|
|
||||||
const { globalFlagLimitReached, projectFlagLimitReached, limitMessage } =
|
const { globalFlagLimitReached, projectFlagLimitReached, limitMessage } =
|
||||||
useFlagLimits({
|
useFlagLimits({
|
||||||
global: {
|
global: {
|
||||||
@ -196,6 +199,18 @@ const CreateFeature = () => {
|
|||||||
mode='Create'
|
mode='Create'
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
featureNaming={projectInfo.featureNaming}
|
featureNaming={projectInfo.featureNaming}
|
||||||
|
Limit={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={resourceLimitsEnabled}
|
||||||
|
show={
|
||||||
|
<Limit
|
||||||
|
name='feature flags'
|
||||||
|
limit={uiConfig.resourceLimits.featureFlags}
|
||||||
|
currentValue={totalFlags ?? 0}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<CreateButton
|
<CreateButton
|
||||||
name='feature flag'
|
name='feature flag'
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
type Theme,
|
type Theme,
|
||||||
Typography,
|
Typography,
|
||||||
Link,
|
Link,
|
||||||
|
Box,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
|
import FeatureTypeSelect from '../FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
|
||||||
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
|
import { CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from 'utils/testIds';
|
||||||
@ -43,6 +44,7 @@ interface IFeatureToggleForm {
|
|||||||
mode: 'Create' | 'Edit';
|
mode: 'Create' | 'Edit';
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
Limit?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledForm = styled('form')({
|
const StyledForm = styled('form')({
|
||||||
@ -79,7 +81,6 @@ const StyledTypeDescription = styled('p')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledButtonContainer = styled('div')({
|
const StyledButtonContainer = styled('div')({
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
});
|
});
|
||||||
@ -98,6 +99,12 @@ const styledTypography = (theme: Theme) => ({
|
|||||||
margin: theme.spacing(1, 0),
|
margin: theme.spacing(1, 0),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LimitContainer = styled(Box)(({ theme }) => ({
|
||||||
|
'&:has(*)': {
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||||
children,
|
children,
|
||||||
type,
|
type,
|
||||||
@ -117,6 +124,7 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
errors,
|
errors,
|
||||||
mode,
|
mode,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
|
Limit,
|
||||||
}) => {
|
}) => {
|
||||||
const { featureTypes } = useFeatureTypes();
|
const { featureTypes } = useFeatureTypes();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -256,6 +264,7 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
|||||||
/>
|
/>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
</StyledFormControl>
|
</StyledFormControl>
|
||||||
|
<LimitContainer>{Limit}</LimitContainer>
|
||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
{children}
|
{children}
|
||||||
<StyledCancelButton onClick={handleCancel}>
|
<StyledCancelButton onClick={handleCancel}>
|
||||||
|
Loading…
Reference in New Issue
Block a user