mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-08 01:15:49 +02:00
chore: remove createProjectWithEnvironmentConfig and newCreateProjectUI flags (#7429)
This PR removes the last two flags related to the project managament improvements project, making the new project creation form GA. In doing so, we can also delete the old project creation form (or at least the page, the form is still in use in the project settings).
This commit is contained in:
parent
5b4ff92454
commit
0af5bbad38
@ -1,141 +1,7 @@
|
|||||||
import { Navigate, useNavigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import ProjectForm from '../ProjectForm/ProjectForm';
|
|
||||||
import useProjectForm, {
|
|
||||||
DEFAULT_PROJECT_STICKINESS,
|
|
||||||
} from '../hooks/useProjectForm';
|
|
||||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
|
||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
|
||||||
import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
|
||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { GO_BACK } from 'constants/navigate';
|
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|
||||||
import { Button, styled } from '@mui/material';
|
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
|
||||||
|
|
||||||
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)(({ theme }) => ({
|
|
||||||
marginLeft: theme.spacing(3),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const CreateProject = () => {
|
const CreateProject = () => {
|
||||||
const { setToastData, setToastApiError } = useToast();
|
return <Navigate to={`/projects?create=true`} replace />;
|
||||||
const { refetchUser } = useAuthUser();
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const useNewProjectForm = useUiFlag('newCreateProjectUI');
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
|
||||||
const {
|
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
projectDesc,
|
|
||||||
projectMode,
|
|
||||||
setProjectMode,
|
|
||||||
setProjectId,
|
|
||||||
setProjectName,
|
|
||||||
setProjectDesc,
|
|
||||||
getCreateProjectPayload,
|
|
||||||
clearErrors,
|
|
||||||
validateProjectId,
|
|
||||||
validateName,
|
|
||||||
setProjectStickiness,
|
|
||||||
projectStickiness,
|
|
||||||
errors,
|
|
||||||
} = useProjectForm();
|
|
||||||
|
|
||||||
if (useNewProjectForm) {
|
|
||||||
return <Navigate to={`/projects?create=true`} replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { createProject, loading } = useProjectApi();
|
|
||||||
|
|
||||||
const handleSubmit = async (e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
clearErrors();
|
|
||||||
const validName = validateName();
|
|
||||||
const validId = useNewProjectForm || (await validateProjectId());
|
|
||||||
|
|
||||||
if (validName && validId) {
|
|
||||||
const payload = getCreateProjectPayload({
|
|
||||||
omitId: useNewProjectForm,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const createdProject = await createProject(payload);
|
|
||||||
refetchUser();
|
|
||||||
navigate(`/projects/${createdProject.id}`, { replace: true });
|
|
||||||
setToastData({
|
|
||||||
title: 'Project created',
|
|
||||||
text: 'Now you can add flags to this project',
|
|
||||||
confetti: true,
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
|
|
||||||
trackEvent('project_stickiness_set');
|
|
||||||
}
|
|
||||||
trackEvent('project-mode', {
|
|
||||||
props: { mode: projectMode, action: 'added' },
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatApiCode = () => {
|
|
||||||
return `curl --location --request POST '${uiConfig.unleashUrl}/api/admin/projects' \\
|
|
||||||
--header 'Authorization: INSERT_API_KEY' \\
|
|
||||||
--header 'Content-Type: application/json' \\
|
|
||||||
--data-raw '${JSON.stringify(
|
|
||||||
getCreateProjectPayload({ omitId: useNewProjectForm }),
|
|
||||||
undefined,
|
|
||||||
2,
|
|
||||||
)}'`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
navigate(GO_BACK);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormTemplate
|
|
||||||
loading={loading}
|
|
||||||
title='Create project'
|
|
||||||
description='Projects allows you to group feature flags together in the management UI.'
|
|
||||||
documentationLink='https://docs.getunleash.io/reference/projects'
|
|
||||||
documentationLinkLabel='Projects documentation'
|
|
||||||
formatApiCode={formatApiCode}
|
|
||||||
>
|
|
||||||
<ProjectForm
|
|
||||||
errors={errors}
|
|
||||||
handleSubmit={handleSubmit}
|
|
||||||
projectId={projectId}
|
|
||||||
setProjectId={setProjectId}
|
|
||||||
projectName={projectName}
|
|
||||||
projectStickiness={projectStickiness}
|
|
||||||
projectMode={projectMode}
|
|
||||||
setProjectMode={setProjectMode}
|
|
||||||
setProjectStickiness={setProjectStickiness}
|
|
||||||
setProjectName={setProjectName}
|
|
||||||
projectDesc={projectDesc}
|
|
||||||
setProjectDesc={setProjectDesc}
|
|
||||||
mode='Create'
|
|
||||||
clearErrors={clearErrors}
|
|
||||||
validateProjectId={validateProjectId}
|
|
||||||
>
|
|
||||||
<CreateButton
|
|
||||||
name='project'
|
|
||||||
permission={CREATE_PROJECT}
|
|
||||||
data-testid={CREATE_PROJECT_BTN}
|
|
||||||
/>
|
|
||||||
<StyledButton onClick={handleCancel}>Cancel</StyledButton>
|
|
||||||
</ProjectForm>
|
|
||||||
</FormTemplate>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateProject;
|
export default CreateProject;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import type { IProjectCard } from 'interfaces/project';
|
import type { IProjectCard } from 'interfaces/project';
|
||||||
@ -20,7 +20,6 @@ import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-featu
|
|||||||
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
|
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
|
||||||
import { safeRegExp } from '@server/util/escape-regex';
|
import { safeRegExp } from '@server/util/escape-regex';
|
||||||
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
|
||||||
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
||||||
import { groupProjects } from './group-projects';
|
import { groupProjects } from './group-projects';
|
||||||
import { ProjectGroup } from './ProjectGroup';
|
import { ProjectGroup } from './ProjectGroup';
|
||||||
@ -92,7 +91,6 @@ const ProjectCreationButton = () => {
|
|||||||
const showCreateDialog = Boolean(searchParams.get('create'));
|
const showCreateDialog = Boolean(searchParams.get('create'));
|
||||||
const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog);
|
const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog);
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const navigate = useNavigate();
|
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
|
|
||||||
const createButtonData = resolveCreateButtonData(
|
const createButtonData = resolveCreateButtonData(
|
||||||
@ -100,14 +98,12 @@ const ProjectCreationButton = () => {
|
|||||||
hasAccess(CREATE_PROJECT),
|
hasAccess(CREATE_PROJECT),
|
||||||
);
|
);
|
||||||
|
|
||||||
const useNewProjectForm = useUiFlag('newCreateProjectUI');
|
return (
|
||||||
|
<>
|
||||||
const CreateButton: React.FC<{ onClick: () => void }> = ({ onClick }) => {
|
|
||||||
return (
|
|
||||||
<ResponsiveButton
|
<ResponsiveButton
|
||||||
Icon={Add}
|
Icon={Add}
|
||||||
endIcon={createButtonData.endIcon}
|
endIcon={createButtonData.endIcon}
|
||||||
onClick={onClick}
|
onClick={() => setOpenCreateDialog(true)}
|
||||||
maxWidth='700px'
|
maxWidth='700px'
|
||||||
permission={CREATE_PROJECT}
|
permission={CREATE_PROJECT}
|
||||||
disabled={createButtonData.disabled}
|
disabled={createButtonData.disabled}
|
||||||
@ -116,22 +112,12 @@ const ProjectCreationButton = () => {
|
|||||||
>
|
>
|
||||||
New project
|
New project
|
||||||
</ResponsiveButton>
|
</ResponsiveButton>
|
||||||
);
|
<CreateProjectDialog
|
||||||
};
|
open={openCreateDialog}
|
||||||
|
onClose={() => setOpenCreateDialog(false)}
|
||||||
if (useNewProjectForm) {
|
/>
|
||||||
return (
|
</>
|
||||||
<>
|
);
|
||||||
<CreateButton onClick={() => setOpenCreateDialog(true)} />
|
|
||||||
<CreateProjectDialog
|
|
||||||
open={openCreateDialog}
|
|
||||||
onClose={() => setOpenCreateDialog(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <CreateButton onClick={() => navigate('/projects/create')} />;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectListNew = () => {
|
export const ProjectListNew = () => {
|
||||||
|
@ -81,8 +81,6 @@ export type UiFlags = {
|
|||||||
projectOverviewRefactorFeedback?: boolean;
|
projectOverviewRefactorFeedback?: boolean;
|
||||||
featureLifecycle?: boolean;
|
featureLifecycle?: boolean;
|
||||||
scimApi?: boolean;
|
scimApi?: boolean;
|
||||||
createProjectWithEnvironmentConfig?: boolean;
|
|
||||||
newCreateProjectUI?: boolean;
|
|
||||||
manyStrategiesPagination?: boolean;
|
manyStrategiesPagination?: boolean;
|
||||||
enableLegacyVariants?: boolean;
|
enableLegacyVariants?: boolean;
|
||||||
navigationSidebar?: boolean;
|
navigationSidebar?: boolean;
|
||||||
|
@ -83,7 +83,6 @@ exports[`should create default config 1`] = `
|
|||||||
"celebrateUnleash": false,
|
"celebrateUnleash": false,
|
||||||
"collectTrafficDataUsage": false,
|
"collectTrafficDataUsage": false,
|
||||||
"commandBarUI": false,
|
"commandBarUI": false,
|
||||||
"createProjectWithEnvironmentConfig": false,
|
|
||||||
"demo": false,
|
"demo": false,
|
||||||
"disableBulkToggle": false,
|
"disableBulkToggle": false,
|
||||||
"disableMetrics": false,
|
"disableMetrics": false,
|
||||||
@ -138,7 +137,6 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
"migrationLock": true,
|
"migrationLock": true,
|
||||||
"navigationSidebar": true,
|
"navigationSidebar": true,
|
||||||
"newCreateProjectUI": false,
|
|
||||||
"outdatedSdksBanner": false,
|
"outdatedSdksBanner": false,
|
||||||
"parseProjectFromSession": false,
|
"parseProjectFromSession": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
|
@ -79,9 +79,7 @@ beforeAll(async () => {
|
|||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
getLogger,
|
getLogger,
|
||||||
experimental: {
|
experimental: {
|
||||||
flags: {
|
flags: {},
|
||||||
createProjectWithEnvironmentConfig: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
eventService = new EventService(stores, config);
|
eventService = new EventService(stores, config);
|
||||||
@ -2715,26 +2713,5 @@ describe('automatic ID generation for create project', () => {
|
|||||||
expect(project.id).toBe(id);
|
expect(project.id).toBe(id);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(['', undefined, ' '])(
|
|
||||||
'if the flag to enable auto ID generation is off, not providing a valid ID (testing `%s`) throws an error',
|
|
||||||
async (id) => {
|
|
||||||
// @ts-expect-error
|
|
||||||
projectService.flagResolver.isEnabled = () => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createProject = () =>
|
|
||||||
projectService.createProject(
|
|
||||||
{
|
|
||||||
name: randomId(),
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
user,
|
|
||||||
auditUser,
|
|
||||||
);
|
|
||||||
expect(createProject).rejects.toThrow();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -294,10 +294,7 @@ export default class ProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validateProjectEnvironments(environments: string[] | undefined) {
|
async validateProjectEnvironments(environments: string[] | undefined) {
|
||||||
if (
|
if (environments) {
|
||||||
this.flagResolver.isEnabled('createProjectWithEnvironmentConfig') &&
|
|
||||||
environments
|
|
||||||
) {
|
|
||||||
if (environments.length === 0) {
|
if (environments.length === 0) {
|
||||||
throw new BadDataError(
|
throw new BadDataError(
|
||||||
'A project must always have at least one environment.',
|
'A project must always have at least one environment.',
|
||||||
@ -339,12 +336,7 @@ export default class ProjectService {
|
|||||||
const validateData = async () => {
|
const validateData = async () => {
|
||||||
await this.validateProjectEnvironments(newProject.environments);
|
await this.validateProjectEnvironments(newProject.environments);
|
||||||
|
|
||||||
if (
|
if (!newProject.id?.trim()) {
|
||||||
!newProject.id?.trim() &&
|
|
||||||
this.flagResolver.isEnabled(
|
|
||||||
'createProjectWithEnvironmentConfig',
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newProject.id = await this.generateUniqueProjectId(
|
newProject.id = await this.generateUniqueProjectId(
|
||||||
newProject.name,
|
newProject.name,
|
||||||
);
|
);
|
||||||
@ -362,15 +354,13 @@ export default class ProjectService {
|
|||||||
|
|
||||||
await this.projectStore.create(data);
|
await this.projectStore.create(data);
|
||||||
|
|
||||||
const envsToEnable =
|
const envsToEnable = newProject.environments?.length
|
||||||
this.flagResolver.isEnabled('createProjectWithEnvironmentConfig') &&
|
? newProject.environments
|
||||||
newProject.environments?.length
|
: (
|
||||||
? newProject.environments
|
await this.environmentStore.getAll({
|
||||||
: (
|
enabled: true,
|
||||||
await this.environmentStore.getAll({
|
})
|
||||||
enabled: true,
|
).map((env) => env.name);
|
||||||
})
|
|
||||||
).map((env) => env.name);
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
envsToEnable.map(async (env) => {
|
envsToEnable.map(async (env) => {
|
||||||
@ -378,10 +368,7 @@ export default class ProjectService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (this.isEnterprise) {
|
||||||
this.isEnterprise &&
|
|
||||||
this.flagResolver.isEnabled('createProjectWithEnvironmentConfig')
|
|
||||||
) {
|
|
||||||
if (newProject.changeRequestEnvironments) {
|
if (newProject.changeRequestEnvironments) {
|
||||||
await this.validateEnvironmentsExist(
|
await this.validateEnvironmentsExist(
|
||||||
newProject.changeRequestEnvironments.map((env) => env.name),
|
newProject.changeRequestEnvironments.map((env) => env.name),
|
||||||
|
@ -56,9 +56,7 @@ export type IFlagKey =
|
|||||||
| 'featureLifecycle'
|
| 'featureLifecycle'
|
||||||
| 'featureLifecycleMetrics'
|
| 'featureLifecycleMetrics'
|
||||||
| 'parseProjectFromSession'
|
| 'parseProjectFromSession'
|
||||||
| 'createProjectWithEnvironmentConfig'
|
|
||||||
| 'manyStrategiesPagination'
|
| 'manyStrategiesPagination'
|
||||||
| 'newCreateProjectUI'
|
|
||||||
| 'enableLegacyVariants'
|
| 'enableLegacyVariants'
|
||||||
| 'navigationSidebar'
|
| 'navigationSidebar'
|
||||||
| 'commandBarUI'
|
| 'commandBarUI'
|
||||||
@ -272,14 +270,6 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_PARSE_PROJECT_FROM_SESSION,
|
process.env.UNLEASH_EXPERIMENTAL_PARSE_PROJECT_FROM_SESSION,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
createProjectWithEnvironmentConfig: parseEnvVarBoolean(
|
|
||||||
process.env.UNLEASH_EXPERIMENTAL_CREATE_PROJECT_WITH_ENVIRONMENT_CONFIG,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
newCreateProjectUI: parseEnvVarBoolean(
|
|
||||||
process.env.UNLEASH_EXPERIMENTAL_NEW_CREATE_PROJECT_UI,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
manyStrategiesPagination: parseEnvVarBoolean(
|
manyStrategiesPagination: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_MANY_STRATEGIES_PAGINATION,
|
process.env.UNLEASH_EXPERIMENTAL_MANY_STRATEGIES_PAGINATION,
|
||||||
false,
|
false,
|
||||||
|
@ -50,7 +50,6 @@ process.nextTick(async () => {
|
|||||||
projectOverviewRefactorFeedback: true,
|
projectOverviewRefactorFeedback: true,
|
||||||
featureLifecycle: true,
|
featureLifecycle: true,
|
||||||
parseProjectFromSession: true,
|
parseProjectFromSession: true,
|
||||||
createProjectWithEnvironmentConfig: true,
|
|
||||||
manyStrategiesPagination: true,
|
manyStrategiesPagination: true,
|
||||||
enableLegacyVariants: false,
|
enableLegacyVariants: false,
|
||||||
commandBarUI: true,
|
commandBarUI: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user