mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-06 00:07:44 +01:00
53354224fc
Upgrades biome to 1.6.1, and updates husky pre-commit hook. Most changes here are making type imports explicit.
258 lines
9.4 KiB
TypeScript
258 lines
9.4 KiB
TypeScript
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<typeof FeatureStrategyProdGuard>
|
|
>({
|
|
open: false,
|
|
label: '',
|
|
loading: false,
|
|
onClose: () => {},
|
|
onClick: () => {},
|
|
});
|
|
const [enableEnvironmentDialogState, setEnableEnvironmentDialogState] =
|
|
useState<ComponentProps<typeof EnableEnvironmentDialog>>({
|
|
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 = (
|
|
<>
|
|
<FeatureStrategyProdGuard {...prodGuardModalState} />
|
|
<ConditionallyRender
|
|
condition={featureSelected}
|
|
show={
|
|
<EnableEnvironmentDialog
|
|
{...enableEnvironmentDialogState}
|
|
/>
|
|
}
|
|
/>
|
|
<ChangeRequestDialogue
|
|
isOpen={changeRequestDialogDetails.isOpen}
|
|
onClose={() => {
|
|
changeRequestDialogCallback?.();
|
|
onChangeRequestToggleClose();
|
|
}}
|
|
environment={changeRequestDialogDetails?.environment}
|
|
disabled={pending}
|
|
onConfirm={() => {
|
|
changeRequestDialogCallback?.();
|
|
onChangeRequestToggleConfirm();
|
|
}}
|
|
messageComponent={
|
|
<UpdateEnabledMessage
|
|
enabled={changeRequestDialogDetails?.enabled!}
|
|
featureName={changeRequestDialogDetails?.featureName!}
|
|
environment={changeRequestDialogDetails.environment!}
|
|
/>
|
|
}
|
|
/>
|
|
</>
|
|
);
|
|
|
|
return {
|
|
onToggle,
|
|
modals,
|
|
};
|
|
};
|