diff --git a/frontend/cypress/integration/import/import.spec.ts b/frontend/cypress/integration/import/import.spec.ts index 25bcae50b1..3b90440eaf 100644 --- a/frontend/cypress/integration/import/import.spec.ts +++ b/frontend/cypress/integration/import/import.spec.ts @@ -120,8 +120,8 @@ describe('imports', () => { cy.get( "[data-testid='feature-toggle-status'] input[type='checkbox']:checked" ) - .closest('div') - .contains('development'); + .invoke('attr', 'aria-label') + .should('eq', 'development'); cy.contains('50%'); }); }); diff --git a/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx index dfa54a6c27..affef02b31 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx @@ -86,8 +86,6 @@ export const StrategyTooltipLink: FC = ({ {previousTitle} - PREVIOUS consectetur adipiscing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. {' '} } @@ -103,9 +101,6 @@ export const StrategyTooltipLink: FC = ({ {change.payload.title || formatStrategyName(change.payload.name)} - lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore - magna aliqua. diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.test.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.test.tsx new file mode 100644 index 0000000000..5bdb5900f3 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.test.tsx @@ -0,0 +1,55 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { testServerRoute, testServerSetup } from 'utils/testServer'; +import { DisableEnableStrategyDialog } from './DisableEnableStrategyDialog'; + +const server = testServerSetup(); + +const defaultProps = { + isOpen: true, + onClose: () => {}, + onConfirm: () => {}, + projectId: 'project1', + featureId: 'feature1', + environmentId: 'env1', + strategy: { + id: 'some-id', + name: 'flexibleRollout', + constraints: [], + parameters: { + rollout: '50%', + stickiness: 'default', + }, + }, +}; + +test('should render disable dialog in regular mode', async () => { + testServerRoute(server, '/api/admin/ui-config', {}); + testServerRoute(server, '/api/admin/projects/project1', {}); + + render(); + + expect( + screen.queryByText('Change requests are enabled for this environment.') + ).not.toBeInTheDocument(); + expect( + screen.getByText('Are you sure you want to disable this strategy?') + ).toBeInTheDocument(); +}); + +test('should render enable dialog in regular mode', async () => { + testServerRoute(server, '/api/admin/ui-config', {}); + testServerRoute(server, '/api/admin/projects/project1', {}); + + const props = { + ...defaultProps, + strategy: { ...defaultProps.strategy, disabled: true }, + }; + + render(); + + expect( + screen.queryByText('Change requests are enabled for this environment.') + ).not.toBeInTheDocument(); + expect(screen.getByText('Enable strategy')).toBeInTheDocument(); +}); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx index 5d263342aa..c667bd21e4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx @@ -18,7 +18,9 @@ export const DisableEnableStrategyDialog = ({ const { projectId, environmentId } = props; const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const isChangeRequest = isChangeRequestConfigured(environmentId); - const { onSuggestEnable } = useSuggestEnableDisable({ ...props }); + const { onSuggestEnable, onSuggestDisable } = useSuggestEnableDisable({ + ...props, + }); const { onEnable, onDisable } = useEnableDisable({ ...props }); const disabled = Boolean(props.strategy?.disabled); @@ -28,7 +30,7 @@ export const DisableEnableStrategyDialog = ({ if (disabled) { onSuggestEnable(); } else { - onSuggestEnable(); + onSuggestDisable(); } } else { if (disabled) { @@ -70,8 +72,8 @@ export const DisableEnableStrategyDialog = ({ } elseShow={ - Enabling the strategy will change which users receive - access to the feature. + {disabled ? 'Enabling' : 'Disabling'} the strategy will + change which users receive access to the feature. } /> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/MenuStrategyRemove.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/MenuStrategyRemove.tsx index 5583f90571..8790e9716b 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/MenuStrategyRemove.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/MenuStrategyRemove.tsx @@ -20,7 +20,10 @@ import { UPDATE_FEATURE_STRATEGY, } from '@server/types/permissions'; import { useHasProjectEnvironmentAccess } from 'hooks/useHasAccess'; -import { STRATEGY_FORM_REMOVE_ID } from 'utils/testIds'; +import { + STRATEGY_FORM_REMOVE_ID, + STRATEGY_REMOVE_MENU_BTN, +} from 'utils/testIds'; export interface IRemoveStrategyMenuProps { projectId: string; @@ -74,6 +77,7 @@ const MenuStrategyRemove = ({ aria-controls={open ? 'actions-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} + data-testid={STRATEGY_REMOVE_MENU_BTN} > 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 a7d740c8b5..9e3d0b752d 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 @@ -1,21 +1,10 @@ -import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors'; -import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import useToast from 'hooks/useToast'; import { IFeatureEnvironment } from 'interfaces/featureToggle'; -import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch'; -import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions'; -import { formatUnknownError } from 'utils/formatUnknownError'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; -import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; -import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { styled } from '@mui/material'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider'; -import { useState } from 'react'; -import { EnableEnvironmentDialog } from './EnableEnvironmentDialog'; +import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch'; const StyledContainer = styled('div')(({ theme }) => ({ marginLeft: theme.spacing(-1.5), @@ -53,98 +42,7 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); - const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = - useFeatureApi(); const { feature, refetchFeature } = useFeature(projectId, featureId); - const { setToastData, setToastApiError } = useToast(); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - const { - onChangeRequestToggle, - onChangeRequestToggleClose, - onChangeRequestToggleConfirm, - changeRequestDialogDetails, - } = useChangeRequestToggle(projectId); - - const [showEnabledDialog, setShowEnabledDialog] = useState(false); - const disabledStrategiesCount = environment.strategies.filter( - strategy => strategy.disabled - ).length; - const handleToggleEnvironmentOn = async ( - shouldActivateDisabled = false - ) => { - try { - await toggleFeatureEnvironmentOn( - projectId, - featureId, - name, - shouldActivateDisabled - ); - setToastData({ - type: 'success', - title: `Available in ${name}`, - text: `${featureId} is now available in ${name} based on its defined strategies.`, - }); - refetchFeature(); - if (callback) { - callback(); - } - } catch (error: unknown) { - if ( - error instanceof Error && - error.message === ENVIRONMENT_STRATEGY_ERROR - ) { - showInfoBox(); - } else { - setToastApiError(formatUnknownError(error)); - } - } - }; - - const handleToggleEnvironmentOff = async () => { - try { - await toggleFeatureEnvironmentOff(projectId, featureId, name); - setToastData({ - type: 'success', - title: `Unavailable in ${name}`, - text: `${featureId} is unavailable in ${name} and its strategies will no longer have any effect.`, - }); - refetchFeature(); - if (callback) { - callback(); - } - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - }; - - const toggleEnvironment = async (e: React.ChangeEvent) => { - if (isChangeRequestConfigured(name)) { - e.preventDefault(); - onChangeRequestToggle(featureId, name, !enabled); - return; - } - if (enabled) { - await handleToggleEnvironmentOff(); - return; - } - - if (featureHasOnlyDisabledStrategies()) { - setShowEnabledDialog(true); - } else { - await handleToggleEnvironmentOn(); - } - }; - - const featureHasOnlyDisabledStrategies = () => { - const featureEnvironment = feature?.environments?.find( - env => env.name === name - ); - return ( - featureEnvironment?.strategies && - featureEnvironment?.strategies?.length > 0 && - featureEnvironment?.strategies?.every(strategy => strategy.disabled) - ); - }; const defaultContent = ( <> @@ -155,30 +53,21 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ ); - const onActivateStrategies = async () => { - await handleToggleEnvironmentOn(true); - setShowEnabledDialog(false); - }; - - const onAddDefaultStrategy = async () => { - await handleToggleEnvironmentOn(); - setShowEnabledDialog(false); + const handleToggle = () => { + refetchFeature(); + if (callback) callback(); }; return ( - {children ?? defaultContent} @@ -187,27 +76,6 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({ hiddenEnvironments={hiddenEnvironments} setHiddenEnvironments={setHiddenEnvironments} /> - - } - /> - setShowEnabledDialog(false)} - environment={name} - disabledStrategiesCount={disabledStrategiesCount} - onActivateDisabledStrategies={onActivateStrategies} - onAddDefaultStrategy={onAddDefaultStrategy} - /> ); }; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx index 157d7f5039..12d85cee74 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx @@ -1,9 +1,21 @@ -import { VFC } from 'react'; +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'; +import useUiConfig from '../../../../../hooks/api/getters/useUiConfig/useUiConfig'; +import { usePlausibleTracker } from '../../../../../hooks/usePlausibleTracker'; const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ mx: 'auto', @@ -11,51 +23,220 @@ const StyledBoxContainer = styled(Box)<{ 'data-testid': string }>(() => ({ })); interface IFeatureToggleSwitchProps { - featureName: string; + featureId: string; environmentName: string; projectId: string; value: boolean; - onToggle: ( + onError?: () => void; + onToggle?: ( projectId: string, feature: string, env: string, state: boolean - ) => Promise; + ) => void; } export const FeatureToggleSwitch: VFC = ({ projectId, - featureName, + featureId, environmentName, value, onToggle, + onError, }) => { + 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 onClick = () => { - setIsChecked(!isChecked); - onToggle(projectId, featureName, environmentName, !isChecked).catch( - rollbackIsChecked + 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 { uiConfig } = useUiConfig(); + const showStrategyImprovements = Boolean( + uiConfig.flags.strategyImprovements + ); + + const { trackEvent } = usePlausibleTracker(); + + 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.`, + }); + onToggle?.(projectId, feature.name, environmentName, !isChecked); + } catch (error: unknown) { + if ( + error instanceof Error && + error.message === ENVIRONMENT_STRATEGY_ERROR + ) { + onError?.(); + } 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.`, + }); + onToggle?.(projectId, feature.name, environmentName, !isChecked); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); + rollbackIsChecked(); + } + }; + + const onClick = async (e: React.MouseEvent) => { + if (isChangeRequestConfigured(environmentName)) { + e.preventDefault(); + if ( + featureHasOnlyDisabledStrategies() && + showStrategyImprovements + ) { + setShowEnabledDialog(true); + } else { + onChangeRequestToggle( + feature.name, + environmentName, + !value, + false + ); + } + return; + } + if (value) { + await handleToggleEnvironmentOff(); + return; + } + + if (featureHasOnlyDisabledStrategies() && showStrategyImprovements) { + setShowEnabledDialog(true); + } else { + await handleToggleEnvironmentOn(); + } + }; + + const onActivateStrategies = async () => { + await trackEvent('strategyImprovements', { + props: { + eventType: 'activate disabled strategies', + }, + }); + if (isChangeRequestConfigured(environmentName)) { + onChangeRequestToggle(feature.name, environmentName, !value, true); + } else { + await handleToggleEnvironmentOn(true); + } + setShowEnabledDialog(false); + }; + + const onAddDefaultStrategy = async () => { + await trackEvent('strategyImprovements', { + props: { + eventType: 'add default strategy', + }, + }); + 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 = `${featureName}-${environmentName}`; + const key = `${feature.name}-${environmentName}`; return ( - - + + + + {showStrategyImprovements && ( + 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 39fb1741e1..ac62ef21f6 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -12,8 +12,8 @@ import { useNavigate, useSearchParams } from 'react-router-dom'; import { SortingRule, useFlexLayout, - useSortBy, useRowSelect, + useSortBy, useTable, } from 'react-table'; import type { FeatureSchema } from 'openapi'; @@ -28,14 +28,11 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell'; -import { formatUnknownError } from 'utils/formatUnknownError'; import { IProject } from 'interfaces/project'; import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import useProject from 'hooks/api/getters/useProject/useProject'; import { createLocalStorage } from 'utils/createLocalStorage'; -import useToast from 'hooks/useToast'; -import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors'; import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog'; import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog'; import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; @@ -44,7 +41,6 @@ import { Search } from 'component/common/Search/Search'; import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { UpdateEnabledMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/UpdateEnabledMessage'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { IFeatureToggleListItem } from 'interfaces/featureToggle'; import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; @@ -52,7 +48,6 @@ import { ProjectEnvironmentType, useEnvironmentsRef, } from './hooks/useEnvironmentsRef'; -import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import { FeatureToggleSwitch } from './FeatureToggleSwitch/FeatureToggleSwitch'; import { ActionsCell } from './ActionsCell/ActionsCell'; import { ColumnsMenu } from './ColumnsMenu/ColumnsMenu'; @@ -146,25 +141,20 @@ export const ProjectFeatureToggles = ({ useGlobalLocalStorage(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const environments = useEnvironmentsRef( loading ? [{ environment: 'a' }, { environment: 'b' }, { environment: 'c' }] : newEnvironments ); const { refetch } = useProject(projectId); - const { setToastData, setToastApiError } = useToast(); const { isFavoritesPinned, sortTypes, onChangeIsFavoritePinned } = usePinnedFavorites( searchParams.has('favorites') ? searchParams.get('favorites') === 'true' : globalStore.favorites ); - const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } = - useFeatureApi(); const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { - onChangeRequestToggle, onChangeRequestToggleClose, onChangeRequestToggleConfirm, changeRequestDialogDetails, @@ -172,60 +162,6 @@ export const ProjectFeatureToggles = ({ const [showExportDialog, setShowExportDialog] = useState(false); const { uiConfig } = useUiConfig(); - const onToggle = useCallback( - async ( - projectId: string, - featureName: string, - environment: string, - enabled: boolean - ) => { - if (isChangeRequestConfigured(environment)) { - onChangeRequestToggle(featureName, environment, enabled); - throw new Error('Additional approval required'); - } - try { - if (enabled) { - await toggleFeatureEnvironmentOn( - projectId, - featureName, - environment - ); - } else { - await toggleFeatureEnvironmentOff( - projectId, - featureName, - environment - ); - } - refetch(); - } catch (error) { - const message = formatUnknownError(error); - if (message === ENVIRONMENT_STRATEGY_ERROR) { - setStrategiesDialogState({ - open: true, - featureId: featureName, - environmentName: environment, - }); - } else { - setToastApiError(message); - } - throw error; // caught when reverting optimistic update - } - - setToastData({ - type: 'success', - title: 'Updated toggle status', - text: 'Successfully updated toggle status.', - }); - refetch(); - }, - [ - toggleFeatureEnvironmentOff, - toggleFeatureEnvironmentOn, - isChangeRequestConfigured, - ] - ); - const onFavorite = useCallback( async (feature: IFeatureToggleListItem) => { if (feature?.favorite) { @@ -355,9 +291,8 @@ export const ProjectFeatureToggles = ({ { +const EditDefaultStrategy = () => { const projectId = useRequiredPathParam('projectId'); const environmentId = useRequiredQueryParam('environmentId'); - const { refetch: refetchProject } = useProject(projectId); + const { project, refetch: refetchProject } = useProject(projectId); - const [defaultStrategy, setDefaultStrategy] = - useState(strategy); + const strategy = project.environments.find( + env => env.environment === environmentId + )?.defaultStrategy; + + const [defaultStrategy, setDefaultStrategy] = useState< + CreateFeatureStrategySchema | undefined + >(strategy); const [segments, setSegments] = useState([]); const { updateDefaultStrategy, loading } = useProjectApi(); - const { strategyDefinition } = useStrategy(strategy.name); + const { strategyDefinition } = useStrategy(strategy?.name); const { setToastData, setToastApiError } = useToast(); const errors = useFormErrors(); const { uiConfig } = useUiConfig(); @@ -60,7 +61,7 @@ const EditDefaultStrategy = ({ strategy }: EditDefaultStrategyProps) => { } setSegments(temp); } - }, [JSON.stringify(allSegments), JSON.stringify(strategy.segments)]); + }, [JSON.stringify(allSegments), JSON.stringify(strategy?.segments)]); const segmentsToSubmit = uiConfig?.flags.SE ? segments : []; const payload = createStrategyPayload( @@ -108,7 +109,7 @@ const EditDefaultStrategy = ({ strategy }: EditDefaultStrategyProps) => { return ( @@ -92,7 +92,7 @@ const ProjectEnvironmentDefaultStrategy = ({ onClose={onSidebarClose} open > - + } /> diff --git a/frontend/src/hooks/useChangeRequestToggle.ts b/frontend/src/hooks/useChangeRequestToggle.ts index 863fbf3a08..0e717fca6d 100644 --- a/frontend/src/hooks/useChangeRequestToggle.ts +++ b/frontend/src/hooks/useChangeRequestToggle.ts @@ -13,17 +13,24 @@ export const useChangeRequestToggle = (project: string) => { const [changeRequestDialogDetails, setChangeRequestDialogDetails] = useState<{ enabled?: boolean; + shouldActivateDisabledStrategies?: boolean; featureName?: string; environment?: string; isOpen: boolean; }>({ isOpen: false }); const onChangeRequestToggle = useCallback( - (featureName: string, environment: string, enabled: boolean) => { + ( + featureName: string, + environment: string, + enabled: boolean, + shouldActivateDisabledStrategies: boolean + ) => { setChangeRequestDialogDetails({ featureName, environment, enabled, + shouldActivateDisabledStrategies, isOpen: true, }); }, @@ -41,6 +48,9 @@ export const useChangeRequestToggle = (project: string) => { action: 'updateEnabled', payload: { enabled: Boolean(changeRequestDialogDetails.enabled), + shouldActivateDisabledStrategies: Boolean( + changeRequestDialogDetails.shouldActivateDisabledStrategies + ), }, }); refetchChangeRequests(); diff --git a/frontend/src/hooks/usePlausibleTracker.ts b/frontend/src/hooks/usePlausibleTracker.ts index c7e9562146..897018d86f 100644 --- a/frontend/src/hooks/usePlausibleTracker.ts +++ b/frontend/src/hooks/usePlausibleTracker.ts @@ -25,6 +25,7 @@ export type CustomEvents = | 'notifications' | 'batch_operations' | 'strategyTitle' + | 'strategyImprovements' | 'default_strategy' | 'demo' | 'demo-start' diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts index 9fef658b39..555de47a1d 100644 --- a/frontend/src/utils/testIds.ts +++ b/frontend/src/utils/testIds.ts @@ -63,6 +63,7 @@ export const ADD_TO_STRATEGY_INPUT_LIST = 'ADD_TO_STRATEGY_INPUT_LIST'; export const STRATEGY_FORM_SUBMIT_ID = 'STRATEGY_FORM_SUBMIT_ID'; export const STRATEGY_FORM_REMOVE_ID = 'STRATEGY_FORM_REMOVE_ID'; export const STRATEGY_FORM_COPY_ID = 'STRATEGY_FORM_COPY_ID'; +export const STRATEGY_REMOVE_MENU_BTN = 'STRATEGY_REMOVE_MENU_BTN'; /* SPLASH */ export const CLOSE_SPLASH = 'CLOSE_SPLASH';