mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
chore: remove create feature component (#7959)
After we implemented new feature flag creation flow, this are not used anymore. Creation is now handled by **CreateFeatureDialog**. Also edit component can be minified, because it does not need so many fields anymore.
This commit is contained in:
parent
e5cca661d9
commit
4a4dafcc3f
@ -46,8 +46,6 @@ const BreadcrumbNav = () => {
|
||||
item !== 'copy' &&
|
||||
item !== 'features' &&
|
||||
item !== 'features2' &&
|
||||
// TODO: this can be removed after new create flag flow goes live
|
||||
item !== 'create-toggle' &&
|
||||
item !== 'settings' &&
|
||||
item !== 'profile' &&
|
||||
item !== 'insights',
|
||||
|
@ -1,93 +0,0 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import CreateFeature from './CreateFeature';
|
||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
const setupApi = ({
|
||||
flagCount,
|
||||
flagLimit,
|
||||
}: { flagCount: number; flagLimit: number }) => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
resourceLimits: true,
|
||||
},
|
||||
resourceLimits: {
|
||||
featureFlags: flagLimit,
|
||||
},
|
||||
});
|
||||
|
||||
testServerRoute(server, '/api/admin/search/features', {
|
||||
total: flagCount,
|
||||
features: Array.from({ length: flagCount }).map((_, i) => ({
|
||||
name: `flag-${i}`,
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
describe('button states', () => {
|
||||
test("should allow you to create feature flags when you're below the global limit", async () => {
|
||||
setupApi({ flagLimit: 3, flagCount: 2 });
|
||||
|
||||
render(
|
||||
<Routes>
|
||||
<Route
|
||||
path='/projects/:projectId/create-toggle'
|
||||
element={<CreateFeature />}
|
||||
/>
|
||||
</Routes>,
|
||||
{
|
||||
route: '/projects/default/create-toggle',
|
||||
permissions: [{ permission: CREATE_FEATURE }],
|
||||
},
|
||||
);
|
||||
|
||||
const button = await screen.findByRole('button', {
|
||||
name: /create feature flag/i,
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(button).not.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');
|
||||
});
|
||||
});
|
@ -1,235 +0,0 @@
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import FeatureForm from '../FeatureForm/FeatureForm';
|
||||
import useFeatureForm from '../hooks/useFeatureForm';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { useContext } from 'react';
|
||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||
import UIContext from 'contexts/UIContext';
|
||||
import { CF_CREATE_BTN_ID } from 'utils/testIds';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { GO_BACK } from 'constants/navigate';
|
||||
import { Alert, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import useProjectOverview, {
|
||||
featuresCount,
|
||||
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useGlobalFeatureSearch } from '../FeatureToggleList/useGlobalFeatureSearch';
|
||||
import { Limit } from 'component/common/Limit/Limit';
|
||||
|
||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const isProjectFeatureLimitReached = (
|
||||
featureLimit: number | null | undefined,
|
||||
currentFeatureCount: number,
|
||||
): boolean => {
|
||||
return (
|
||||
featureLimit !== null &&
|
||||
featureLimit !== undefined &&
|
||||
featureLimit <= currentFeatureCount
|
||||
);
|
||||
};
|
||||
|
||||
const useGlobalFlagLimit = (flagLimit: number, flagCount: number) => {
|
||||
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||
const limitReached = resourceLimitsEnabled && flagCount >= flagLimit;
|
||||
|
||||
return {
|
||||
limitReached,
|
||||
limitMessage: limitReached
|
||||
? `You have reached the instance-wide limit of ${flagLimit} feature flags.`
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
type FlagLimitsProps = {
|
||||
global: { limit: number; count: number };
|
||||
project: { limit?: number; count: number };
|
||||
};
|
||||
|
||||
export const useFlagLimits = ({ global, project }: FlagLimitsProps) => {
|
||||
const {
|
||||
limitReached: globalFlagLimitReached,
|
||||
limitMessage: globalLimitMessage,
|
||||
} = useGlobalFlagLimit(global.limit, global.count);
|
||||
|
||||
const projectFlagLimitReached = isProjectFeatureLimitReached(
|
||||
project.limit,
|
||||
project.count,
|
||||
);
|
||||
|
||||
const limitMessage = globalFlagLimitReached
|
||||
? globalLimitMessage
|
||||
: projectFlagLimitReached
|
||||
? `You have reached the project limit of ${project.limit} feature flags.`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
limitMessage,
|
||||
globalFlagLimitReached,
|
||||
projectFlagLimitReached,
|
||||
};
|
||||
};
|
||||
|
||||
const CreateFeature = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { setShowFeedback } = useContext(UIContext);
|
||||
const { uiConfig } = useUiConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
type,
|
||||
setType,
|
||||
name,
|
||||
setName,
|
||||
project,
|
||||
setProject,
|
||||
description,
|
||||
setDescription,
|
||||
validateToggleName,
|
||||
impressionData,
|
||||
setImpressionData,
|
||||
getTogglePayload,
|
||||
clearErrors,
|
||||
errors,
|
||||
} = useFeatureForm();
|
||||
|
||||
const { project: projectInfo } = useProjectOverview(project);
|
||||
|
||||
const { createFeatureToggle, loading } = useFeatureApi();
|
||||
|
||||
const { total: totalFlags, loading: loadingTotalFlagCount } =
|
||||
useGlobalFeatureSearch();
|
||||
|
||||
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||
|
||||
const { globalFlagLimitReached, projectFlagLimitReached, limitMessage } =
|
||||
useFlagLimits({
|
||||
global: {
|
||||
limit: uiConfig.resourceLimits.featureFlags,
|
||||
count: totalFlags ?? 0,
|
||||
},
|
||||
project: {
|
||||
limit: projectInfo.featureLimit,
|
||||
count: featuresCount(projectInfo),
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
clearErrors();
|
||||
const validToggleName = await validateToggleName();
|
||||
|
||||
if (validToggleName) {
|
||||
const payload = getTogglePayload();
|
||||
try {
|
||||
await createFeatureToggle(project, payload);
|
||||
navigate(`/projects/${project}/features/${name}`, {
|
||||
replace: true,
|
||||
});
|
||||
setToastData({
|
||||
title: 'Flag created successfully',
|
||||
text: 'Now you can start using your flag.',
|
||||
confetti: true,
|
||||
type: 'success',
|
||||
});
|
||||
setShowFeedback(true);
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formatApiCode = () => {
|
||||
return `curl --location --request POST '${
|
||||
uiConfig.unleashUrl
|
||||
}/api/admin/projects/${project}/features' \\
|
||||
--header 'Authorization: INSERT_API_KEY' \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--data-raw '${JSON.stringify(getTogglePayload(), undefined, 2)}'`;
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate(GO_BACK);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
title='Create feature flag'
|
||||
description='Feature flags support different use cases, each with their own specific needs such as simple static routing or more complex routing.
|
||||
The feature flag is disabled when created and you decide when to enable'
|
||||
documentationLink='https://docs.getunleash.io/reference/feature-toggle-types'
|
||||
documentationLinkLabel='Feature flag types documentation'
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={projectFlagLimitReached}
|
||||
show={
|
||||
<StyledAlert severity='error'>
|
||||
<strong>Feature flag project limit reached. </strong> To
|
||||
be able to create more feature flags in this project
|
||||
please increase the feature flag upper limit in the
|
||||
project settings.
|
||||
</StyledAlert>
|
||||
}
|
||||
/>
|
||||
|
||||
<FeatureForm
|
||||
type={type}
|
||||
name={name}
|
||||
project={project}
|
||||
description={description}
|
||||
setType={setType}
|
||||
setName={setName}
|
||||
setProject={setProject}
|
||||
setDescription={setDescription}
|
||||
validateToggleName={validateToggleName}
|
||||
setImpressionData={setImpressionData}
|
||||
impressionData={impressionData}
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
mode='Create'
|
||||
clearErrors={clearErrors}
|
||||
featureNaming={projectInfo.featureNaming}
|
||||
Limit={
|
||||
<ConditionallyRender
|
||||
condition={resourceLimitsEnabled}
|
||||
show={
|
||||
<Limit
|
||||
name='feature flags'
|
||||
limit={uiConfig.resourceLimits.featureFlags}
|
||||
currentValue={totalFlags ?? 0}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CreateButton
|
||||
name='feature flag'
|
||||
disabled={
|
||||
loadingTotalFlagCount ||
|
||||
globalFlagLimitReached ||
|
||||
projectFlagLimitReached
|
||||
}
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={project}
|
||||
data-testid={CF_CREATE_BTN_ID}
|
||||
tooltipProps={{
|
||||
title: limitMessage,
|
||||
arrow: true,
|
||||
}}
|
||||
/>
|
||||
</FeatureForm>
|
||||
</FormTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateFeature;
|
@ -1,6 +1,6 @@
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import FeatureForm from '../FeatureForm/FeatureForm';
|
||||
import EditFeatureForm from '../FeatureForm/EditFeatureForm';
|
||||
import useFeatureForm from '../hooks/useFeatureForm';
|
||||
import * as jsonpatch from 'fast-json-patch';
|
||||
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||
@ -26,15 +26,12 @@ const EditFeature = () => {
|
||||
type,
|
||||
setType,
|
||||
name,
|
||||
setName,
|
||||
project,
|
||||
setProject,
|
||||
description,
|
||||
setDescription,
|
||||
impressionData,
|
||||
setImpressionData,
|
||||
clearErrors,
|
||||
errors,
|
||||
} = useFeatureForm(
|
||||
feature?.name,
|
||||
feature?.type,
|
||||
@ -88,25 +85,19 @@ const EditFeature = () => {
|
||||
documentationLinkLabel='Feature flag types documentation'
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
<FeatureForm
|
||||
<EditFeatureForm
|
||||
type={type}
|
||||
name={name}
|
||||
project={project}
|
||||
description={description}
|
||||
setType={setType}
|
||||
setName={setName}
|
||||
setProject={setProject}
|
||||
setDescription={setDescription}
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
impressionData={impressionData}
|
||||
setImpressionData={setImpressionData}
|
||||
mode='Edit'
|
||||
clearErrors={clearErrors}
|
||||
>
|
||||
<UpdateButton permission={UPDATE_FEATURE} projectId={project} />
|
||||
</FeatureForm>
|
||||
</EditFeatureForm>
|
||||
</FormTemplate>
|
||||
);
|
||||
};
|
||||
|
@ -10,40 +10,23 @@ import {
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
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_TYPE_ID } from 'utils/testIds';
|
||||
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
||||
import KeyboardArrowDownOutlined from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import { projectFilterGenerator } from 'utils/projectFilterGenerator';
|
||||
import FeatureProjectSelect from '../FeatureView/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { trim } from 'component/common/util';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
|
||||
import type { FeatureNamingType } from 'interfaces/project';
|
||||
import { FeatureNamingPatternInfo } from '../FeatureNamingPatternInfo/FeatureNamingPatternInfo';
|
||||
import type React from 'react';
|
||||
import type { CreateFeatureSchemaType } from 'openapi';
|
||||
|
||||
interface IFeatureToggleForm {
|
||||
type: CreateFeatureSchemaType;
|
||||
name: string;
|
||||
description: string;
|
||||
project: string;
|
||||
impressionData: boolean;
|
||||
setType: React.Dispatch<React.SetStateAction<CreateFeatureSchemaType>>;
|
||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||
setDescription: React.Dispatch<React.SetStateAction<string>>;
|
||||
setProject: React.Dispatch<React.SetStateAction<string>>;
|
||||
setImpressionData: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
featureNaming?: FeatureNamingType;
|
||||
validateToggleName?: () => void;
|
||||
handleSubmit: (e: any) => void;
|
||||
handleCancel: () => void;
|
||||
errors: { [key: string]: string };
|
||||
mode: 'Create' | 'Edit';
|
||||
clearErrors: () => void;
|
||||
children?: React.ReactNode;
|
||||
Limit?: React.ReactNode;
|
||||
}
|
||||
@ -106,73 +89,37 @@ const LimitContainer = styled(Box)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
const EditFeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
children,
|
||||
type,
|
||||
name,
|
||||
description,
|
||||
project,
|
||||
setType,
|
||||
setName,
|
||||
setDescription,
|
||||
setProject,
|
||||
validateToggleName,
|
||||
featureNaming,
|
||||
setImpressionData,
|
||||
impressionData,
|
||||
handleSubmit,
|
||||
handleCancel,
|
||||
errors,
|
||||
mode,
|
||||
clearErrors,
|
||||
Limit,
|
||||
}) => {
|
||||
const { featureTypes } = useFeatureTypes();
|
||||
const navigate = useNavigate();
|
||||
const { permissions } = useAuthPermissions();
|
||||
const editable = mode !== 'Edit';
|
||||
|
||||
const renderToggleDescription = () => {
|
||||
return featureTypes.find((flag) => flag.id === type)?.description;
|
||||
};
|
||||
|
||||
const displayFeatureNamingInfo = Boolean(featureNaming?.pattern);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (featureNaming?.pattern && validateToggleName && name) {
|
||||
clearErrors();
|
||||
validateToggleName();
|
||||
}
|
||||
}, [featureNaming?.pattern]);
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<StyledInputDescription>
|
||||
What would you like to call your flag?
|
||||
</StyledInputDescription>
|
||||
<ConditionallyRender
|
||||
condition={displayFeatureNamingInfo}
|
||||
show={
|
||||
<FeatureNamingPatternInfo featureNaming={featureNaming!} />
|
||||
}
|
||||
/>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
disabled={mode === 'Edit'}
|
||||
disabled={true}
|
||||
label='Name'
|
||||
aria-details={
|
||||
displayFeatureNamingInfo
|
||||
? 'feature-naming-pattern-info'
|
||||
: undefined
|
||||
}
|
||||
id='feature-flag-name'
|
||||
error={Boolean(errors.name)}
|
||||
errorText={errors.name}
|
||||
onFocus={() => clearErrors()}
|
||||
value={name}
|
||||
onChange={(e) => setName(trim(e.target.value))}
|
||||
data-testid={CF_NAME_ID}
|
||||
onBlur={validateToggleName}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<StyledInputDescription>
|
||||
What kind of feature flag do you want?
|
||||
@ -190,31 +137,6 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
<StyledTypeDescription>
|
||||
{renderToggleDescription()}
|
||||
</StyledTypeDescription>
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<StyledInputDescription>
|
||||
In which project do you want to save the flag?
|
||||
</StyledInputDescription>
|
||||
}
|
||||
/>
|
||||
{/* TODO: this can be removed after new create flag flow goes live */}
|
||||
<FeatureProjectSelect
|
||||
value={project}
|
||||
onChange={(projectId) => {
|
||||
setProject(projectId);
|
||||
navigate(`/projects/${projectId}/create-toggle`, {
|
||||
replace: true,
|
||||
});
|
||||
}}
|
||||
enabled={editable}
|
||||
filter={projectFilterGenerator(
|
||||
permissions || [],
|
||||
CREATE_FEATURE,
|
||||
)}
|
||||
IconComponent={KeyboardArrowDownOutlined}
|
||||
sx={styledSelectInput}
|
||||
/>
|
||||
<StyledInputDescription>
|
||||
How would you describe your feature flag?
|
||||
</StyledInputDescription>
|
||||
@ -276,4 +198,4 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureForm;
|
||||
export default EditFeatureForm;
|
@ -65,14 +65,6 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"title": "FeatureView",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
"parent": "/projects/:projectId/features",
|
||||
"path": "/projects/:projectId/create-toggle",
|
||||
"title": "Create feature flag",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
"$$typeof": Symbol(react.lazy),
|
||||
|
@ -16,7 +16,6 @@ import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironm
|
||||
import { EditContext } from 'component/context/EditContext/EditContext';
|
||||
import EditTagType from 'component/tags/EditTagType/EditTagType';
|
||||
import CreateTagType from 'component/tags/CreateTagType/CreateTagType';
|
||||
import CreateFeature from 'component/feature/CreateFeature/CreateFeature';
|
||||
import EditFeature from 'component/feature/EditFeature/EditFeature';
|
||||
import ContextList from 'component/context/ContextList/ContextList/ContextList';
|
||||
import { CreateIntegration } from 'component/integrations/CreateIntegration/CreateIntegration';
|
||||
@ -102,14 +101,6 @@ export const routes: IRoute[] = [
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/projects/:projectId/create-toggle',
|
||||
parent: '/projects/:projectId/features',
|
||||
title: 'Create feature flag',
|
||||
component: CreateFeature,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/projects/:projectId/*',
|
||||
parent: '/projects',
|
||||
|
@ -21,7 +21,6 @@ import useFeatureForm from 'component/feature/hooks/useFeatureForm';
|
||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
import FlagIcon from '@mui/icons-material/Flag';
|
||||
import ImpressionDataIcon from '@mui/icons-material/AltRoute';
|
||||
import { useFlagLimits } from 'component/feature/CreateFeature/CreateFeature';
|
||||
import { useGlobalFeatureSearch } from 'component/feature/FeatureToggleList/useGlobalFeatureSearch';
|
||||
import useProjectOverview, {
|
||||
featuresCount,
|
||||
@ -37,6 +36,7 @@ import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
|
||||
import { MultiSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton';
|
||||
import type { ITag } from 'interfaces/tags';
|
||||
import { ToggleConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/ToggleConfigButton';
|
||||
import { useFlagLimits } from './useFlagLimits';
|
||||
|
||||
interface ICreateFeatureDialogProps {
|
||||
open: boolean;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isProjectFeatureLimitReached } from './CreateFeature';
|
||||
import { isProjectFeatureLimitReached } from './useFlagLimits';
|
||||
|
||||
test('isFeatureLimitReached should return false when featureLimit is null', async () => {
|
||||
expect(isProjectFeatureLimitReached(null, 5)).toBe(false);
|
@ -1,6 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useFlagLimits } from './CreateFeature';
|
||||
import { vi } from 'vitest';
|
||||
import { useFlagLimits } from './useFlagLimits';
|
||||
|
||||
vi.mock('hooks/useUiFlag', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
@ -0,0 +1,53 @@
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
type FlagLimitsProps = {
|
||||
global: { limit: number; count: number };
|
||||
project: { limit?: number; count: number };
|
||||
};
|
||||
|
||||
export const useFlagLimits = ({ global, project }: FlagLimitsProps) => {
|
||||
const {
|
||||
limitReached: globalFlagLimitReached,
|
||||
limitMessage: globalLimitMessage,
|
||||
} = useGlobalFlagLimit(global.limit, global.count);
|
||||
|
||||
const projectFlagLimitReached = isProjectFeatureLimitReached(
|
||||
project.limit,
|
||||
project.count,
|
||||
);
|
||||
|
||||
const limitMessage = globalFlagLimitReached
|
||||
? globalLimitMessage
|
||||
: projectFlagLimitReached
|
||||
? `You have reached the project limit of ${project.limit} feature flags.`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
limitMessage,
|
||||
globalFlagLimitReached,
|
||||
projectFlagLimitReached,
|
||||
};
|
||||
};
|
||||
|
||||
const useGlobalFlagLimit = (flagLimit: number, flagCount: number) => {
|
||||
const resourceLimitsEnabled = useUiFlag('resourceLimits');
|
||||
const limitReached = resourceLimitsEnabled && flagCount >= flagLimit;
|
||||
|
||||
return {
|
||||
limitReached,
|
||||
limitMessage: limitReached
|
||||
? `You have reached the instance-wide limit of ${flagLimit} feature flags.`
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const isProjectFeatureLimitReached = (
|
||||
featureLimit: number | null | undefined,
|
||||
currentFeatureCount: number,
|
||||
): boolean => {
|
||||
return (
|
||||
featureLimit !== null &&
|
||||
featureLimit !== undefined &&
|
||||
featureLimit <= currentFeatureCount
|
||||
);
|
||||
};
|
@ -6,7 +6,7 @@ export const getCreateTogglePath = (
|
||||
projectId: string,
|
||||
query?: Record<string, string>,
|
||||
) => {
|
||||
const path = `/projects/${projectId}/create-toggle`;
|
||||
const path = `/projects/${projectId}?create=true`;
|
||||
|
||||
let queryString: string | undefined;
|
||||
if (query) {
|
||||
@ -16,12 +16,8 @@ export const getCreateTogglePath = (
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
return `${path}?${queryString}`;
|
||||
return `${path}&${queryString}`;
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getProjectEditPath = (projectId: string) => {
|
||||
return `/projects/${projectId}/settings`;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user