import { useEffect, useState, type VFC } from 'react'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender.tsx'; import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; import { Alert, Typography } from '@mui/material'; import { Link } from 'react-router-dom'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; import { useScheduledChangeRequestsWithFlags } from 'hooks/api/getters/useScheduledChangeRequestsWithFlags/useScheduledChangeRequestsWithFlags'; import type { ScheduledChangeRequestViewModel } from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy'; interface IFeatureArchiveDialogProps { isOpen: boolean; onConfirm: () => void; onClose: () => void; projectId: string; featureIds: string[]; featuresWithUsage?: string[]; } const RemovedDependenciesAlert = () => { return ( theme.spacing(2, 0) }}> Archiving features with dependencies will also remove those dependencies. ); }; const UsageWarning = ({ ids, projectId, }: { ids?: string[]; projectId: string; }) => { const formatPath = (id: string) => { return `/projects/${projectId}/features/${id}`; }; if (ids) { return ( theme.spacing(2, 0) }} > {`${ids.length} feature flags `} have usage from applications. If you archive these feature flags they will not be available to Client SDKs: ); } return null; }; const ArchiveParentError = ({ ids, projectId, }: { ids?: string[]; projectId: string; }) => { const formatPath = (id: string) => { return `/projects/${projectId}/features/${id}`; }; if (ids && ids.length > 1) { return ( theme.spacing(2, 0) }} > {`${ids.length} feature flags `} have child features that depend on them and are not part of the archive operation. These parent features can not be archived: ); } if (ids && ids.length === 1) { return ( theme.spacing(2, 0) }} > {ids[0]} has child features that depend on it and are not part of the archive operation. ); } return null; }; const ScheduledChangeRequestAlert: VFC<{ changeRequests?: ScheduledChangeRequestViewModel[]; projectId: string; }> = ({ changeRequests, projectId }) => { if (changeRequests && changeRequests.length > 0) { return ( theme.spacing(2, 0) }} >

This archive operation would conflict with{' '} {changeRequests.length} scheduled change request(s). The change request(s) that would be affected by this are:

); } else if (changeRequests === undefined) { return (

This archive operation might conflict with one or more scheduled change requests. If you complete it, those change requests can no longer be applied.

); } // all good, we have nothing to show return null; }; const useActionButtonText = (projectId: string, isBulkArchive: boolean) => { const getHighestEnvironment = useHighestPermissionChangeRequestEnvironment(projectId); const environment = getHighestEnvironment(); const { isChangeRequestConfiguredForReview } = useChangeRequestsEnabled(projectId); if ( environment && isChangeRequestConfiguredForReview(environment) && isBulkArchive ) { return 'Add to change request'; } if (environment && isChangeRequestConfiguredForReview(environment)) { return 'Add change to draft'; } if (isBulkArchive) { return 'Archive flags'; } return 'Archive flag'; }; const useArchiveAction = ({ projectId, featureIds, onSuccess, onError, }: { projectId: string; featureIds: string[]; onSuccess: () => void; onError: () => void; }) => { const { setToastData, setToastApiError } = useToast(); const { archiveFeatureToggle } = useFeatureApi(); const { archiveFeatures } = useProjectApi(); const { isChangeRequestConfiguredForReview } = useChangeRequestsEnabled(projectId); const { addChange } = useChangeRequestApi(); const { refetch: refetchChangeRequests } = usePendingChangeRequests(projectId); const getHighestEnvironment = useHighestPermissionChangeRequestEnvironment(projectId); const isBulkArchive = featureIds?.length > 1; const environment = getHighestEnvironment(); const addArchiveToggleToChangeRequest = async () => { if (!environment) { console.error('No change request environment'); return; } await addChange( projectId, environment, featureIds.map((feature) => ({ action: 'archiveFeature', feature: feature, payload: undefined, })), ); refetchChangeRequests(); setToastData({ type: 'success', text: isBulkArchive ? 'Changes added to a draft' : 'Change added to a draft', }); }; const archiveToggle = async () => { await archiveFeatureToggle(projectId, featureIds[0]); setToastData({ type: 'success', text: 'Feature flag archived', }); }; const archiveToggles = async () => { await archiveFeatures(projectId, featureIds); setToastData({ type: 'success', text: 'Feature flags archived', }); }; return async () => { try { if ( environment && isChangeRequestConfiguredForReview(environment) ) { await addArchiveToggleToChangeRequest(); } else if (isBulkArchive) { await archiveToggles(); } else { await archiveToggle(); } onSuccess(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); onError(); } }; }; const useVerifyArchive = ( featureIds: string[], projectId: string, isOpen: boolean, ) => { const [disableArchive, setDisableArchive] = useState(true); const [offendingParents, setOffendingParents] = useState([]); const [hasDeletedDependencies, setHasDeletedDependencies] = useState(false); const { verifyArchiveFeatures } = useProjectApi(); useEffect(() => { if (isOpen) { verifyArchiveFeatures(projectId, featureIds) .then((res) => res.json()) .then( ({ hasDeletedDependencies, parentsWithChildFeatures }) => { if (parentsWithChildFeatures.length === 0) { setDisableArchive(false); setOffendingParents(parentsWithChildFeatures); } else { setDisableArchive(true); setOffendingParents(parentsWithChildFeatures); } setHasDeletedDependencies(hasDeletedDependencies); }, ); } }, [ JSON.stringify(featureIds), isOpen, projectId, setOffendingParents, setDisableArchive, setHasDeletedDependencies, ]); return { disableArchive, offendingParents, hasDeletedDependencies }; }; export const FeatureArchiveDialog: VFC = ({ isOpen, onClose, onConfirm, projectId, featureIds, featuresWithUsage, }) => { const isBulkArchive = featureIds?.length > 1; const buttonText = useActionButtonText(projectId, isBulkArchive); const dialogTitle = isBulkArchive ? 'Archive feature flags' : 'Archive feature flag'; const archiveAction = useArchiveAction({ projectId, featureIds, onSuccess() { onConfirm(); onClose(); }, onError() { onClose(); }, }); const { changeRequests } = useScheduledChangeRequestsWithFlags( projectId, featureIds, ); const { disableArchive, offendingParents, hasDeletedDependencies } = useVerifyArchive(featureIds, projectId, isOpen); const removeDependenciesWarning = offendingParents.length === 0 && hasDeletedDependencies; return (

Are you sure you want to archive{' '} {featureIds?.length} feature flags?

0, )} show={ } /> 0} show={ } /> } /> {featureIds?.map((id) => (
  • {id}
  • ))} } /> } elseShow={ <>

    Are you sure you want to archive{' '} {isBulkArchive ? 'these feature flags' : 'this feature flag'} ?

    0} show={ } /> } /> } />
    ); };