import React, { useState, VFC } from 'react'; import { Box, styled } from '@mui/material'; import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch'; import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions'; import { useOptimisticUpdate } from './hooks/useOptimisticUpdate'; import { flexRow } from 'themes/themeStyles'; import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors'; import { formatUnknownError } from 'utils/formatUnknownError'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import useToast from 'hooks/useToast'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { EnableEnvironmentDialog } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/EnableEnvironmentDialog'; import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { FeatureStrategyProdGuard, useFeatureStrategyProdGuard, } from '../../../../feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard'; const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ mx: 'auto', ...flexRow, })); interface IFeatureToggleSwitchProps { featureId: string; environmentName: string; projectId: string; value: boolean; onError?: () => void; onToggle?: ( projectId: string, feature: string, env: string, state: boolean, ) => void; } export const FeatureToggleSwitch: VFC = ({ projectId, featureId, environmentName, value, onToggle, onError, }) => { const { loading, toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = useFeatureApi(); const { setToastData, setToastApiError } = useToast(); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { onChangeRequestToggle, onChangeRequestToggleClose, onChangeRequestToggleConfirm, changeRequestDialogDetails, } = useChangeRequestToggle(projectId); const [isChecked, setIsChecked, rollbackIsChecked] = useOptimisticUpdate(value); const [showEnabledDialog, setShowEnabledDialog] = useState(false); const { feature } = useFeature(projectId, featureId); const enableProdGuard = useFeatureStrategyProdGuard( feature, environmentName, ); const [showProdGuard, setShowProdGuard] = useState(false); const disabledStrategiesCount = feature?.environments .find((env) => env.name === environmentName) ?.strategies.filter((strategy) => strategy.disabled).length ?? 0; const handleToggleEnvironmentOn = async ( shouldActivateDisabled = false, ) => { try { setIsChecked(!isChecked); await toggleFeatureEnvironmentOn( projectId, feature.name, environmentName, shouldActivateDisabled, ); setToastData({ type: 'success', title: `Available in ${environmentName}`, text: `${feature.name} is now available in ${environmentName} based on its defined strategies.`, }); onToggle?.(projectId, feature.name, environmentName, !isChecked); } catch (error: unknown) { if ( error instanceof Error && error.message === ENVIRONMENT_STRATEGY_ERROR ) { onError?.(); } else { setToastApiError(formatUnknownError(error)); } rollbackIsChecked(); } }; const handleToggleEnvironmentOff = async () => { try { setIsChecked(!isChecked); await toggleFeatureEnvironmentOff( projectId, feature.name, environmentName, ); setToastData({ type: 'success', title: `Unavailable in ${environmentName}`, text: `${feature.name} is unavailable in ${environmentName} and its strategies will no longer have any effect.`, }); onToggle?.(projectId, feature.name, environmentName, !isChecked); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); rollbackIsChecked(); } }; const handleClick = async () => { setShowProdGuard(false); if (isChangeRequestConfigured(environmentName)) { if (featureHasOnlyDisabledStrategies()) { setShowEnabledDialog(true); } else { onChangeRequestToggle( feature.name, environmentName, !isChecked, false, ); } return; } if (isChecked) { await handleToggleEnvironmentOff(); return; } if (featureHasOnlyDisabledStrategies()) { setShowEnabledDialog(true); } else { await handleToggleEnvironmentOn(); } }; const onClick = async () => { if (enableProdGuard && !isChangeRequestConfigured(environmentName)) { setShowProdGuard(true); } else { await handleClick(); } }; const onActivateStrategies = async () => { if (isChangeRequestConfigured(environmentName)) { onChangeRequestToggle( feature.name, environmentName, !isChecked, true, ); } else { await handleToggleEnvironmentOn(true); } setShowEnabledDialog(false); }; const onAddDefaultStrategy = async () => { if (isChangeRequestConfigured(environmentName)) { onChangeRequestToggle( feature.name, environmentName, !isChecked, false, ); } else { await handleToggleEnvironmentOn(); } setShowEnabledDialog(false); }; const featureHasOnlyDisabledStrategies = () => { const featureEnvironment = feature?.environments?.find( (env) => env.name === environmentName, ); return ( featureEnvironment?.strategies && featureEnvironment?.strategies?.length > 0 && featureEnvironment?.strategies?.every( (strategy) => strategy.disabled, ) ); }; const key = `${feature.name}-${environmentName}`; return ( <> setShowEnabledDialog(false)} environment={environmentName} disabledStrategiesCount={disabledStrategiesCount} onActivateDisabledStrategies={onActivateStrategies} onAddDefaultStrategy={onAddDefaultStrategy} /> } /> setShowProdGuard(false)} onClick={handleClick} loading={loading} label={`${isChecked ? 'Disable' : 'Enable'} Environment`} /> ); };