1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

UI/bulk archive (#3319)

Ability to archive multiple feature toggles from project overview
This commit is contained in:
Tymoteusz Czech 2023-03-15 14:10:16 +01:00 committed by GitHub
parent a5f1b89b4a
commit 0784afd255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 29 deletions

View File

@ -3,13 +3,14 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
interface IFeatureArchiveDialogProps {
isOpen: boolean;
onConfirm: () => void;
onClose: () => void;
projectId: string;
featureId: string;
featureIds: string[];
}
export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
@ -17,14 +18,15 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
onClose,
onConfirm,
projectId,
featureId,
featureIds,
}) => {
const { archiveFeatureToggle } = useFeatureApi();
const { setToastData, setToastApiError } = useToast();
const isBulkArchive = featureIds?.length > 1;
const archiveToggle = async () => {
try {
await archiveFeatureToggle(projectId, featureId);
await archiveFeatureToggle(projectId, featureIds[0]);
setToastData({
text: 'Your feature toggle has been archived',
type: 'success',
@ -38,16 +40,69 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
}
};
const archiveToggles = async () => {
try {
// TODO: bulk archive
await Promise.allSettled(
featureIds.map(id => {
archiveFeatureToggle(projectId, id);
})
);
setToastData({
text: 'Selected feature toggles have been archived',
type: 'success',
title: 'Feature toggles archived',
});
onConfirm();
onClose();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
onClose();
}
};
return (
<Dialogue
onClick={() => archiveToggle()}
onClick={isBulkArchive ? archiveToggles : archiveToggle}
open={isOpen}
onClose={onClose}
primaryButtonText="Archive toggle"
primaryButtonText={
isBulkArchive ? 'Archive toggles' : 'Archive toggle'
}
secondaryButtonText="Cancel"
title="Archive feature toggle"
title={
isBulkArchive
? 'Archive feature toggles'
: 'Archive feature toggle'
}
>
Are you sure you want to archive this feature toggle?
<ConditionallyRender
condition={isBulkArchive}
show={
<>
<p>
Are you sure you want to archive{' '}
<strong>{featureIds?.length}</strong> feature
toggles?
</p>
<ConditionallyRender
condition={featureIds?.length <= 5}
show={
<ul>
{featureIds?.map(id => (
<li key={id}>{id}</li>
))}
</ul>
}
/>
</>
}
elseShow={
<p>
Are you sure you want to archive these feature toggles?
</p>
}
/>
</Dialogue>
);
};

View File

@ -248,7 +248,7 @@ export const FeatureView = () => {
}}
onClose={() => setShowDelDialog(false)}
projectId={projectId}
featureId={featureId}
featureIds={[featureId]}
/>
<FeatureStaleDialog
isStale={feature.stale}

View File

@ -685,7 +685,7 @@ export const ProjectFeatureToggles = ({
onClose={() => {
setFeatureArchiveState(undefined);
}}
featureId={featureArchiveState || ''}
featureIds={[featureArchiveState || '']}
projectId={projectId}
/>{' '}
<ChangeRequestDialogue
@ -717,6 +717,7 @@ export const ProjectFeatureToggles = ({
<SelectionActionsBar
selectedIds={Object.keys(selectedRowIds)}
data={features}
projectId={projectId}
/>
</PageContent>
);

View File

@ -0,0 +1,50 @@
import { useState, VFC } from 'react';
import { Button } from '@mui/material';
import { Archive } from '@mui/icons-material';
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
import { DELETE_FEATURE } from 'component/providers/AccessProvider/permissions';
import useProject from 'hooks/api/getters/useProject/useProject';
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
interface IArchiveButtonProps {
projectId: string;
features: string[];
}
export const ArchiveButton: VFC<IArchiveButtonProps> = ({
projectId,
features,
}) => {
const { refetch } = useProject(projectId);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const onConfirm = async () => {
setIsDialogOpen(false);
await refetch();
};
return (
<>
<PermissionHOC projectId={projectId} permission={DELETE_FEATURE}>
{({ hasAccess }) => (
<Button
disabled={!hasAccess || isDialogOpen}
startIcon={<Archive />}
variant="outlined"
size="small"
onClick={() => setIsDialogOpen(true)}
>
Archive
</Button>
)}
</PermissionHOC>
<FeatureArchiveDialog
projectId={projectId}
featureIds={features}
onConfirm={onConfirm}
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
/>
</>
);
};

View File

@ -0,0 +1,54 @@
import { VFC } from 'react';
import { Button } from '@mui/material';
import { WatchLater } from '@mui/icons-material';
import type { FeatureSchema } from 'openapi';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
interface IMarkAsStaleButtonsProps {
projectId: string;
data: FeatureSchema[];
}
export const MarkAsStaleButtons: VFC<IMarkAsStaleButtonsProps> = ({
projectId,
data,
}) => {
const hasStale = data.some(d => d.stale);
const hasUnstale = data.some(d => !d.stale);
return (
<PermissionHOC projectId={projectId} permission={UPDATE_FEATURE}>
{({ hasAccess }) => (
<>
<ConditionallyRender
condition={hasUnstale || !hasAccess}
show={
<Button
startIcon={<WatchLater />}
variant="outlined"
size="small"
disabled={!hasAccess}
>
Mark as stale
</Button>
}
/>
<ConditionallyRender
condition={Boolean(hasAccess && hasStale)}
show={
<Button
startIcon={<WatchLater />}
variant="outlined"
size="small"
>
Un-mark as stale
</Button>
}
/>
</>
)}
</PermissionHOC>
);
};

View File

@ -1,26 +1,30 @@
import { useMemo, useState, VFC } from 'react';
import { Box, Button, Paper, styled, Typography } from '@mui/material';
import { Archive, FileDownload, Label, WatchLater } from '@mui/icons-material';
import { FileDownload, Label } from '@mui/icons-material';
import type { FeatureSchema } from 'openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ArchiveButton } from './ArchiveButton/ArchiveButton';
import { MarkAsStaleButtons } from './MarkAsStaleButtons/MarkAsStaleButtons';
interface ISelectionActionsBarProps {
selectedIds: string[];
data: FeatureSchema[];
projectId: string;
}
const StyledContainer = styled(Box)(() => ({
display: 'flex',
justifyContent: 'center',
width: '100%',
flexWrap: 'wrap',
}));
const StyledBar = styled(Paper)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
justifyContent: 'flex-end',
marginTop: theme.spacing(2),
marginLeft: 'auto',
marginRight: 'auto',
@ -28,7 +32,8 @@ const StyledBar = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: theme.shape.borderRadiusLarge,
columnGap: theme.spacing(1),
gap: theme.spacing(1),
flexWrap: 'wrap',
}));
const StyledCount = styled('span')(({ theme }) => ({
@ -39,12 +44,14 @@ const StyledCount = styled('span')(({ theme }) => ({
}));
const StyledText = styled(Typography)(({ theme }) => ({
marginRight: theme.spacing(2),
paddingRight: theme.spacing(2),
marginRight: 'auto',
}));
export const SelectionActionsBar: VFC<ISelectionActionsBarProps> = ({
selectedIds,
data,
projectId,
}) => {
const { uiConfig } = useUiConfig();
const [showExportDialog, setShowExportDialog] = useState(false);
@ -71,22 +78,8 @@ export const SelectionActionsBar: VFC<ISelectionActionsBarProps> = ({
<StyledCount>{selectedIds.length}</StyledCount>
&ensp;selected
</StyledText>
<Button
disabled
startIcon={<Archive />}
variant="outlined"
size="small"
>
Archive
</Button>
<Button
disabled
startIcon={<WatchLater />}
variant="outlined"
size="small"
>
Mark as stale
</Button>
<ArchiveButton projectId={projectId} features={selectedIds} />
<MarkAsStaleButtons projectId={projectId} data={selectedData} />
<Button
startIcon={<FileDownload />}
variant="outlined"