import { type ComponentProps, useCallback, useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useToast from 'hooks/useToast'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { FeatureStrategyProdGuard, isProdGuardEnabled, } from 'component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard'; import { EnableEnvironmentDialog } from './EnableEnvironmentDialog/EnableEnvironmentDialog'; import type { OnFeatureToggleSwitchArgs, UseFeatureToggleSwitchType, } from './FeatureToggleSwitch.types'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; type Middleware = (next: () => void) => void; const composeAndRunMiddlewares = (middlewares: Middleware[]) => { const runMiddleware = (currentIndex: number) => { if (currentIndex < middlewares.length) { middlewares[currentIndex](() => runMiddleware(currentIndex + 1)); } }; runMiddleware(0); }; export const useFeatureToggleSwitch: UseFeatureToggleSwitchType = ( projectId: string, ) => { const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = useFeatureApi(); const { setToastData, setToastApiError } = useToast(); const [prodGuardModalState, setProdGuardModalState] = useState< ComponentProps >({ open: false, label: '', loading: false, onClose: () => {}, onClick: () => {}, }); const [enableEnvironmentDialogState, setEnableEnvironmentDialogState] = useState>({ isOpen: false, environment: '', featureId: '', onClose: () => {}, onActivateDisabledStrategies: () => {}, onAddDefaultStrategy: () => {}, }); const { pending, onChangeRequestToggle, onChangeRequestToggleClose, onChangeRequestToggleConfirm, changeRequestDialogDetails, } = useChangeRequestToggle(projectId); const [changeRequestDialogCallback, setChangeRequestDialogCallback] = useState<() => void>(); const onToggle = useCallback( async (newState: boolean, config: OnFeatureToggleSwitchArgs) => { let shouldActivateDisabledStrategies = false; const confirmProductionChanges: Middleware = (next) => { if (config.isChangeRequestEnabled) { // skip if change requests are enabled return next(); } if (!isProdGuardEnabled(config.environmentType || '')) { return next(); } setProdGuardModalState({ open: true, label: `${!newState ? 'Disable' : 'Enable'} Environment`, loading: false, onClose: () => { setProdGuardModalState((prev) => ({ ...prev, open: false, })); config.onRollback?.(); }, onClick: () => { setProdGuardModalState((prev) => ({ ...prev, open: false, loading: true, })); next(); }, }); }; const ensureActiveStrategies: Middleware = (next) => { if ( newState === false || !config.hasStrategies || config.hasEnabledStrategies ) { return next(); } setEnableEnvironmentDialogState({ isOpen: true, environment: config.environmentName, featureId: config.featureId, onClose: () => { setEnableEnvironmentDialogState((prev) => ({ ...prev, isOpen: false, })); config.onRollback?.(); }, onActivateDisabledStrategies: () => { setEnableEnvironmentDialogState((prev) => ({ ...prev, isOpen: false, })); shouldActivateDisabledStrategies = true; next(); }, onAddDefaultStrategy: () => { setEnableEnvironmentDialogState((prev) => ({ ...prev, isOpen: false, })); next(); }, }); }; const addToChangeRequest: Middleware = (next) => { if (!config.isChangeRequestEnabled) { return next(); } setChangeRequestDialogCallback(() => { setChangeRequestDialogCallback(undefined); // always reset to previous state when using change requests config.onRollback?.(); }); onChangeRequestToggle( config.featureId, config.environmentName, newState, shouldActivateDisabledStrategies, ); }; const handleToggleEnvironmentOn: Middleware = async (next) => { if (newState !== true) { return next(); } try { await toggleFeatureEnvironmentOn( config.projectId, config.featureId, config.environmentName, shouldActivateDisabledStrategies, ); setToastData({ type: 'success', title: `Enabled in ${config.environmentName}`, text: `${config.featureId} is now available in ${config.environmentName} based on its defined strategies.`, }); config.onSuccess?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); config.onRollback?.(); } }; const handleToggleEnvironmentOff: Middleware = async (next) => { if (newState !== false) { return next(); } try { await toggleFeatureEnvironmentOff( config.projectId, config.featureId, config.environmentName, ); setToastData({ type: 'success', title: `Disabled in ${config.environmentName}`, text: `${config.featureId} is unavailable in ${config.environmentName} and its strategies will no longer have any effect.`, }); config.onSuccess?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); config.onRollback?.(); } }; return composeAndRunMiddlewares([ confirmProductionChanges, ensureActiveStrategies, addToChangeRequest, handleToggleEnvironmentOff, handleToggleEnvironmentOn, ]); }, [setProdGuardModalState], ); const featureSelected = enableEnvironmentDialogState.featureId.length !== 0; const modals = ( <> } /> { changeRequestDialogCallback?.(); onChangeRequestToggleClose(); }} environment={changeRequestDialogDetails?.environment} disabled={pending} onConfirm={() => { changeRequestDialogCallback?.(); onChangeRequestToggleConfirm(); }} messageComponent={ } /> ); return { onToggle, modals, }; };