mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-27 00:19:39 +01:00
update project archive and revive dialogs (#7918)
This commit is contained in:
parent
cf3379d0b3
commit
004038e872
@ -1,5 +1,5 @@
|
||||
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
|
||||
import type { VFC } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import type { SortingRule } from 'react-table';
|
||||
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||
import { ArchiveTable } from './ArchiveTable/ArchiveTable';
|
||||
@ -10,7 +10,7 @@ interface IProjectFeaturesTable {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const ProjectFeaturesArchiveTable: VFC<IProjectFeaturesTable> = ({
|
||||
export const ProjectFeaturesArchiveTable: FC<IProjectFeaturesTable> = ({
|
||||
projectId,
|
||||
}) => {
|
||||
const { archivedFeatures, loading, refetchArchived } =
|
||||
@ -23,7 +23,7 @@ export const ProjectFeaturesArchiveTable: VFC<IProjectFeaturesTable> = ({
|
||||
|
||||
return (
|
||||
<ArchiveTable
|
||||
title='Project archive'
|
||||
title='Archived flags'
|
||||
archivedFeatures={archivedFeatures || []}
|
||||
loading={loading}
|
||||
storedParams={value}
|
||||
|
@ -62,11 +62,13 @@ export const ProjectArchiveCard: FC<ProjectArchiveCardProps> = ({
|
||||
<ProjectIcon color='action' />
|
||||
</StyledIconBox>
|
||||
<StyledBox data-loading>
|
||||
<StyledCardTitle>
|
||||
<Highlighter search={searchQuery}>
|
||||
{name}
|
||||
</Highlighter>
|
||||
</StyledCardTitle>
|
||||
<Tooltip title={`id: ${id}`} arrow>
|
||||
<StyledCardTitle>
|
||||
<Highlighter search={searchQuery}>
|
||||
{name}
|
||||
</Highlighter>
|
||||
</StyledCardTitle>
|
||||
</Tooltip>
|
||||
</StyledBox>
|
||||
<ProjectModeBadge mode={mode} />
|
||||
</StyledDivHeader>
|
||||
@ -114,7 +116,7 @@ export const ProjectArchiveCard: FC<ProjectArchiveCardProps> = ({
|
||||
onClick={onRevive}
|
||||
projectId={id}
|
||||
permission={UPDATE_PROJECT}
|
||||
tooltipProps={{ title: 'Restore project' }}
|
||||
tooltipProps={{ title: 'Revive project' }}
|
||||
data-testid={`revive-feature-flag-button`}
|
||||
>
|
||||
<Undo />
|
||||
|
@ -45,11 +45,11 @@ export const ArchiveProjectDialogue = ({
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
onClose={onClose}
|
||||
title='Really archive project'
|
||||
title='Are you sure?'
|
||||
>
|
||||
<Typography>
|
||||
This will archive the project and all feature flags archived in
|
||||
it.
|
||||
The project will be moved to the projects archive, where it can
|
||||
either be revived or permanently deleted.
|
||||
</Typography>
|
||||
</Dialogue>
|
||||
);
|
||||
|
@ -7,19 +7,26 @@ import useToast from 'hooks/useToast';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { Typography } from '@mui/material';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { ProjectId } from 'component/project/ProjectId/ProjectId';
|
||||
|
||||
interface IDeleteProjectDialogueProps {
|
||||
project: string;
|
||||
projectId: string;
|
||||
projectName?: string;
|
||||
open: boolean;
|
||||
onClose: (e: React.SyntheticEvent) => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
const StyledParagraph = styled(Typography)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const DeleteProjectDialogue = ({
|
||||
open,
|
||||
onClose,
|
||||
project,
|
||||
projectId,
|
||||
projectName,
|
||||
onSuccess,
|
||||
}: IDeleteProjectDialogueProps) => {
|
||||
const { deleteProject } = useProjectApi();
|
||||
@ -32,7 +39,7 @@ export const DeleteProjectDialogue = ({
|
||||
const onClick = async (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await deleteProject(project);
|
||||
await deleteProject(projectId);
|
||||
refetchProjects();
|
||||
refetchProjectArchive();
|
||||
setToastData({
|
||||
@ -52,17 +59,34 @@ export const DeleteProjectDialogue = ({
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
onClose={onClose}
|
||||
title='Really delete project'
|
||||
title='Are you sure?'
|
||||
>
|
||||
<Typography>
|
||||
This will irreversibly remove the project, all feature flags
|
||||
archived in it, all API keys scoped to only this project
|
||||
<ConditionallyRender
|
||||
condition={isEnterprise() && automatedActionsEnabled}
|
||||
show=', and all actions configured for it'
|
||||
/>
|
||||
.
|
||||
</Typography>
|
||||
<StyledParagraph>
|
||||
This will irreversibly remove:
|
||||
<ul>
|
||||
<li>project with all of its settings</li>
|
||||
<li>all feature flags archived in it</li>
|
||||
<li>all API keys scoped to only to this project</li>
|
||||
<ConditionallyRender
|
||||
condition={isEnterprise() && automatedActionsEnabled}
|
||||
show={<li>all actions configured for it</li>}
|
||||
/>
|
||||
</ul>
|
||||
</StyledParagraph>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(projectName)}
|
||||
show={
|
||||
<>
|
||||
<StyledParagraph>
|
||||
Are you sure you'd like to permanently delete
|
||||
project <strong>{projectName}</strong>?
|
||||
</StyledParagraph>
|
||||
<StyledParagraph>
|
||||
Project ID: <ProjectId>{projectId}</ProjectId>
|
||||
</StyledParagraph>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
|
@ -103,7 +103,7 @@ export const Project = () => {
|
||||
name: 'health',
|
||||
},
|
||||
{
|
||||
title: 'Archive',
|
||||
title: 'Archived flags',
|
||||
path: `${basePath}/archive`,
|
||||
name: 'archive',
|
||||
},
|
||||
@ -285,7 +285,7 @@ export const Project = () => {
|
||||
</StyledTabContainer>
|
||||
</StyledHeader>
|
||||
<DeleteProjectDialogue
|
||||
project={projectId}
|
||||
projectId={projectId}
|
||||
open={showDelDialog}
|
||||
onClose={() => {
|
||||
setShowDelDialog(false);
|
||||
|
@ -6,7 +6,7 @@ import { useProjectOverviewNameOrId } from 'hooks/api/getters/useProjectOverview
|
||||
export const ProjectFeaturesArchive = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const projectName = useProjectOverviewNameOrId(projectId);
|
||||
usePageTitle(`Project archive – ${projectName}`);
|
||||
usePageTitle(`Project archived flags – ${projectName}`);
|
||||
|
||||
return <ProjectFeaturesArchiveTable projectId={projectId} />;
|
||||
};
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { Link, 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 { Link as RouterLink } from 'react-router-dom';
|
||||
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 }) => ({
|
||||
@ -32,54 +30,34 @@ export const ArchiveProject = ({
|
||||
projectId,
|
||||
featureCount,
|
||||
}: IDeleteProjectProps) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const automatedActionsEnabled = useUiFlag('automatedActions');
|
||||
const { actions } = useActions(projectId);
|
||||
const [showArchiveDialog, setShowArchiveDialog] = useState(false);
|
||||
const actionsCount = actions.filter(({ enabled }) => enabled).length;
|
||||
const navigate = useNavigate();
|
||||
const disabled = featureCount > 0;
|
||||
|
||||
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'
|
||||
: ''}
|
||||
.
|
||||
Before you can archive a project, you must first archive all of
|
||||
the feature flags associated with 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>
|
||||
<Link component={RouterLink} to='../..'>
|
||||
<strong>
|
||||
{featureCount} active feature{' '}
|
||||
{featureCount === 1 ? 'flag' : 'flags'}.
|
||||
</strong>
|
||||
</Link>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<StyledButtonContainer>
|
||||
<PermissionButton
|
||||
permission={DELETE_PROJECT}
|
||||
disabled={featureCount > 0}
|
||||
disabled={disabled}
|
||||
projectId={projectId}
|
||||
onClick={() => {
|
||||
setShowArchiveDialog(true);
|
||||
|
@ -25,11 +25,13 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
interface IDeleteProjectProps {
|
||||
projectId: string;
|
||||
projectName?: string;
|
||||
featureCount: number;
|
||||
}
|
||||
|
||||
export const DeleteProject = ({
|
||||
projectId,
|
||||
projectName,
|
||||
featureCount,
|
||||
}: IDeleteProjectProps) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
@ -106,7 +108,8 @@ export const DeleteProject = ({
|
||||
</PermissionButton>
|
||||
</StyledButtonContainer>
|
||||
<DeleteProjectDialogue
|
||||
project={projectId}
|
||||
projectId={projectId}
|
||||
projectName={projectName}
|
||||
open={showDelDialog}
|
||||
onClose={() => {
|
||||
setShowDelDialog(false);
|
||||
|
9
frontend/src/component/project/ProjectId/ProjectId.tsx
Normal file
9
frontend/src/component/project/ProjectId/ProjectId.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
export const ProjectId = styled('code')(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.elevation2,
|
||||
padding: theme.spacing(0.5, 1.5),
|
||||
display: 'inline-block',
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
}));
|
@ -49,6 +49,7 @@ export const ArchiveProjectList: FC = () => {
|
||||
const [deleteProject, setDeleteProject] = useState<{
|
||||
isOpen: boolean;
|
||||
id?: string;
|
||||
name?: string;
|
||||
}>({ isOpen: false });
|
||||
|
||||
useEffect(() => {
|
||||
@ -76,6 +77,7 @@ export const ArchiveProjectList: FC = () => {
|
||||
onDelete={() =>
|
||||
setDeleteProject({
|
||||
id,
|
||||
name: projects?.find((project) => project.id === id)?.name,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
@ -155,7 +157,8 @@ export const ArchiveProjectList: FC = () => {
|
||||
}
|
||||
/>
|
||||
<DeleteProjectDialogue
|
||||
project={deleteProject.id || ''}
|
||||
projectId={deleteProject.id || ''}
|
||||
projectName={deleteProject.name || ''}
|
||||
open={deleteProject.isOpen}
|
||||
onClose={() => {
|
||||
setDeleteProject((state) => ({ ...state, isOpen: false }));
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { ProjectId } from 'component/project/ProjectId/ProjectId';
|
||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
import useToast from 'hooks/useToast';
|
||||
@ -11,6 +13,10 @@ type ReviveProjectDialogProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const StyledParagraph = styled(Typography)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const ReviveProjectDialog = ({
|
||||
name,
|
||||
id,
|
||||
@ -30,9 +36,9 @@ export const ReviveProjectDialog = ({
|
||||
refetchProjects();
|
||||
refetchProjectArchive();
|
||||
setToastData({
|
||||
title: 'Restored project',
|
||||
title: 'Revive project',
|
||||
type: 'success',
|
||||
text: 'Successfully restored project',
|
||||
text: 'Successfully revived project',
|
||||
});
|
||||
} catch (ex: unknown) {
|
||||
setToastApiError(formatUnknownError(ex));
|
||||
@ -43,14 +49,20 @@ export const ReviveProjectDialog = ({
|
||||
return (
|
||||
<Dialogue
|
||||
open={open}
|
||||
secondaryButtonText='Close'
|
||||
onClose={onClose}
|
||||
onClick={onClick}
|
||||
title='Restore archived project'
|
||||
title='Revive an archived project'
|
||||
>
|
||||
Are you sure you'd like to restore project <strong>{name}</strong>{' '}
|
||||
(id: <code>{id}</code>)?
|
||||
{/* TODO: more explanation */}
|
||||
<StyledParagraph>
|
||||
Are you sure you'd like to revive project{' '}
|
||||
<strong>{name}</strong>?
|
||||
</StyledParagraph>
|
||||
<StyledParagraph>
|
||||
Project ID: <ProjectId>{id}</ProjectId>
|
||||
</StyledParagraph>
|
||||
<StyledParagraph>
|
||||
All flags in the revived project will remain archived.
|
||||
</StyledParagraph>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user