1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-18 13:48:58 +02:00

refactor: frontend for feature toggle switch

This commit is contained in:
Tymoteusz Czech 2023-10-12 14:45:12 +02:00
parent 16575c4f4b
commit 576e907d7c
No known key found for this signature in database
GPG Key ID: 133555230D88D75F
5 changed files with 72 additions and 55 deletions

View File

@ -62,20 +62,24 @@ export const FeatureStrategyProdGuard = ({
// Check if the prod guard dialog should be enabled.
export const useFeatureStrategyProdGuard = (
feature: IFeatureToggle,
environmentId: string,
featureOrType: string | IFeatureToggle,
environmentId?: string,
): boolean => {
const [settings] = useFeatureStrategyProdGuardSettings();
const environment = feature.environments.find((environment) => {
return environment.name === environmentId;
});
if (settings.hide) {
return false;
}
return environment?.type === PRODUCTION;
if (typeof featureOrType === 'string') {
return featureOrType === PRODUCTION;
}
return featureOrType?.environments?.some(
(environment) =>
environment.name === environmentId ||
environment.type === PRODUCTION,
);
};
// Store the "always hide" prod guard dialog setting in localStorage.

View File

@ -4,6 +4,7 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
interface IEnableEnvironmentDialogProps {
isOpen: boolean;
@ -12,7 +13,7 @@ interface IEnableEnvironmentDialogProps {
onClose: () => void;
environment?: string;
showBanner?: boolean;
disabledStrategiesCount: number;
disabledStrategiesCount?: number;
}
export const EnableEnvironmentDialog: FC<IEnableEnvironmentDialogProps> = ({
@ -21,7 +22,7 @@ export const EnableEnvironmentDialog: FC<IEnableEnvironmentDialogProps> = ({
onActivateDisabledStrategies,
onClose,
environment,
disabledStrategiesCount = 0,
disabledStrategiesCount,
}) => {
const projectId = useRequiredPathParam('projectId');
@ -61,8 +62,12 @@ export const EnableEnvironmentDialog: FC<IEnableEnvironmentDialogProps> = ({
color='text.primary'
sx={{ mb: (theme) => theme.spacing(2) }}
>
The feature toggle has {disabledStrategiesCount} disabled
{disabledStrategiesCount === 1 ? ' strategy' : ' strategies'}.
<ConditionallyRender
condition={disabledStrategiesCount !== undefined}
show={<>The feature toggle has {disabledStrategiesCount} disabled
{disabledStrategiesCount === 1 ? ' strategy' : ' strategies'}.</>}
elseShow={"The feature toggle has disabled strategies."}
/>
</Typography>
<Typography variant='body1' color='text.primary'>
You can choose to enable all the disabled strategies or you can

View File

@ -5,6 +5,7 @@ import { styled } from '@mui/material';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider';
import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch';
import { useMemo } from 'react';
const StyledContainer = styled('div')(({ theme }) => ({
marginLeft: theme.spacing(-1.5),
@ -58,6 +59,20 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
if (callback) callback();
};
const featureEnvironment = feature?.environments?.find(
(env) => env.name === environment.name,
);
const hasStrategies =
featureEnvironment?.strategies &&
featureEnvironment?.strategies?.length > 0;
const hasEnabledStrategies =
hasStrategies &&
featureEnvironment?.strategies?.some(
(strategy) => strategy.disabled !== true,
);
return (
<StyledContainer>
<StyledLabel>
@ -65,9 +80,12 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
featureId={feature.name}
projectId={projectId}
environmentName={environment.name}
type={featureEnvironment?.type}
onToggle={handleToggle}
onError={showInfoBox}
value={enabled}
hasStrategies={hasStrategies}
hasEnabledStrategies={hasEnabledStrategies}
/>
{children ?? defaultContent}
</StyledLabel>

View File

@ -1,4 +1,4 @@
import React, { useState, VFC } from 'react';
import { useState, type 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';
@ -7,7 +7,6 @@ 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';
@ -17,7 +16,7 @@ import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConf
import {
FeatureStrategyProdGuard,
useFeatureStrategyProdGuard,
} from '../../../../feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard';
} from 'component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard';
const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({
mx: 'auto',
@ -29,6 +28,9 @@ interface IFeatureToggleSwitchProps {
environmentName: string;
projectId: string;
value: boolean;
type: string;
hasStrategies?: boolean;
hasEnabledStrategies?: boolean;
onError?: () => void;
onToggle?: (
projectId: string,
@ -43,6 +45,9 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
featureId,
environmentName,
value,
type,
hasStrategies,
hasEnabledStrategies,
onToggle,
onError,
}) => {
@ -60,17 +65,10 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
useOptimisticUpdate<boolean>(value);
const [showEnabledDialog, setShowEnabledDialog] = useState(false);
const { feature } = useFeature(projectId, featureId);
const enableProdGuard = useFeatureStrategyProdGuard(
feature,
environmentName,
);
const enableProdGuard = useFeatureStrategyProdGuard(type, environmentName);
const [showProdGuard, setShowProdGuard] = useState(false);
const disabledStrategiesCount =
feature?.environments
.find((env) => env.name === environmentName)
?.strategies.filter((strategy) => strategy.disabled).length ?? 0;
const featureHasOnlyDisabledStrategies =
hasStrategies && !hasEnabledStrategies;
const handleToggleEnvironmentOn = async (
shouldActivateDisabled = false,
@ -79,16 +77,16 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
setIsChecked(!isChecked);
await toggleFeatureEnvironmentOn(
projectId,
feature.name,
featureId,
environmentName,
shouldActivateDisabled,
);
setToastData({
type: 'success',
title: `Available in ${environmentName}`,
text: `${feature.name} is now available in ${environmentName} based on its defined strategies.`,
text: `${featureId} is now available in ${environmentName} based on its defined strategies.`,
});
onToggle?.(projectId, feature.name, environmentName, !isChecked);
onToggle?.(projectId, featureId, environmentName, !isChecked);
} catch (error: unknown) {
if (
error instanceof Error &&
@ -107,15 +105,15 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
setIsChecked(!isChecked);
await toggleFeatureEnvironmentOff(
projectId,
feature.name,
featureId,
environmentName,
);
setToastData({
type: 'success',
title: `Unavailable in ${environmentName}`,
text: `${feature.name} is unavailable in ${environmentName} and its strategies will no longer have any effect.`,
text: `${featureId} is unavailable in ${environmentName} and its strategies will no longer have any effect.`,
});
onToggle?.(projectId, feature.name, environmentName, !isChecked);
onToggle?.(projectId, featureId, environmentName, !isChecked);
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
rollbackIsChecked();
@ -125,11 +123,11 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
const handleClick = async () => {
setShowProdGuard(false);
if (isChangeRequestConfigured(environmentName)) {
if (featureHasOnlyDisabledStrategies()) {
if (featureHasOnlyDisabledStrategies) {
setShowEnabledDialog(true);
} else {
onChangeRequestToggle(
feature.name,
featureId,
environmentName,
!isChecked,
false,
@ -142,7 +140,7 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
return;
}
if (featureHasOnlyDisabledStrategies()) {
if (featureHasOnlyDisabledStrategies) {
setShowEnabledDialog(true);
} else {
await handleToggleEnvironmentOn();
@ -159,12 +157,7 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
const onActivateStrategies = async () => {
if (isChangeRequestConfigured(environmentName)) {
onChangeRequestToggle(
feature.name,
environmentName,
!isChecked,
true,
);
onChangeRequestToggle(featureId, environmentName, !isChecked, true);
} else {
await handleToggleEnvironmentOn(true);
}
@ -174,7 +167,7 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
const onAddDefaultStrategy = async () => {
if (isChangeRequestConfigured(environmentName)) {
onChangeRequestToggle(
feature.name,
featureId,
environmentName,
!isChecked,
false,
@ -185,20 +178,7 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
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}`;
const key = `${featureId}-${environmentName}`;
return (
<>
@ -225,7 +205,6 @@ export const FeatureToggleSwitch: VFC<IFeatureToggleSwitchProps> = ({
isOpen={showEnabledDialog}
onClose={() => setShowEnabledDialog(false)}
environment={environmentName}
disabledStrategiesCount={disabledStrategiesCount}
onActivateDisabledStrategies={onActivateStrategies}
onAddDefaultStrategy={onAddDefaultStrategy}
/>

View File

@ -100,6 +100,9 @@ type ListItemType = Pick<
name: string;
enabled: boolean;
variantCount: number;
type: string;
hasStrategies: boolean;
hasEnabledStrategies: boolean;
};
};
someEnabledEnvironmentHasVariants: boolean;
@ -317,6 +320,14 @@ export const ProjectFeatureToggles = ({
projectId={projectId}
featureId={feature.name}
environmentName={name}
type={feature.environments[name].type}
hasStrategies={
feature.environments[name].hasStrategies
}
hasEnabledStrategies={
feature.environments[name]
.hasEnabledStrategies
}
/>
<ConditionallyRender
condition={hasWarning}