Skip to content

Commit

Permalink
UI/bulk archive (#3319)
Browse files Browse the repository at this point in the history
Ability to archive multiple feature toggles from project overview
  • Loading branch information
Tymek committed Mar 15, 2023
1 parent a5f1b89 commit 0784afd
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 29 deletions.
Expand Up @@ -3,28 +3,30 @@ 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> = ({
isOpen,
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',
Expand All @@ -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>
);
};
2 changes: 1 addition & 1 deletion frontend/src/component/feature/FeatureView/FeatureView.tsx
Expand Up @@ -248,7 +248,7 @@ export const FeatureView = () => {
}}
onClose={() => setShowDelDialog(false)}
projectId={projectId}
featureId={featureId}
featureIds={[featureId]}
/>
<FeatureStaleDialog
isStale={feature.stale}
Expand Down
Expand Up @@ -685,7 +685,7 @@ export const ProjectFeatureToggles = ({
onClose={() => {
setFeatureArchiveState(undefined);
}}
featureId={featureArchiveState || ''}
featureIds={[featureArchiveState || '']}
projectId={projectId}
/>{' '}
<ChangeRequestDialogue
Expand Down Expand Up @@ -717,6 +717,7 @@ export const ProjectFeatureToggles = ({
<SelectionActionsBar
selectedIds={Object.keys(selectedRowIds)}
data={features}
projectId={projectId}
/>
</PageContent>
);
Expand Down
@@ -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)}
/>
</>
);
};
@@ -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>
);
};
@@ -1,34 +1,39 @@
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',
padding: theme.spacing(2, 3),
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 }) => ({
Expand All @@ -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);
Expand All @@ -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"
Expand Down

0 comments on commit 0784afd

Please sign in to comment.