From 02ca60511f513630d013ac7d51f38d8cdd66cdff Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:26:07 +0200 Subject: [PATCH] Splitted strategy button (#4025) ## About the changes ![image](https://github.com/Unleash/unleash/assets/2625371/afaaaedf-4539-4a0b-a0fb-916d858ac6d3) https://linear.app/unleash/issue/1-1038/strategy-creation-split-into-two-buttons --- .../PermissionButton/PermissionButton.tsx | 4 +- .../FeatureStrategyEmpty.tsx | 141 +++++++++++------- .../FeatureStrategyMenu.tsx | 50 ++++++- .../LegacyFeatureStrategyMenu.tsx | 70 +++++++++ .../FeatureOverviewEnvironment.tsx | 88 ++++++++--- frontend/src/interfaces/uiConfig.ts | 1 + .../__snapshots__/create-config.test.ts.snap | 2 + src/lib/types/experimental.ts | 7 +- 8 files changed, 284 insertions(+), 79 deletions(-) create mode 100644 frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/LegacyFeatureStrategyMenu.tsx diff --git a/frontend/src/component/common/PermissionButton/PermissionButton.tsx b/frontend/src/component/common/PermissionButton/PermissionButton.tsx index e94e5cfedd..a2b2a4dd80 100644 --- a/frontend/src/component/common/PermissionButton/PermissionButton.tsx +++ b/frontend/src/component/common/PermissionButton/PermissionButton.tsx @@ -20,6 +20,7 @@ export interface IPermissionButtonProps extends Omit { projectId?: string; environmentId?: string; tooltipProps?: Omit; + hideLockIcon?: boolean; } interface IPermissionBaseButtonProps extends IPermissionButtonProps { @@ -68,6 +69,7 @@ const BasePermissionButton: React.FC = projectId, environmentId, tooltipProps, + hideLockIcon, ...rest }, ref @@ -92,7 +94,7 @@ const BasePermissionButton: React.FC = endIcon={ <> } elseShow={ Boolean(rest.endIcon) && diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx index 0c1869fcd1..98d438d112 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx @@ -15,6 +15,8 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { getFeatureStrategyIcon } from 'utils/strategyNames'; import { AddFromTemplateCard } from './AddFromTemplateCard/AddFromTemplateCard'; import { FeatureStrategyMenu } from '../FeatureStrategyMenu/FeatureStrategyMenu'; +import { LegacyFeatureStrategyMenu } from '../FeatureStrategyMenu/LegacyFeatureStrategyMenu'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; interface IFeatureStrategyEmptyProps { projectId: string; @@ -76,6 +78,9 @@ export const FeatureStrategyEmpty = ({ onChangeRequestAddStrategyClose, } = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy'); + const { uiConfig } = useUiConfig(); + const strategySplittedButton = uiConfig?.flags?.strategySplittedButton; + const onAfterAddStrategy = (multiple = false) => { refetchFeature(); refetchFeatureImmutable(); @@ -166,12 +171,26 @@ export const FeatureStrategyEmpty = ({ justifyContent: 'center', }} > - + } + elseShow={ + + } /> - - - Or use a strategy template - - - - - The standard strategy is strictly on/off for your entire - userbase. - - - Roll out to a percentage of your userbase. - - + + + + Or use a strategy template + + + + + The standard strategy is strictly on/off for + your entire userbase. + + + Roll out to a percentage of your userbase. + + + + } + /> ); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx index 66b4c2bb1f..2a763bc361 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx @@ -1,10 +1,13 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import PermissionButton, { IPermissionButtonProps, } from 'component/common/PermissionButton/PermissionButton'; import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; -import { Popover } from '@mui/material'; +import { Popover, styled } from '@mui/material'; import { FeatureStrategyMenuCards } from './FeatureStrategyMenuCards/FeatureStrategyMenuCards'; +import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate'; +import { MoreVert } from '@mui/icons-material'; interface IFeatureStrategyMenuProps { label: string; @@ -13,17 +16,30 @@ interface IFeatureStrategyMenuProps { environmentId: string; variant?: IPermissionButtonProps['variant']; matchWidth?: boolean; + size?: IPermissionButtonProps['size']; } +const StyledAdditionalMenuButton = styled(PermissionButton)(({ theme }) => ({ + minWidth: 0, + width: theme.spacing(4.5), + alignItems: 'center', + justifyContent: 'center', + align: 'center', + flexDirection: 'column', + marginLeft: theme.spacing(1), +})); + export const FeatureStrategyMenu = ({ label, projectId, featureId, environmentId, variant, + size, matchWidth, }: IFeatureStrategyMenuProps) => { const [anchor, setAnchor] = useState(); + const navigate = useNavigate(); const isPopoverOpen = Boolean(anchor); const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined; @@ -35,25 +51,55 @@ export const FeatureStrategyMenu = ({ setAnchor(event.currentTarget); }; + const createStrategyPath = formatCreateStrategyPath( + projectId, + featureId, + environmentId, + 'flexibleRollout', + true + ); + return (
event.stopPropagation()}> navigate(createStrategyPath)} aria-labelledby={popoverId} variant={variant} + size={size} sx={{ minWidth: matchWidth ? '282px' : 'auto' }} > {label} + + + ({ margin: theme.spacing(0.25, 0) })} /> + ({ + paddingBottom: theme.spacing(1), + }), + }} > { + const [anchor, setAnchor] = useState(); + const isPopoverOpen = Boolean(anchor); + const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined; + + const onClose = () => { + setAnchor(undefined); + }; + + const onClick = (event: React.SyntheticEvent) => { + setAnchor(event.currentTarget); + }; + + return ( +
event.stopPropagation()}> + + {label} + + + + +
+ ); +}; 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 272ebc3ee4..9327f44844 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -17,11 +17,13 @@ import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAcco import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter'; import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics'; import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; +import { LegacyFeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/LegacyFeatureStrategyMenu'; import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons'; import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage'; import { Badge } from 'component/common/Badge/Badge'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; interface IFeatureOverviewEnvironmentProps { env: IFeatureEnvironment; @@ -104,7 +106,10 @@ const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({ }, })); -const StyledContainer = styled('div')(({ theme }) => ({ +/** + * @deprecated + */ +const LegacyStyledButtonContainer = styled('div')(({ theme }) => ({ display: 'flex', alignItems: 'center', marginLeft: '1.8rem', @@ -114,6 +119,15 @@ const StyledContainer = styled('div')(({ theme }) => ({ }, })); +const StyledButtonContainer = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + marginTop: theme.spacing(2), + [theme.breakpoints.down(560)]: { + flexDirection: 'column', + }, +})); + const FeatureOverviewEnvironment = ({ env, }: IFeatureOverviewEnvironmentProps) => { @@ -122,6 +136,8 @@ const FeatureOverviewEnvironment = ({ const { metrics } = useFeatureMetrics(projectId, featureId); const { feature } = useFeature(projectId, featureId); const { value: globalStore } = useGlobalLocalStorage(); + const { uiConfig } = useUiConfig(); + const strategySplittedButton = uiConfig?.flags?.strategySplittedButton; const featureMetrics = getFeatureMetrics(feature?.environments, metrics); const environmentMetric = featureMetrics.find( @@ -171,20 +187,37 @@ const FeatureOverviewEnvironment = ({ } /> - - - - + + + + } + elseShow={ + + + + + } + /> - + } + elseShow={ + + } /> ; @@ -90,6 +91,10 @@ const flags: IFlags = { process.env.UNLEASH_STRATEGY_IMPROVEMENTS, false, ), + strategySplittedButton: parseEnvVarBoolean( + process.env.UNLEASH_STRATEGY_SPLITTED_BUTTON, + false, + ), googleAuthEnabled: parseEnvVarBoolean( process.env.GOOGLE_AUTH_ENABLED, false,