diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
index f022381f6cce..937dd38814b7 100644
--- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
+++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx
@@ -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',
diff --git a/frontend/src/component/feature/CreateFeature/CreateFeature.test.tsx b/frontend/src/component/feature/CreateFeature/CreateFeature.test.tsx
deleted file mode 100644
index 6215809f4dd9..000000000000
--- a/frontend/src/component/feature/CreateFeature/CreateFeature.test.tsx
+++ /dev/null
@@ -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(
-
- }
- />
- ,
- {
- 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(
-
- }
- />
- ,
- {
- 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(
-
- }
- />
- ,
- {
- route: '/projects/default/create-toggle',
- permissions: [{ permission: CREATE_FEATURE }],
- },
- );
-
- await screen.findByText('You are nearing the limit for feature flags');
- });
-});
diff --git a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx
deleted file mode 100644
index bb059bf26ff8..000000000000
--- a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx
+++ /dev/null
@@ -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 (
-
-
- Feature flag project limit reached. To
- be able to create more feature flags in this project
- please increase the feature flag upper limit in the
- project settings.
-
- }
- />
-
-
- }
- />
- }
- >
-
-
-
- );
-};
-
-export default CreateFeature;
diff --git a/frontend/src/component/feature/EditFeature/EditFeature.tsx b/frontend/src/component/feature/EditFeature/EditFeature.tsx
index 333f1d7e9f40..77066c8190d6 100644
--- a/frontend/src/component/feature/EditFeature/EditFeature.tsx
+++ b/frontend/src/component/feature/EditFeature/EditFeature.tsx
@@ -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}
>
-
-
+
);
};
diff --git a/frontend/src/component/feature/FeatureForm/FeatureForm.tsx b/frontend/src/component/feature/FeatureForm/EditFeatureForm.tsx
similarity index 63%
rename from frontend/src/component/feature/FeatureForm/FeatureForm.tsx
rename to frontend/src/component/feature/FeatureForm/EditFeatureForm.tsx
index 57ae8124f775..793942cccf1d 100644
--- a/frontend/src/component/feature/FeatureForm/FeatureForm.tsx
+++ b/frontend/src/component/feature/FeatureForm/EditFeatureForm.tsx
@@ -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>;
- setName: React.Dispatch>;
setDescription: React.Dispatch>;
- setProject: React.Dispatch>;
setImpressionData: React.Dispatch>;
- 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 = ({
+const EditFeatureForm: React.FC = ({
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 (
What would you like to call your flag?
-
- }
- />
clearErrors()}
value={name}
- onChange={(e) => setName(trim(e.target.value))}
- data-testid={CF_NAME_ID}
- onBlur={validateToggleName}
+ onChange={() => {}}
/>
What kind of feature flag do you want?
@@ -190,31 +137,6 @@ const FeatureForm: React.FC = ({
{renderToggleDescription()}
-
- In which project do you want to save the flag?
-
- }
- />
- {/* TODO: this can be removed after new create flag flow goes live */}
- {
- setProject(projectId);
- navigate(`/projects/${projectId}/create-toggle`, {
- replace: true,
- });
- }}
- enabled={editable}
- filter={projectFilterGenerator(
- permissions || [],
- CREATE_FEATURE,
- )}
- IconComponent={KeyboardArrowDownOutlined}
- sx={styledSelectInput}
- />
How would you describe your feature flag?
@@ -276,4 +198,4 @@ const FeatureForm: React.FC = ({
);
};
-export default FeatureForm;
+export default EditFeatureForm;
diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
index 9f3ab5c999c2..e347aed92c26 100644
--- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
+++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
@@ -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),
diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts
index b7901e015719..d6373d94e8a9 100644
--- a/frontend/src/component/menu/routes.ts
+++ b/frontend/src/component/menu/routes.ts
@@ -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',
diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx
index 616434619f87..66dd34060d81 100644
--- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx
@@ -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;
diff --git a/frontend/src/component/feature/CreateFeature/isProjectFeatureLimitReached.test.ts b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/isProjectFeatureLimitReached.test.ts
similarity index 93%
rename from frontend/src/component/feature/CreateFeature/isProjectFeatureLimitReached.test.ts
rename to frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/isProjectFeatureLimitReached.test.ts
index 77a520f9792e..d3d764f1fc58 100644
--- a/frontend/src/component/feature/CreateFeature/isProjectFeatureLimitReached.test.ts
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/isProjectFeatureLimitReached.test.ts
@@ -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);
diff --git a/frontend/src/component/feature/CreateFeature/useFlagLimits.test.ts b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.test.ts
similarity index 97%
rename from frontend/src/component/feature/CreateFeature/useFlagLimits.test.ts
rename to frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.test.ts
index b8abb2cf312a..a785ec26e366 100644
--- a/frontend/src/component/feature/CreateFeature/useFlagLimits.test.ts
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.test.ts
@@ -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();
diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.tsx
new file mode 100644
index 000000000000..c41d26a70b6b
--- /dev/null
+++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/useFlagLimits.tsx
@@ -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
+ );
+};
diff --git a/frontend/src/utils/routePathHelpers.ts b/frontend/src/utils/routePathHelpers.ts
index bab9a6c391d0..177937ac914f 100644
--- a/frontend/src/utils/routePathHelpers.ts
+++ b/frontend/src/utils/routePathHelpers.ts
@@ -6,7 +6,7 @@ export const getCreateTogglePath = (
projectId: string,
query?: Record,
) => {
- 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`;
-};