mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Delete archived toggles from the front end (#1150)
* Grouping fix grid not showing user names * Remove deprecated access endpoints * Manual prettier * Revert user role update * Add a way to delete archived toggles from the front end * Fix layout * Add project to permissionbutton * Prettier * Minor fixes * Run prettier
This commit is contained in:
parent
61c0d6f0a1
commit
00ab52875e
@ -14,7 +14,7 @@ import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/Fe
|
||||
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell';
|
||||
import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell';
|
||||
import { ArchivedFeatureActionCell } from 'component/archive/ArchiveTable/ArchivedFeatureActionCell/ArchivedFeatureActionCell';
|
||||
import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable';
|
||||
import theme from 'themes/theme';
|
||||
import { FeatureSchema } from 'openapi';
|
||||
@ -24,6 +24,8 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { ArchivedFeatureDeleteConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureDeleteConfirm/ArchivedFeatureDeleteConfirm';
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
|
||||
export interface IFeaturesArchiveTableProps {
|
||||
archivedFeatures: FeatureSchema[];
|
||||
@ -52,6 +54,9 @@ export const ArchiveTable = ({
|
||||
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { reviveFeature } = useFeatureArchiveApi();
|
||||
|
||||
@ -153,12 +158,16 @@ export const ArchiveTable = ({
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
maxWidth: 85,
|
||||
maxWidth: 120,
|
||||
canSort: false,
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
<ReviveArchivedFeatureCell
|
||||
project={original.project}
|
||||
onRevive={() => onRevive(original.name)}
|
||||
Cell: ({ row: { original: feature } }: any) => (
|
||||
<ArchivedFeatureActionCell
|
||||
project={feature.project}
|
||||
onRevive={() => onRevive(feature.name)}
|
||||
onDelete={() => {
|
||||
setDeletedFeature(feature);
|
||||
setDeleteModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -290,6 +299,12 @@ export const ArchiveTable = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ArchivedFeatureDeleteConfirm
|
||||
deletedFeature={deletedFeature}
|
||||
open={deleteModalOpen}
|
||||
setOpen={setDeleteModalOpen}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
@ -1,16 +1,21 @@
|
||||
import { VFC } from 'react';
|
||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import { Undo } from '@mui/icons-material';
|
||||
import { Delete, Undo } from '@mui/icons-material';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import {
|
||||
DELETE_FEATURE,
|
||||
UPDATE_FEATURE,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
interface IReviveArchivedFeatureCell {
|
||||
onRevive: () => void;
|
||||
onDelete: () => void;
|
||||
project: string;
|
||||
}
|
||||
|
||||
export const ReviveArchivedFeatureCell: VFC<IReviveArchivedFeatureCell> = ({
|
||||
export const ArchivedFeatureActionCell: VFC<IReviveArchivedFeatureCell> = ({
|
||||
onRevive,
|
||||
onDelete,
|
||||
project,
|
||||
}) => {
|
||||
return (
|
||||
@ -23,6 +28,14 @@ export const ReviveArchivedFeatureCell: VFC<IReviveArchivedFeatureCell> = ({
|
||||
>
|
||||
<Undo />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={project}
|
||||
tooltipProps={{ title: 'Delete feature toggle' }}
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
</ActionCell>
|
||||
);
|
||||
};
|
@ -0,0 +1,97 @@
|
||||
import { Alert, styled } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
interface IArchivedFeatureDeleteConfirmProps {
|
||||
deletedFeature?: IFeatureToggle;
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
const StyledDeleteParagraph = styled('p')(({ theme }) => ({
|
||||
marginTop: theme.spacing(4),
|
||||
}));
|
||||
|
||||
const StyledFormInput = styled(Input)(({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const ArchivedFeatureDeleteConfirm = ({
|
||||
deletedFeature,
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: IArchivedFeatureDeleteConfirmProps) => {
|
||||
const [confirmName, setConfirmName] = useState('');
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { deleteFeature } = useFeatureArchiveApi();
|
||||
|
||||
const onDeleteFeatureToggle = async () => {
|
||||
try {
|
||||
if (!deletedFeature) {
|
||||
return;
|
||||
}
|
||||
await deleteFeature(deletedFeature.name);
|
||||
await refetch();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Feature deleted',
|
||||
text: `You have successfully deleted the ${deletedFeature.name} feature toggle.`,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
} finally {
|
||||
clearModal();
|
||||
}
|
||||
};
|
||||
|
||||
const clearModal = () => {
|
||||
setOpen(false);
|
||||
setConfirmName('');
|
||||
};
|
||||
|
||||
const formId = 'delete-feature-toggle-confirmation-form';
|
||||
|
||||
return (
|
||||
<Dialogue
|
||||
title="Are you sure you want to delete this feature toggle?"
|
||||
open={open}
|
||||
primaryButtonText="Delete feature toggle"
|
||||
secondaryButtonText="Cancel"
|
||||
onClick={onDeleteFeatureToggle}
|
||||
onClose={clearModal}
|
||||
disabledPrimaryButton={deletedFeature?.name !== confirmName}
|
||||
formId={formId}
|
||||
>
|
||||
<Alert severity="warning">
|
||||
Warning! To safely delete a feature toggle you might want to
|
||||
delete the related code in your application first. This ensures
|
||||
you avoid any errors in case you create a new feature toggle
|
||||
with the same name in the future.
|
||||
</Alert>
|
||||
|
||||
<StyledDeleteParagraph>
|
||||
In order to delete this feature toggle, please enter the name of
|
||||
the toggle in the textfield below:{' '}
|
||||
<strong>{deletedFeature?.name}</strong>
|
||||
</StyledDeleteParagraph>
|
||||
|
||||
<form id={formId}>
|
||||
<StyledFormInput
|
||||
autoFocus
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfirmName(e.currentTarget.value);
|
||||
}}
|
||||
value={confirmName}
|
||||
label="Feature toggle name"
|
||||
/>
|
||||
</form>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
@ -11,5 +11,11 @@ export const useFeatureArchiveApi = () => {
|
||||
return makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return { reviveFeature, errors, loading };
|
||||
const deleteFeature = async (feature: string) => {
|
||||
const path = `api/admin/archive/${feature}`;
|
||||
const req = createRequest(path, { method: 'DELETE' });
|
||||
return makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return { reviveFeature, deleteFeature, errors, loading };
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user