1
0
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:
Thomas Heartman 2024-06-24 12:53:55 +02:00 committed by GitHub
parent 5b4ff92454
commit 0af5bbad38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 23 additions and 222 deletions

View File

@ -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();
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 />; 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;

View File

@ -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');
const CreateButton: React.FC<{ onClick: () => void }> = ({ onClick }) => {
return ( 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>
);
};
if (useNewProjectForm) {
return (
<>
<CreateButton onClick={() => setOpenCreateDialog(true)} />
<CreateProjectDialog <CreateProjectDialog
open={openCreateDialog} open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)} onClose={() => setOpenCreateDialog(false)}
/> />
</> </>
); );
} else {
return <CreateButton onClick={() => navigate('/projects/create')} />;
}
}; };
export const ProjectListNew = () => { export const ProjectListNew = () => {

View File

@ -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;

View File

@ -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,

View File

@ -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();
},
);
}); });
}); });

View File

@ -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,9 +354,7 @@ 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?.length
? newProject.environments ? newProject.environments
: ( : (
await this.environmentStore.getAll({ await this.environmentStore.getAll({
@ -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),

View File

@ -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,

View File

@ -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,