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 { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell';
|
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 { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { FeatureSchema } from 'openapi';
|
import { FeatureSchema } from 'openapi';
|
||||||
@ -24,6 +24,8 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
|||||||
import { useSearch } from 'hooks/useSearch';
|
import { useSearch } from 'hooks/useSearch';
|
||||||
import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell';
|
import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { ArchivedFeatureDeleteConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureDeleteConfirm/ArchivedFeatureDeleteConfirm';
|
||||||
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
export interface IFeaturesArchiveTableProps {
|
export interface IFeaturesArchiveTableProps {
|
||||||
archivedFeatures: FeatureSchema[];
|
archivedFeatures: FeatureSchema[];
|
||||||
@ -52,6 +54,9 @@ export const ArchiveTable = ({
|
|||||||
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||||
|
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const { reviveFeature } = useFeatureArchiveApi();
|
const { reviveFeature } = useFeatureArchiveApi();
|
||||||
|
|
||||||
@ -153,12 +158,16 @@ export const ArchiveTable = ({
|
|||||||
Header: 'Actions',
|
Header: 'Actions',
|
||||||
id: 'Actions',
|
id: 'Actions',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
maxWidth: 85,
|
maxWidth: 120,
|
||||||
canSort: false,
|
canSort: false,
|
||||||
Cell: ({ row: { original } }: any) => (
|
Cell: ({ row: { original: feature } }: any) => (
|
||||||
<ReviveArchivedFeatureCell
|
<ArchivedFeatureActionCell
|
||||||
project={original.project}
|
project={feature.project}
|
||||||
onRevive={() => onRevive(original.name)}
|
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>
|
</PageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { VFC } from 'react';
|
import { VFC } from 'react';
|
||||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
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 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 {
|
interface IReviveArchivedFeatureCell {
|
||||||
onRevive: () => void;
|
onRevive: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
project: string;
|
project: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReviveArchivedFeatureCell: VFC<IReviveArchivedFeatureCell> = ({
|
export const ArchivedFeatureActionCell: VFC<IReviveArchivedFeatureCell> = ({
|
||||||
onRevive,
|
onRevive,
|
||||||
|
onDelete,
|
||||||
project,
|
project,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@ -23,6 +28,14 @@ export const ReviveArchivedFeatureCell: VFC<IReviveArchivedFeatureCell> = ({
|
|||||||
>
|
>
|
||||||
<Undo />
|
<Undo />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={DELETE_FEATURE}
|
||||||
|
projectId={project}
|
||||||
|
tooltipProps={{ title: 'Delete feature toggle' }}
|
||||||
|
onClick={onDelete}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</PermissionIconButton>
|
||||||
</ActionCell>
|
</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 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