Skip to content

Commit

Permalink
UI/bulk stale (#3320)
Browse files Browse the repository at this point in the history
Mark and un-mark selected toggles as stale.
  • Loading branch information
Tymek committed Mar 16, 2023
1 parent b4c4a23 commit a983cf1
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 66 deletions.
Expand Up @@ -4,6 +4,7 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';

interface IFeatureArchiveDialogProps {
isOpen: boolean;
Expand All @@ -21,6 +22,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
featureIds,
}) => {
const { archiveFeatureToggle } = useFeatureApi();
const { archiveFeatures } = useProjectApi();
const { setToastData, setToastApiError } = useToast();
const isBulkArchive = featureIds?.length > 1;

Expand All @@ -42,12 +44,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({

const archiveToggles = async () => {
try {
// TODO: bulk archive
await Promise.allSettled(
featureIds.map(id => {
archiveFeatureToggle(projectId, id);
})
);
await archiveFeatures(projectId, featureIds);
setToastData({
text: 'Selected feature toggles have been archived',
type: 'success',
Expand Down
Expand Up @@ -87,7 +87,7 @@ export const ActionsCell: VFC<IActionsCellProps> = ({
disableScrollLock={true}
PaperProps={{
sx: theme => ({
borderRadius: theme.shape.borderRadius,
borderRadius: `${theme.shape.borderRadius}px`,
padding: theme.spacing(1, 1.5),
}),
}}
Expand Down
Expand Up @@ -21,6 +21,7 @@ export const ArchiveButton: VFC<IArchiveButtonProps> = ({
const onConfirm = async () => {
setIsDialogOpen(false);
await refetch();
// TODO: toast
};

return (
Expand Down

This file was deleted.

@@ -0,0 +1,165 @@
import { useState, VFC } from 'react';
import {
IconButton,
ListItemIcon,
ListItemText,
MenuItem,
MenuList,
Popover,
Tooltip,
Typography,
} from '@mui/material';
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { MoreVert, WatchLater } from '@mui/icons-material';
import type { FeatureSchema } from 'openapi';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useProject from 'hooks/api/getters/useProject/useProject';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';

interface IMoreActionsProps {
projectId: string;
data: FeatureSchema[];
}

const menuId = 'selection-actions-menu';

export const MoreActions: VFC<IMoreActionsProps> = ({ projectId, data }) => {
const { refetch } = useProject(projectId);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const { staleFeatures } = useProjectApi();
const { setToastData, setToastApiError } = useToast();

const open = Boolean(anchorEl);
const selectedIds = data.map(({ name }) => name);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const hasStale = data.some(({ stale }) => stale === true);
const hasUnstale = data.some(({ stale }) => stale === false);

const onMarkAsStale = async () => {
try {
handleClose();
await staleFeatures(projectId, selectedIds);
await refetch();
setToastData({
title: 'State updated',
text: 'Feature toggles marked as stale',
type: 'success',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};

const onUnmarkAsStale = async () => {
try {
handleClose();
await staleFeatures(projectId, selectedIds, false);
await refetch();
setToastData({
title: 'State updated',
text: 'Feature toggles unmarked as stale',
type: 'success',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};

return (
<>
<Tooltip title="Feature toggle actions" arrow describeChild>
<IconButton
id={menuId}
aria-controls={open ? menuId : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
type="button"
>
<MoreVert />
</IconButton>
</Tooltip>
<Popover
id={`${menuId}-menu`}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
disableScrollLock={true}
PaperProps={{
sx: theme => ({
borderRadius: `${theme.shape.borderRadius}px`,
padding: theme.spacing(1, 1.5),
}),
}}
>
<MenuList aria-labelledby={`${menuId}-menu`}>
<PermissionHOC
projectId={projectId}
permission={UPDATE_FEATURE}
>
{({ hasAccess }) => (
<>
<ConditionallyRender
condition={hasUnstale}
show={() => (
<MenuItem
onClick={onMarkAsStale}
disabled={!hasAccess}
sx={{
borderRadius: theme =>
`${theme.shape.borderRadius}px`,
}}
>
<ListItemIcon>
<WatchLater />
</ListItemIcon>
<ListItemText>
<Typography variant="body2">
Mark as stale
</Typography>
</ListItemText>
</MenuItem>
)}
/>
<ConditionallyRender
condition={hasStale}
show={() => (
<MenuItem
onClick={onUnmarkAsStale}
disabled={!hasAccess}
sx={{
borderRadius: theme =>
`${theme.shape.borderRadius}px`,
}}
>
<ListItemIcon>
<WatchLater />
</ListItemIcon>
<ListItemText>
<Typography variant="body2">
Un-mark as stale
</Typography>
</ListItemText>
</MenuItem>
)}
/>
</>
)}
</PermissionHOC>
</MenuList>
</Popover>
</>
);
};
@@ -1,12 +1,12 @@
import { useMemo, useState, VFC } from 'react';
import { Box, Button, Paper, styled, Typography } from '@mui/material';
import { FileDownload, Label } from '@mui/icons-material';
import { FileDownload, Label, WatchLater } 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';
import { MoreActions } from './MoreActions/MoreActions';

interface ISelectionActionsBarProps {
selectedIds: string[];
Expand Down Expand Up @@ -79,7 +79,6 @@ export const SelectionActionsBar: VFC<ISelectionActionsBarProps> = ({
&ensp;selected
</StyledText>
<ArchiveButton projectId={projectId} features={selectedIds} />
<MarkAsStaleButtons projectId={projectId} data={selectedData} />
<Button
startIcon={<FileDownload />}
variant="outlined"
Expand All @@ -96,6 +95,7 @@ export const SelectionActionsBar: VFC<ISelectionActionsBarProps> = ({
>
Tags
</Button>
<MoreActions projectId={projectId} data={selectedData} />
</StyledBar>
<ConditionallyRender
condition={Boolean(uiConfig?.flags?.featuresExportImport)}
Expand Down
36 changes: 34 additions & 2 deletions frontend/src/hooks/api/actions/useProjectApi/useProjectApi.ts
@@ -1,3 +1,4 @@
import type { BatchStaleSchema } from 'openapi';
import useAPI from '../useApi/useApi';

interface ICreatePayload {
Expand Down Expand Up @@ -215,6 +216,35 @@ const useProjectApi = () => {
return makeRequest(req.caller, req.id);
};

const archiveFeatures = async (projectId: string, featureIds: string[]) => {
const path = `api/admin/projects/${projectId}/archive`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify({ features: featureIds }),
});

return makeRequest(req.caller, req.id);
};

const staleFeatures = async (
projectId: string,
featureIds: string[],
stale = true
) => {
const payload: BatchStaleSchema = {
features: featureIds,
stale,
};

const path = `api/admin/projects/${projectId}/stale`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify(payload),
});

return makeRequest(req.caller, req.id);
};

return {
createProject,
validateId,
Expand All @@ -227,10 +257,12 @@ const useProjectApi = () => {
removeGroupFromRole,
changeUserRole,
changeGroupRole,
errors,
loading,
archiveFeatures,
staleFeatures,
searchProjectUser,
setDefaultProjectStickiness,
errors,
loading,
};
};

Expand Down

0 comments on commit a983cf1

Please sign in to comment.