diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx index df51797341..dba386ff22 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx @@ -12,7 +12,7 @@ import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { CopyStrategiesMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { FeatureStrategyMenu } from '../FeatureStrategyMenu/FeatureStrategyMenu'; +import { FeatureStrategyMenuWrapper } from '../FeatureStrategyMenu/FeatureStrategyMenu'; interface IFeatureStrategyEmptyProps { projectId: string; @@ -161,7 +161,7 @@ export const FeatureStrategyEmpty = ({ justifyContent: 'center', }} > - ({ paddingBlock: 0, })); +export const FeatureStrategyMenuWrapper = ( + props: IFeatureStrategyMenuProps, +) => { + const newStrategyDropdownEnabled = useUiFlag('newStrategyDropdown'); + + if (newStrategyDropdownEnabled) { + return ; + } + + return ; +}; + export const FeatureStrategyMenu = ({ label, projectId, @@ -77,7 +88,6 @@ export const FeatureStrategyMenu = ({ const { addReleasePlanToFeature } = useReleasePlansApi(); const { isOss } = useUiConfig(); const releasePlansEnabled = useUiFlag('releasePlans'); - const newStrategyDropdownEnabled = useUiFlag('newStrategyDropdown'); const displayReleasePlanButton = !isOss() && releasePlansEnabled; const crProtected = releasePlansEnabled && isChangeRequestConfigured(environmentId); @@ -211,22 +221,17 @@ export const FeatureStrategyMenu = ({ > - '12px', }, }} > - {newStrategyDropdownEnabled ? ( + { - ) : ( - { - setSelectedTemplate(template); - setAddReleasePlanOpen(true); - }} - /> - )} - - {selectedTemplate && - (newStrategyDropdownEnabled ? ( - { - addReleasePlan(selectedTemplate); - }} - template={selectedTemplate} - projectId={projectId} - featureName={featureId} - environment={environmentId} - crProtected={crProtected} - /> - ) : ( - { - addReleasePlan(selectedTemplate); - }} - template={selectedTemplate} - projectId={projectId} - featureName={featureId} - environment={environmentId} - /> - ))} + } + + {selectedTemplate && ( + { + addReleasePlan(selectedTemplate); + }} + template={selectedTemplate} + projectId={projectId} + featureName={featureId} + environment={environmentId} + crProtected={crProtected} + /> + )} ); }; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx index 283125e4bc..f43646a238 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx @@ -29,7 +29,7 @@ interface IFeatureStrategyMenuCardsProps { const StyledTypography = styled(Typography)(({ theme }) => ({ fontSize: theme.fontSizes.smallBody, - padding: theme.spacing(1, 2), + padding: theme.spacing(1, 4), width: '100%', })); @@ -48,14 +48,14 @@ const ScrollableContent = styled(Box)(({ theme }) => ({ width: '100%', maxHeight: '70vh', overflowY: 'auto', - padding: theme.spacing(0, 0, 1, 0), + padding: theme.spacing(1, 0, 1, 0), })); const GridSection = styled(Box)(({ theme }) => ({ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: theme.spacing(1.5), - padding: theme.spacing(0, 2), + padding: theme.spacing(0, 4), marginBottom: theme.spacing(3), width: '100%', })); @@ -69,7 +69,7 @@ const TitleRow = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', - padding: theme.spacing(1, 2), + padding: theme.spacing(4, 4, 2, 4), })); const TitleText = styled(Typography)(({ theme }) => ({ @@ -82,7 +82,7 @@ const SectionTitle = styled(Box)(({ theme }) => ({ display: 'flex', alignItems: 'center', gap: theme.spacing(0.5), - padding: theme.spacing(1, 2), + padding: theme.spacing(0, 4, 1, 4), width: '100%', })); @@ -111,7 +111,7 @@ const EmptyStateContainer = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.neutral.light, borderRadius: theme.shape.borderRadiusMedium, padding: theme.spacing(3), - margin: theme.spacing(0, 2), + margin: theme.spacing(0, 4), width: 'auto', })); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/OldFeatureStrategyMenu.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/OldFeatureStrategyMenu.tsx new file mode 100644 index 0000000000..1c0ae12b0a --- /dev/null +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/OldFeatureStrategyMenu.tsx @@ -0,0 +1,254 @@ +import type React from 'react'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import PermissionButton, { + type IPermissionButtonProps, +} from 'component/common/PermissionButton/PermissionButton'; +import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; +import { Popover, styled } from '@mui/material'; +import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate'; +import MoreVert from '@mui/icons-material/MoreVert'; +import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; +import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; +import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; +import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; +import useToast from 'hooks/useToast'; +import { ReleasePlanAddDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddDialog'; +import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; +import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; +import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import { useUiFlag } from 'hooks/useUiFlag'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { OldFeatureStrategyMenuCards } from './FeatureStrategyMenuCards/OldFeatureStrategyMenuCards'; + +interface IFeatureStrategyMenuProps { + label: string; + projectId: string; + featureId: string; + environmentId: string; + variant?: IPermissionButtonProps['variant']; + matchWidth?: boolean; + size?: IPermissionButtonProps['size']; + disableReason?: string; +} + +const StyledStrategyMenu = styled('div')(({ theme }) => ({ + display: 'flex', + flexFlow: 'row', + justifyContent: 'flex-end', + gap: theme.spacing(1), +})); + +const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({ + minWidth: 0, + width: theme.spacing(4.5), + alignSelf: 'stretch', + paddingBlock: 0, +})); + +export const OldFeatureStrategyMenu = ({ + label, + projectId, + featureId, + environmentId, + variant, + size, + matchWidth, + disableReason, +}: IFeatureStrategyMenuProps) => { + const [anchor, setAnchor] = useState(); + const [onlyReleasePlans, setOnlyReleasePlans] = useState(false); + const navigate = useNavigate(); + const { trackEvent } = usePlausibleTracker(); + const [selectedTemplate, setSelectedTemplate] = + useState(); + const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false); + const isPopoverOpen = Boolean(anchor); + const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined; + const { setToastApiError, setToastData } = useToast(); + const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); + const { addChange } = useChangeRequestApi(); + const { refetch: refetchChangeRequests } = + usePendingChangeRequests(projectId); + const { refetch } = useReleasePlans(projectId, featureId, environmentId); + const { addReleasePlanToFeature } = useReleasePlansApi(); + const { isOss } = useUiConfig(); + const releasePlansEnabled = useUiFlag('releasePlans'); + const displayReleasePlanButton = !isOss() && releasePlansEnabled; + const crProtected = + releasePlansEnabled && isChangeRequestConfigured(environmentId); + + const onClose = () => { + setAnchor(undefined); + }; + + const openDefaultStrategyCreationModal = (event: React.SyntheticEvent) => { + trackEvent('strategy-add', { + props: { + buttonTitle: label, + }, + }); + navigate(createStrategyPath); + }; + + const openMoreStrategies = (event: React.SyntheticEvent) => { + setOnlyReleasePlans(false); + setAnchor(event.currentTarget); + }; + + const openReleasePlans = (event: React.SyntheticEvent) => { + setOnlyReleasePlans(true); + setAnchor(event.currentTarget); + }; + + const addReleasePlan = async () => { + if (!selectedTemplate) return; + + try { + if (crProtected) { + await addChange(projectId, environmentId, { + feature: featureId, + action: 'addReleasePlan', + payload: { + templateId: selectedTemplate.id, + }, + }); + + setToastData({ + type: 'success', + text: 'Added to draft', + }); + + refetchChangeRequests(); + } else { + await addReleasePlanToFeature( + featureId, + selectedTemplate.id, + projectId, + environmentId, + ); + + setToastData({ + type: 'success', + text: 'Release plan added', + }); + + refetch(); + } + trackEvent('release-management', { + props: { + eventType: 'add-plan', + plan: selectedTemplate.name, + }, + }); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); + } finally { + setAddReleasePlanOpen(false); + setSelectedTemplate(undefined); + } + }; + + const createStrategyPath = formatCreateStrategyPath( + projectId, + featureId, + environmentId, + 'flexibleRollout', + true, + ); + + return ( + event.stopPropagation()}> + {displayReleasePlanButton ? ( + + Use template + + ) : null} + + + {label} + + + + + + ({ + paddingBottom: theme.spacing(1), + }), + }} + > + { + setSelectedTemplate(template); + setAddReleasePlanOpen(true); + }} + /> + + {selectedTemplate && ( + + )} + + ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index dea0b04ed8..189f042eb0 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -3,7 +3,7 @@ import type { IFeatureEnvironment, IFeatureEnvironmentMetrics, } from 'interfaces/featureToggle'; -import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; +import { FeatureStrategyMenuWrapper } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { UpgradeChangeRequests } from './UpgradeChangeRequests/UpgradeChangeRequests'; @@ -103,7 +103,7 @@ export const FeatureOverviewEnvironment = ({ environment={environment} /> {!hasActivations ? ( - - - -