diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 325f529251e..ec4daf26ee5 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -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; @@ -21,6 +22,7 @@ export const FeatureArchiveDialog: VFC = ({ featureIds, }) => { const { archiveFeatureToggle } = useFeatureApi(); + const { archiveFeatures } = useProjectApi(); const { setToastData, setToastApiError } = useToast(); const isBulkArchive = featureIds?.length > 1; @@ -42,12 +44,7 @@ export const FeatureArchiveDialog: VFC = ({ 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', diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ActionsCell/ActionsCell.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ActionsCell/ActionsCell.tsx index 2631c9286f4..23cccf5da60 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ActionsCell/ActionsCell.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ActionsCell/ActionsCell.tsx @@ -87,7 +87,7 @@ export const ActionsCell: VFC = ({ disableScrollLock={true} PaperProps={{ sx: theme => ({ - borderRadius: theme.shape.borderRadius, + borderRadius: `${theme.shape.borderRadius}px`, padding: theme.spacing(1, 1.5), }), }} diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx index e313e084be0..742c8c366f1 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx @@ -21,6 +21,7 @@ export const ArchiveButton: VFC = ({ const onConfirm = async () => { setIsDialogOpen(false); await refetch(); + // TODO: toast }; return ( diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx deleted file mode 100644 index e00944e4582..00000000000 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx +++ /dev/null @@ -1,54 +0,0 @@ -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 = ({ - projectId, - data, -}) => { - const hasStale = data.some(d => d.stale); - const hasUnstale = data.some(d => !d.stale); - - return ( - - {({ hasAccess }) => ( - <> - } - variant="outlined" - size="small" - disabled={!hasAccess} - > - Mark as stale - - } - /> - } - variant="outlined" - size="small" - > - Un-mark as stale - - } - /> - - )} - - ); -}; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MoreActions/MoreActions.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MoreActions/MoreActions.tsx new file mode 100644 index 00000000000..259cff12533 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MoreActions/MoreActions.tsx @@ -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 = ({ projectId, data }) => { + const { refetch } = useProject(projectId); + const [anchorEl, setAnchorEl] = useState(null); + const { staleFeatures } = useProjectApi(); + const { setToastData, setToastApiError } = useToast(); + + const open = Boolean(anchorEl); + const selectedIds = data.map(({ name }) => name); + const handleClick = (event: React.MouseEvent) => { + 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 ( + <> + + + + + + ({ + borderRadius: `${theme.shape.borderRadius}px`, + padding: theme.spacing(1, 1.5), + }), + }} + > + + + {({ hasAccess }) => ( + <> + ( + + `${theme.shape.borderRadius}px`, + }} + > + + + + + + Mark as stale + + + + )} + /> + ( + + `${theme.shape.borderRadius}px`, + }} + > + + + + + + Un-mark as stale + + + + )} + /> + + )} + + + + + ); +}; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx index 170519806b1..e48be00bea1 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx @@ -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[]; @@ -79,7 +79,6 @@ export const SelectionActionsBar: VFC = ({  selected - + { 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, @@ -227,10 +257,12 @@ const useProjectApi = () => { removeGroupFromRole, changeUserRole, changeGroupRole, - errors, - loading, + archiveFeatures, + staleFeatures, searchProjectUser, setDefaultProjectStickiness, + errors, + loading, }; };