1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-10 17:53:36 +02:00

cr support in new feature toggle switch

This commit is contained in:
Tymoteusz Czech 2023-10-17 19:05:21 +02:00
parent 948374d55b
commit d73912669f
No known key found for this signature in database
GPG Key ID: 133555230D88D75F
2 changed files with 95 additions and 55 deletions

View File

@ -1,10 +1,7 @@
import { import {
ComponentProps, ComponentProps,
FC,
ReactNode,
useCallback, useCallback,
useMemo, useMemo,
useReducer,
useState, useState,
type VFC, type VFC,
} from 'react'; } from 'react';
@ -13,18 +10,15 @@ import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch
import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions'; import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions';
import { useOptimisticUpdate } from './hooks/useOptimisticUpdate'; import { useOptimisticUpdate } from './hooks/useOptimisticUpdate';
import { flexRow } from 'themes/themeStyles'; import { flexRow } from 'themes/themeStyles';
import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage';
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { import {
FeatureStrategyProdGuard, FeatureStrategyProdGuard,
isProdGuardEnabled, isProdGuardEnabled,
useFeatureStrategyProdGuard,
} from 'component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard'; } from 'component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard';
import { EnableEnvironmentDialog } from './EnableEnvironmentDialog/EnableEnvironmentDialog'; import { EnableEnvironmentDialog } from './EnableEnvironmentDialog/EnableEnvironmentDialog';
import { ListItemType } from '../ProjectFeatureToggles.types'; import { ListItemType } from '../ProjectFeatureToggles.types';
@ -44,7 +38,8 @@ type OnFeatureToggleSwitchArgs = {
hasStrategies?: boolean; hasStrategies?: boolean;
hasEnabledStrategies?: boolean; hasEnabledStrategies?: boolean;
isChangeRequestEnabled?: boolean; isChangeRequestEnabled?: boolean;
onError?: () => void; changeRequestToggle?: ReturnType<typeof useChangeRequestToggle>;
onRollback?: () => void;
onSuccess?: () => void; onSuccess?: () => void;
}; };
@ -68,11 +63,10 @@ const composeAndRunMiddlewares = (middlewares: Middleware[]) => {
runMiddleware(0); runMiddleware(0);
}; };
export const useFeatureToggleSwitch = () => { export const useFeatureToggleSwitch = (projectId: string) => {
const { loading, toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = const { loading, toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
useFeatureApi(); useFeatureApi();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
// FIXME: change modals approach
const [prodGuardModalState, setProdGuardModalState] = useState< const [prodGuardModalState, setProdGuardModalState] = useState<
ComponentProps<typeof FeatureStrategyProdGuard> ComponentProps<typeof FeatureStrategyProdGuard>
>({ >({
@ -82,10 +76,26 @@ export const useFeatureToggleSwitch = () => {
onClose: () => {}, onClose: () => {},
onClick: () => {}, onClick: () => {},
}); });
const [enableEnvironmentDialogState, setEnableEnvironmentDialogState] =
useState<ComponentProps<typeof EnableEnvironmentDialog>>({
isOpen: false,
environment: '',
onClose: () => {},
onActivateDisabledStrategies: () => {},
onAddDefaultStrategy: () => {},
});
const {
onChangeRequestToggle,
onChangeRequestToggleClose,
onChangeRequestToggleConfirm,
changeRequestDialogDetails,
} = useChangeRequestToggle(projectId);
const [changeRequestDialogCallback, setChangeRequestDialogCallback] =
useState<() => void>();
const onToggle = useCallback( const onToggle = useCallback(
async (newState: boolean, config: OnFeatureToggleSwitchArgs) => { async (newState: boolean, config: OnFeatureToggleSwitchArgs) => {
let shouldActivateDisabled = false; let shouldActivateDisabledStrategies = false;
const confirmProductionChanges: Middleware = (next) => { const confirmProductionChanges: Middleware = (next) => {
if (config.isChangeRequestEnabled) { if (config.isChangeRequestEnabled) {
@ -105,7 +115,7 @@ export const useFeatureToggleSwitch = () => {
...prev, ...prev,
open: false, open: false,
})); }));
config.onError?.(); config.onRollback?.();
}, },
onClick: () => { onClick: () => {
setProdGuardModalState((prev) => ({ setProdGuardModalState((prev) => ({
@ -122,16 +132,55 @@ export const useFeatureToggleSwitch = () => {
}; };
const ensureActiveStrategies: Middleware = (next) => { const ensureActiveStrategies: Middleware = (next) => {
shouldActivateDisabled = false; if (!config.hasStrategies || config.hasEnabledStrategies) {
// TODO: implementation return next();
}
setEnableEnvironmentDialogState({
isOpen: true,
environment: config.environmentName,
onClose: () => {
setEnableEnvironmentDialogState((prev) => ({
...prev,
isOpen: false,
}));
config.onRollback?.();
},
onActivateDisabledStrategies: () => {
setEnableEnvironmentDialogState((prev) => ({
...prev,
isOpen: false,
}));
shouldActivateDisabledStrategies = true;
next(); next();
},
onAddDefaultStrategy: () => {
setEnableEnvironmentDialogState((prev) => ({
...prev,
isOpen: false,
}));
next();
},
});
}; };
const addToChangeRequest: Middleware = (next) => { const addToChangeRequest: Middleware = (next) => {
if (!config.isChangeRequestEnabled) { if (!config.isChangeRequestEnabled) {
next(); return next();
} }
// TODO: implementation
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) => { const handleToggleEnvironmentOn: Middleware = async (next) => {
@ -144,7 +193,7 @@ export const useFeatureToggleSwitch = () => {
config.projectId, config.projectId,
config.featureId, config.featureId,
config.environmentName, config.environmentName,
shouldActivateDisabled, shouldActivateDisabledStrategies,
); );
setToastData({ setToastData({
type: 'success', type: 'success',
@ -154,7 +203,7 @@ export const useFeatureToggleSwitch = () => {
config.onSuccess?.(); config.onSuccess?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
config.onError?.(); config.onRollback?.();
} }
}; };
@ -177,32 +226,45 @@ export const useFeatureToggleSwitch = () => {
config.onSuccess?.(); config.onSuccess?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
config.onError?.(); config.onRollback?.();
} }
}; };
return composeAndRunMiddlewares([ return composeAndRunMiddlewares([
confirmProductionChanges, confirmProductionChanges,
addToChangeRequest,
ensureActiveStrategies, ensureActiveStrategies,
addToChangeRequest,
handleToggleEnvironmentOff, handleToggleEnvironmentOff,
handleToggleEnvironmentOn, handleToggleEnvironmentOn,
() => {
console.log('done', { newState, config }); // FIXME: remove
config.onSuccess?.();
},
]); ]);
}, },
[setProdGuardModalState], [setProdGuardModalState],
); );
const modals = useMemo( const modals = (
() => (
<> <>
<FeatureStrategyProdGuard {...prodGuardModalState} /> <FeatureStrategyProdGuard {...prodGuardModalState} />
<EnableEnvironmentDialog {...enableEnvironmentDialogState} />
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={() => {
changeRequestDialogCallback?.();
onChangeRequestToggleClose();
}}
environment={changeRequestDialogDetails?.environment}
onConfirm={() => {
changeRequestDialogCallback?.();
onChangeRequestToggleConfirm();
}}
messageComponent={
<UpdateEnabledMessage
enabled={changeRequestDialogDetails?.enabled!}
featureName={changeRequestDialogDetails?.featureName!}
environment={changeRequestDialogDetails.environment!}
/>
}
/>
</> </>
),
[prodGuardModalState],
); );
return { onToggle, modals }; return { onToggle, modals };
@ -305,7 +367,7 @@ export const createFeatureToggleCell =
hasStrategies: environment?.hasStrategies, hasStrategies: environment?.hasStrategies,
hasEnabledStrategies: environment?.hasEnabledStrategies, hasEnabledStrategies: environment?.hasEnabledStrategies,
isChangeRequestEnabled, isChangeRequestEnabled,
onError: onRollback, onRollback,
onSuccess: refetch, onSuccess: refetch,
}); });
}; };

View File

@ -110,7 +110,7 @@ export const ProjectFeatureToggles = ({
>(); >();
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const { onToggle: onFeatureToggle, modals: featureToggleModals } = const { onToggle: onFeatureToggle, modals: featureToggleModals } =
useFeatureToggleSwitch(); useFeatureToggleSwitch(projectId);
const { value: storedParams, setValue: setStoredParams } = const { value: storedParams, setValue: setStoredParams } =
createLocalStorage( createLocalStorage(
@ -134,11 +134,6 @@ export const ProjectFeatureToggles = ({
: globalStore.favorites, : globalStore.favorites,
); );
const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { favorite, unfavorite } = useFavoriteFeaturesApi();
const {
onChangeRequestToggleClose,
onChangeRequestToggleConfirm,
changeRequestDialogDetails,
} = useChangeRequestToggle(projectId);
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const [showExportDialog, setShowExportDialog] = useState(false); const [showExportDialog, setShowExportDialog] = useState(false);
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
@ -645,23 +640,6 @@ export const ProjectFeatureToggles = ({
}} }}
featureIds={[featureArchiveState || '']} featureIds={[featureArchiveState || '']}
projectId={projectId} projectId={projectId}
/>{' '}
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm}
messageComponent={
<UpdateEnabledMessage
featureName={
changeRequestDialogDetails.featureName!
}
enabled={changeRequestDialogDetails.enabled!}
environment={
changeRequestDialogDetails?.environment!
}
/>
}
/> />
<ConditionallyRender <ConditionallyRender
condition={ condition={