From 576e907d7ce92f38bceb264d8c85cdf790c8560a Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:45:12 +0200 Subject: [PATCH] refactor: frontend for feature toggle switch --- .../FeatureStrategyProdGuard.tsx | 18 +++-- .../EnableEnvironmentDialog.tsx | 13 ++-- ...tureOverviewSidePanelEnvironmentSwitch.tsx | 18 +++++ .../FeatureToggleSwitch.tsx | 67 +++++++------------ .../ProjectFeatureToggles.tsx | 11 +++ 5 files changed, 72 insertions(+), 55 deletions(-) diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx index 44da2cbd90..450ed93ee2 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyProdGuard/FeatureStrategyProdGuard.tsx @@ -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. diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/EnableEnvironmentDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/EnableEnvironmentDialog.tsx index c1ad87aaef..7c34de17e4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/EnableEnvironmentDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/EnableEnvironmentDialog.tsx @@ -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 = ({ @@ -21,7 +22,7 @@ export const EnableEnvironmentDialog: FC = ({ onActivateDisabledStrategies, onClose, environment, - disabledStrategiesCount = 0, + disabledStrategiesCount, }) => { const projectId = useRequiredPathParam('projectId'); @@ -61,8 +62,12 @@ export const EnableEnvironmentDialog: FC = ({ color='text.primary' sx={{ mb: (theme) => theme.spacing(2) }} > - The feature toggle has {disabledStrategiesCount} disabled - {disabledStrategiesCount === 1 ? ' strategy' : ' strategies'}. + The feature toggle has {disabledStrategiesCount} disabled + {disabledStrategiesCount === 1 ? ' strategy' : ' strategies'}.} + elseShow={"The feature toggle has disabled strategies."} + /> You can choose to enable all the disabled strategies or you can diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx index dcb1291087..2cdac8cc12 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx @@ -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 ( @@ -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} diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx index 973dfd3b29..917da21deb 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx @@ -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 = ({ featureId, environmentName, value, + type, + hasStrategies, + hasEnabledStrategies, onToggle, onError, }) => { @@ -60,17 +65,10 @@ export const FeatureToggleSwitch: VFC = ({ useOptimisticUpdate(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 = ({ 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 = ({ 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 = ({ 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 = ({ return; } - if (featureHasOnlyDisabledStrategies()) { + if (featureHasOnlyDisabledStrategies) { setShowEnabledDialog(true); } else { await handleToggleEnvironmentOn(); @@ -159,12 +157,7 @@ export const FeatureToggleSwitch: VFC = ({ 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 = ({ const onAddDefaultStrategy = async () => { if (isChangeRequestConfigured(environmentName)) { onChangeRequestToggle( - feature.name, + featureId, environmentName, !isChecked, false, @@ -185,20 +178,7 @@ export const FeatureToggleSwitch: VFC = ({ 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 = ({ isOpen={showEnabledDialog} onClose={() => setShowEnabledDialog(false)} environment={environmentName} - disabledStrategiesCount={disabledStrategiesCount} onActivateDisabledStrategies={onActivateStrategies} onAddDefaultStrategy={onAddDefaultStrategy} /> diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 6dda035cf0..e463b45366 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -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 + } />