mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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