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:
parent
8caa1e242c
commit
a01305040d
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -14,6 +14,8 @@ import { DeleteProjectForm } from './DeleteProjectForm';
|
||||
import useProjectOverview, {
|
||||
featuresCount,
|
||||
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { ArchiveProjectForm } from './ArchiveProjectForm';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const StyledFormContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -26,6 +28,7 @@ const EditProject = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const id = useRequiredPathParam('projectId');
|
||||
const { project } = useProjectOverview(id);
|
||||
const archiveProjectsEnabled = useUiFlag('archiveProjects');
|
||||
|
||||
if (!project.name) {
|
||||
return null;
|
||||
@ -49,7 +52,19 @@ const EditProject = () => {
|
||||
condition={isEnterprise()}
|
||||
show={<UpdateEnterpriseSettings project={project} />}
|
||||
/>
|
||||
<DeleteProjectForm featureCount={featuresCount(project)} />
|
||||
<ConditionallyRender
|
||||
condition={archiveProjectsEnabled}
|
||||
show={
|
||||
<ArchiveProjectForm
|
||||
featureCount={featuresCount(project)}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<DeleteProjectForm
|
||||
featureCount={featuresCount(project)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
</>
|
||||
);
|
||||
|
@ -80,6 +80,15 @@ const useProjectApi = () => {
|
||||
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 (
|
||||
projectId: string,
|
||||
environment: string,
|
||||
@ -253,6 +262,7 @@ const useProjectApi = () => {
|
||||
editProject,
|
||||
editProjectSettings,
|
||||
deleteProject,
|
||||
archiveProject,
|
||||
addEnvironmentToProject,
|
||||
removeEnvironmentFromProject,
|
||||
addAccessToProject,
|
||||
|
Loading…
Reference in New Issue
Block a user