import { 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'; const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ mx: 'auto', ...flexRow, })); interface IFeatureToggleSwitchProps { featureId: string; environmentName: string; projectId: string; value: boolean; showInfoBox?: () => void; onToggle?: ( projectId: string, feature: string, env: string, state: boolean ) => void; } export const FeatureToggleSwitch: VFC = ({ projectId, featureId, environmentName, value, onToggle, showInfoBox, }) => { const { 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 disabledStrategiesCount = feature?.environments .find(env => env.name === environmentName) ?.strategies.filter(strategy => strategy.disabled).length ?? 0; const callback = () => { onToggle && onToggle(projectId, feature.name, environmentName, !isChecked); }; 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.`, }); callback(); } catch (error: unknown) { if ( error instanceof Error && error.message === ENVIRONMENT_STRATEGY_ERROR ) { showInfoBox && showInfoBox(); } 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.`, }); callback(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); rollbackIsChecked(); } }; const onClick = async (e: React.MouseEvent) => { if (isChangeRequestConfigured(environmentName)) { e.preventDefault(); if (featureHasOnlyDisabledStrategies()) { setShowEnabledDialog(true); } else { onChangeRequestToggle( feature.name, environmentName, !value, false ); } return; } if (value) { await handleToggleEnvironmentOff(); return; } if (featureHasOnlyDisabledStrategies()) { setShowEnabledDialog(true); } else { await handleToggleEnvironmentOn(); } }; const onActivateStrategies = async () => { if (isChangeRequestConfigured(environmentName)) { onChangeRequestToggle(feature.name, environmentName, !value, true); } else { await handleToggleEnvironmentOn(true); } setShowEnabledDialog(false); }; const onAddDefaultStrategy = async () => { if (isChangeRequestConfigured(environmentName)) { onChangeRequestToggle(feature.name, environmentName, !value, 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} /> } /> ); };