1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-04 00:18:40 +01:00

feat: archive project form (#7797)

This commit is contained in:
Mateusz Kwasniewski 2024-08-08 09:29:28 +02:00 committed by GitHub
parent 8caa1e242c
commit a01305040d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 233 additions and 1 deletions

View File

@ -0,0 +1,56 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import type React from 'react';
import { formatUnknownError } from 'utils/formatUnknownError';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import useToast from 'hooks/useToast';
import { Typography } from '@mui/material';
interface IDeleteProjectDialogueProps {
project: string;
open: boolean;
onClose: (e: React.SyntheticEvent) => void;
onSuccess?: () => void;
}
export const ArchiveProjectDialogue = ({
open,
onClose,
project,
onSuccess,
}: IDeleteProjectDialogueProps) => {
const { archiveProject } = useProjectApi();
const { refetch: refetchProjectOverview } = useProjects();
const { setToastData, setToastApiError } = useToast();
const onClick = async (e: React.SyntheticEvent) => {
e.preventDefault();
try {
await archiveProject(project);
refetchProjectOverview();
setToastData({
title: 'Archived project',
type: 'success',
text: 'Successfully archived project',
});
onSuccess?.();
} catch (ex: unknown) {
setToastApiError(formatUnknownError(ex));
}
onClose(e);
};
return (
<Dialogue
open={open}
onClick={onClick}
onClose={onClose}
title='Really archive project'
>
<Typography>
This will archive the project and all feature flags archived in
it.
</Typography>
</Dialogue>
);
};

View File

@ -0,0 +1,108 @@
import { styled } from '@mui/material';
import { DELETE_PROJECT } from 'component/providers/AccessProvider/permissions';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { useState } from 'react';
import { useNavigate } from 'react-router';
import { useUiFlag } from 'hooks/useUiFlag';
import { useActions } from 'hooks/api/getters/useActions/useActions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ArchiveProjectDialogue } from '../../ArchiveProject/ArchiveProjectDialogue';
const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
borderTop: `1px solid ${theme.palette.divider}`,
paddingTop: theme.spacing(4),
gap: theme.spacing(2),
}));
const StyledButtonContainer = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
paddingTop: theme.spacing(3),
}));
interface IDeleteProjectProps {
projectId: string;
featureCount: number;
}
export const ArchiveProject = ({
projectId,
featureCount,
}: IDeleteProjectProps) => {
const { isEnterprise } = useUiConfig();
const automatedActionsEnabled = useUiFlag('automatedActions');
const archiveProjectsEnabled = useUiFlag('archiveProjects');
const { actions } = useActions(projectId);
const [showArchiveDialog, setShowArchiveDialog] = useState(false);
const actionsCount = actions.filter(({ enabled }) => enabled).length;
const navigate = useNavigate();
return (
<StyledContainer>
<p>
Before you can archive a project, you must first archive all the
feature flags associated with it
{isEnterprise() && automatedActionsEnabled
? ' and disable all actions that are in it'
: ''}
.
</p>
<ConditionallyRender
condition={featureCount > 0}
show={
<p>
Currently there {featureCount <= 1 ? 'is' : 'are'}{' '}
<strong>
{featureCount} active feature{' '}
{featureCount === 1 ? 'flag' : 'flags'}.
</strong>
</p>
}
/>
<ConditionallyRender
condition={
isEnterprise() &&
automatedActionsEnabled &&
actionsCount > 0
}
show={
<p>
Currently there {actionsCount <= 1 ? 'is' : 'are'}{' '}
<strong>
{actionsCount} enabled{' '}
{actionsCount === 1 ? 'action' : 'actions'}.
</strong>
</p>
}
/>
<StyledButtonContainer>
<PermissionButton
permission={DELETE_PROJECT}
disabled={featureCount > 0}
projectId={projectId}
onClick={() => {
setShowArchiveDialog(true);
}}
tooltipProps={{
title: 'Archive project',
}}
data-loading
>
Archive project
</PermissionButton>
</StyledButtonContainer>
<ArchiveProjectDialogue
project={projectId}
open={showArchiveDialog}
onClose={() => {
setShowArchiveDialog(false);
}}
onSuccess={() => {
navigate('/projects');
}}
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,43 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { styled } from '@mui/material';
import { ArchiveProject } from '../ArchiveProject';
const StyledContainer = styled('div')(({ theme }) => ({
borderRadius: theme.spacing(2),
overflow: 'hidden',
}));
interface IDeleteProjectForm {
featureCount: number;
}
export const ArchiveProjectForm = ({ featureCount }: IDeleteProjectForm) => {
const id = useRequiredPathParam('projectId');
const { uiConfig } = useUiConfig();
const { loading } = useProjectApi();
const formatProjectArchiveApiCode = () => {
return `curl --location --request DELETE '${uiConfig.unleashUrl}/api/admin/projects/${id}/archive' \\
--header 'Authorization: INSERT_API_KEY' '`;
};
return (
<StyledContainer>
<FormTemplate
loading={loading}
title='Archive project'
description=''
documentationLink='https://docs.getunleash.io/reference/projects'
documentationLinkLabel='Projects documentation'
formatApiCode={formatProjectArchiveApiCode}
compact
compactPadding
showDescription={false}
showLink={false}
>
<ArchiveProject projectId={id} featureCount={featureCount} />
</FormTemplate>
</StyledContainer>
);
};

View File

@ -14,6 +14,8 @@ import { DeleteProjectForm } from './DeleteProjectForm';
import useProjectOverview, { import useProjectOverview, {
featuresCount, featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview'; } from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ArchiveProjectForm } from './ArchiveProjectForm';
import { useUiFlag } from 'hooks/useUiFlag';
const StyledFormContainer = styled('div')(({ theme }) => ({ const StyledFormContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
@ -26,6 +28,7 @@ const EditProject = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const id = useRequiredPathParam('projectId'); const id = useRequiredPathParam('projectId');
const { project } = useProjectOverview(id); const { project } = useProjectOverview(id);
const archiveProjectsEnabled = useUiFlag('archiveProjects');
if (!project.name) { if (!project.name) {
return null; return null;
@ -49,7 +52,19 @@ const EditProject = () => {
condition={isEnterprise()} condition={isEnterprise()}
show={<UpdateEnterpriseSettings project={project} />} show={<UpdateEnterpriseSettings project={project} />}
/> />
<DeleteProjectForm featureCount={featuresCount(project)} /> <ConditionallyRender
condition={archiveProjectsEnabled}
show={
<ArchiveProjectForm
featureCount={featuresCount(project)}
/>
}
elseShow={
<DeleteProjectForm
featureCount={featuresCount(project)}
/>
}
/>
</StyledFormContainer> </StyledFormContainer>
</> </>
); );

View File

@ -80,6 +80,15 @@ const useProjectApi = () => {
return res; return res;
}; };
const archiveProject = async (projectId: string) => {
const path = `api/admin/projects/${projectId}/archive`;
const req = createRequest(path, { method: 'POST' });
const res = await makeRequest(req.caller, req.id);
return res;
};
const addEnvironmentToProject = async ( const addEnvironmentToProject = async (
projectId: string, projectId: string,
environment: string, environment: string,
@ -253,6 +262,7 @@ const useProjectApi = () => {
editProject, editProject,
editProjectSettings, editProjectSettings,
deleteProject, deleteProject,
archiveProject,
addEnvironmentToProject, addEnvironmentToProject,
removeEnvironmentFromProject, removeEnvironmentFromProject,
addAccessToProject, addAccessToProject,