1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

chore: list release templates in strategy popover (#8703)

https://linear.app/unleash/issue/2-2817/expand-the-strategy-dropdown-with-release-plan-templates

Does what it says on the tin, lists release plan templates in the
strategy popover.

I think we should improve this popover soon, at least behind the
`flagOverviewRedesign` flag, but not in this PR.


![image](https://github.com/user-attachments/assets/6f91175f-f941-4566-b56b-24007d5077da)
This commit is contained in:
Nuno Góis 2024-11-08 14:26:01 +00:00 committed by GitHub
parent da805f2036
commit 044e61454b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 175 additions and 12 deletions

View File

@ -140,12 +140,9 @@ export const TOPICS: ITutorialTopic[] = [
), ),
}, },
{ {
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step2/strategies/create?environmentId=${ENVIRONMENT}&strategyName=default&defaultStrategy=false"]`, target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step2/strategies/create?environmentId=${ENVIRONMENT}&strategyName=flexibleRollout&defaultStrategy=true"]`,
content: ( content: (
<Description> <Description>Select the default strategy.</Description>
Select the <Badge as='span'>Standard</Badge> strategy
type.
</Description>
), ),
placement: 'right', placement: 'right',
optional: true, optional: true,
@ -480,6 +477,15 @@ export const TOPICS: ITutorialTopic[] = [
</Description> </Description>
), ),
}, },
{
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step4/strategies/create?environmentId=${ENVIRONMENT}&strategyName=flexibleRollout&defaultStrategy=true"]`,
content: (
<Description>Select the default strategy.</Description>
),
placement: 'right',
optional: true,
backCloseModal: true,
},
{ {
target: 'button[data-testid="STRATEGY_TARGETING_TAB"]', target: 'button[data-testid="STRATEGY_TARGETING_TAB"]',
content: ( content: (

View File

@ -0,0 +1,88 @@
import { getFeatureStrategyIcon } from 'utils/strategyNames';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { Link, styled } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
const StyledIcon = styled('div')(({ theme }) => ({
width: theme.spacing(4),
height: 'auto',
'& > svg': {
fill: theme.palette.primary.main,
},
'& > div': {
height: theme.spacing(2),
marginLeft: '-.75rem',
color: theme.palette.primary.main,
},
}));
const StyledDescription = styled('div')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
const StyledName = styled(StringTruncator)(({ theme }) => ({
fontWeight: theme.fontWeight.bold,
}));
const StyledCard = styled(Link)(({ theme }) => ({
display: 'grid',
gridTemplateColumns: '3rem 1fr',
width: '20rem',
padding: theme.spacing(2),
color: 'inherit',
textDecoration: 'inherit',
lineHeight: 1.25,
borderWidth: '1px',
borderStyle: 'solid',
borderColor: theme.palette.divider,
borderRadius: theme.spacing(1),
'&:hover, &:focus': {
borderColor: theme.palette.primary.main,
},
}));
interface IFeatureReleasePlanCardProps {
projectId: string;
featureId: string;
environmentId: string;
releasePlanTemplate: IReleasePlanTemplate;
}
export const FeatureReleasePlanCard = ({
projectId,
featureId,
environmentId,
releasePlanTemplate,
}: IFeatureReleasePlanCardProps) => {
const Icon = getFeatureStrategyIcon('releasePlanTemplate');
const { trackEvent } = usePlausibleTracker();
const addReleasePlan = () => {
trackEvent('release-plans', {
props: {
eventType: 'add',
name: releasePlanTemplate.name,
},
});
console.log('TODO: call and implement addReleasePlan');
};
return (
<StyledCard onClick={addReleasePlan}>
<StyledIcon>
<Icon />
</StyledIcon>
<div>
<StyledName
text={releasePlanTemplate.name}
maxWidth='200'
maxLength={25}
/>
<StyledDescription>
{releasePlanTemplate.description}
</StyledDescription>
</div>
</StyledCard>
);
};

View File

@ -10,6 +10,7 @@ import { FeatureStrategyMenuCards } from './FeatureStrategyMenuCards/FeatureStra
import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate'; import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate';
import MoreVert from '@mui/icons-material/MoreVert'; import MoreVert from '@mui/icons-material/MoreVert';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
interface IFeatureStrategyMenuProps { interface IFeatureStrategyMenuProps {
label: string; label: string;
@ -51,6 +52,7 @@ export const FeatureStrategyMenu = ({
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const isPopoverOpen = Boolean(anchor); const isPopoverOpen = Boolean(anchor);
const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined; const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
const flagOverviewRedesignEnabled = useUiFlag('flagOverviewRedesign');
const onClose = () => { const onClose = () => {
setAnchor(undefined); setAnchor(undefined);
@ -77,6 +79,48 @@ export const FeatureStrategyMenu = ({
true, true,
); );
if (flagOverviewRedesignEnabled) {
return (
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
<PermissionButton
data-testid='ADD_STRATEGY_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openMoreStrategies}
aria-labelledby={popoverId}
variant={variant}
size={size}
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
{label}
</PermissionButton>
<Popover
id={popoverId}
open={isPopoverOpen}
anchorEl={anchor}
onClose={onClose}
onClick={onClose}
PaperProps={{
sx: (theme) => ({
paddingBottom: theme.spacing(1),
}),
}}
>
<FeatureStrategyMenuCards
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
/>
</Popover>
</StyledStrategyMenu>
);
}
return ( return (
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}> <StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
<PermissionButton <PermissionButton

View File

@ -2,6 +2,8 @@ import { List, ListItem, styled, Typography } from '@mui/material';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import { FeatureStrategyMenuCard } from '../FeatureStrategyMenuCard/FeatureStrategyMenuCard'; import { FeatureStrategyMenuCard } from '../FeatureStrategyMenuCard/FeatureStrategyMenuCard';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
import { FeatureReleasePlanCard } from '../FeatureReleasePlanCard/FeatureReleasePlanCard';
interface IFeatureStrategyMenuCardsProps { interface IFeatureStrategyMenuCardsProps {
projectId: string; projectId: string;
@ -20,6 +22,7 @@ export const FeatureStrategyMenuCards = ({
environmentId, environmentId,
}: IFeatureStrategyMenuCardsProps) => { }: IFeatureStrategyMenuCardsProps) => {
const { strategies } = useStrategies(); const { strategies } = useStrategies();
const { templates } = useReleasePlanTemplates();
const preDefinedStrategies = strategies.filter( const preDefinedStrategies = strategies.filter(
(strategy) => !strategy.deprecated && !strategy.editable, (strategy) => !strategy.deprecated && !strategy.editable,
@ -39,7 +42,7 @@ export const FeatureStrategyMenuCards = ({
<List dense> <List dense>
<> <>
<StyledTypography color='textSecondary'> <StyledTypography color='textSecondary'>
{environmentId} environment default strategy Default strategy for {environmentId} environment
</StyledTypography> </StyledTypography>
<ListItem key={defaultStrategy.name}> <ListItem key={defaultStrategy.name}>
<FeatureStrategyMenuCard <FeatureStrategyMenuCard
@ -51,6 +54,26 @@ export const FeatureStrategyMenuCards = ({
/> />
</ListItem> </ListItem>
</> </>
<ConditionallyRender
condition={templates.length > 0}
show={
<>
<StyledTypography color='textSecondary'>
Release templates
</StyledTypography>
{templates.map((template) => (
<ListItem key={template.id}>
<FeatureReleasePlanCard
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
releasePlanTemplate={template}
/>
</ListItem>
))}
</>
}
/>
<StyledTypography color='textSecondary'> <StyledTypography color='textSecondary'>
Predefined strategy types Predefined strategy types
</StyledTypography> </StyledTypography>

View File

@ -1,6 +1,5 @@
import { useContext, useMemo } from 'react'; import { useMemo } from 'react';
import useUiConfig from '../useUiConfig/useUiConfig'; import useUiConfig from '../useUiConfig/useUiConfig';
import AccessContext from 'contexts/AccessContext';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
@ -12,12 +11,11 @@ const ENDPOINT = 'api/admin/release-plan-templates';
const DEFAULT_DATA: IReleasePlanTemplate[] = []; const DEFAULT_DATA: IReleasePlanTemplate[] = [];
export const useReleasePlanTemplates = () => { export const useReleasePlanTemplates = () => {
const { isAdmin } = useContext(AccessContext);
const { isEnterprise } = useUiConfig(); const { isEnterprise } = useUiConfig();
const signalsEnabled = useUiFlag('releasePlans'); const releasePlansEnabled = useUiFlag('releasePlans');
const { data, error, mutate } = useConditionalSWR<IReleasePlanTemplate[]>( const { data, error, mutate } = useConditionalSWR<IReleasePlanTemplate[]>(
isEnterprise() && isAdmin && signalsEnabled, isEnterprise() && releasePlansEnabled,
DEFAULT_DATA, DEFAULT_DATA,
formatApiPath(ENDPOINT), formatApiPath(ENDPOINT),
fetcher, fetcher,

View File

@ -73,7 +73,8 @@ export type CustomEvents =
| 'order-environments' | 'order-environments'
| 'unleash-ai-chat' | 'unleash-ai-chat'
| 'project-navigation' | 'project-navigation'
| 'productivity-report'; | 'productivity-report'
| 'release-plans';
export const usePlausibleTracker = () => { export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext); const plausible = useContext(PlausibleContext);

View File

@ -6,6 +6,7 @@ import LanguageIcon from '@mui/icons-material/Language';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import CodeIcon from '@mui/icons-material/Code'; import CodeIcon from '@mui/icons-material/Code';
import { ReactComponent as RolloutIcon } from 'assets/icons/rollout.svg'; import { ReactComponent as RolloutIcon } from 'assets/icons/rollout.svg';
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
export const formatStrategyName = (strategyName: string): string => { export const formatStrategyName = (strategyName: string): string => {
return formattedStrategyNames[strategyName] ?? strategyName; return formattedStrategyNames[strategyName] ?? strategyName;
@ -31,6 +32,8 @@ export const getFeatureStrategyIcon = (strategyName: string) => {
return PeopleIcon; return PeopleIcon;
case 'applicationHostname': case 'applicationHostname':
return LocationOnIcon; return LocationOnIcon;
case 'releasePlanTemplate':
return FormatListNumberedIcon;
default: default:
return CodeIcon; return CodeIcon;
} }