mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-06 01:15:28 +02:00
feat: selector dropdown for milestone new strategy (#8841)
This commit is contained in:
parent
c85c877c93
commit
f985cb1deb
@ -1,8 +1,12 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { Box, Button, Card, Grid, styled } from '@mui/material';
|
import { Box, Button, Card, Grid, Popover, styled } from '@mui/material';
|
||||||
import Edit from '@mui/icons-material/Edit';
|
import Edit from '@mui/icons-material/Edit';
|
||||||
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
import type {
|
||||||
|
IReleasePlanMilestonePayload,
|
||||||
|
IReleasePlanMilestoneStrategy,
|
||||||
|
} from 'interfaces/releasePlans';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenuCards';
|
||||||
|
|
||||||
const StyledEditIcon = styled(Edit)(({ theme }) => ({
|
const StyledEditIcon = styled(Edit)(({ theme }) => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@ -53,7 +57,10 @@ interface IMilestoneCardProps {
|
|||||||
index: number;
|
index: number;
|
||||||
milestone: IReleasePlanMilestonePayload;
|
milestone: IReleasePlanMilestonePayload;
|
||||||
milestoneNameChanged: (index: number, name: string) => void;
|
milestoneNameChanged: (index: number, name: string) => void;
|
||||||
showAddStrategyDialog: (index: number) => void;
|
showAddStrategyDialog: (
|
||||||
|
index: number,
|
||||||
|
strategy: IReleasePlanMilestoneStrategy,
|
||||||
|
) => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
}
|
}
|
||||||
@ -67,6 +74,22 @@ export const MilestoneCard = ({
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
}: IMilestoneCardProps) => {
|
}: IMilestoneCardProps) => {
|
||||||
const [editMode, setEditMode] = useState(false);
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [anchor, setAnchor] = useState<Element>();
|
||||||
|
const isPopoverOpen = Boolean(anchor);
|
||||||
|
const popoverId = isPopoverOpen
|
||||||
|
? 'MilestoneStrategyMenuPopover'
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
setAnchor(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectStrategy = (
|
||||||
|
milestoneId: string,
|
||||||
|
strategy: IReleasePlanMilestoneStrategy,
|
||||||
|
) => {
|
||||||
|
showAddStrategyDialog(index, strategy);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMilestoneCard>
|
<StyledMilestoneCard>
|
||||||
@ -111,10 +134,27 @@ export const MilestoneCard = ({
|
|||||||
<Button
|
<Button
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
color='primary'
|
color='primary'
|
||||||
onClick={() => showAddStrategyDialog(index)}
|
onClick={(ev) => setAnchor(ev.currentTarget)}
|
||||||
>
|
>
|
||||||
Add strategy
|
Add strategy
|
||||||
</Button>
|
</Button>
|
||||||
|
<Popover
|
||||||
|
id={popoverId}
|
||||||
|
open={isPopoverOpen}
|
||||||
|
anchorEl={anchor}
|
||||||
|
onClose={onClose}
|
||||||
|
onClick={onClose}
|
||||||
|
PaperProps={{
|
||||||
|
sx: (theme) => ({
|
||||||
|
paddingBottom: theme.spacing(1),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MilestoneStrategyMenuCards
|
||||||
|
milestoneId={milestone.id ?? index.toString()}
|
||||||
|
openAddStrategy={onSelectStrategy}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StyledMilestoneCardBody>
|
</StyledMilestoneCardBody>
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
import type {
|
||||||
|
IReleasePlanMilestonePayload,
|
||||||
|
IReleasePlanMilestoneStrategy,
|
||||||
|
} from 'interfaces/releasePlans';
|
||||||
import { MilestoneCard } from './MilestoneCard';
|
import { MilestoneCard } from './MilestoneCard';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
@ -9,7 +12,10 @@ interface IMilestoneListProps {
|
|||||||
setMilestones: React.Dispatch<
|
setMilestones: React.Dispatch<
|
||||||
React.SetStateAction<IReleasePlanMilestonePayload[]>
|
React.SetStateAction<IReleasePlanMilestonePayload[]>
|
||||||
>;
|
>;
|
||||||
setAddStrategyOpen: (open: boolean) => void;
|
openAddStrategyForm: (
|
||||||
|
index: number,
|
||||||
|
strategy: IReleasePlanMilestoneStrategy,
|
||||||
|
) => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
}
|
}
|
||||||
@ -22,14 +28,10 @@ const StyledAddMilestoneButton = styled(Button)(({ theme }) => ({
|
|||||||
export const MilestoneList = ({
|
export const MilestoneList = ({
|
||||||
milestones,
|
milestones,
|
||||||
setMilestones,
|
setMilestones,
|
||||||
setAddStrategyOpen,
|
openAddStrategyForm,
|
||||||
errors,
|
errors,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}: IMilestoneListProps) => {
|
}: IMilestoneListProps) => {
|
||||||
const showAddStrategyDialog = (index: number) => {
|
|
||||||
setAddStrategyOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const milestoneNameChanged = (index: number, name: string) => {
|
const milestoneNameChanged = (index: number, name: string) => {
|
||||||
setMilestones((prev) =>
|
setMilestones((prev) =>
|
||||||
prev.map((milestone, i) =>
|
prev.map((milestone, i) =>
|
||||||
@ -46,7 +48,7 @@ export const MilestoneList = ({
|
|||||||
index={index}
|
index={index}
|
||||||
milestone={milestone}
|
milestone={milestone}
|
||||||
milestoneNameChanged={milestoneNameChanged}
|
milestoneNameChanged={milestoneNameChanged}
|
||||||
showAddStrategyDialog={showAddStrategyDialog}
|
showAddStrategyDialog={openAddStrategyForm}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
formatStrategyName,
|
||||||
|
getFeatureStrategyIcon,
|
||||||
|
} from 'utils/strategyNames';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { IStrategy } from 'interfaces/strategy';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { createFeatureStrategy } from 'utils/createFeatureStrategy';
|
||||||
|
|
||||||
|
const StyledIcon = styled('div')(({ theme }) => ({
|
||||||
|
width: theme.spacing(4),
|
||||||
|
height: 'auto',
|
||||||
|
'& > svg': {
|
||||||
|
// Styling for SVG icons.
|
||||||
|
fill: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
'& > div': {
|
||||||
|
// Styling for the Rollout icon.
|
||||||
|
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('div')(({ 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 IMilestoneStrategyMenuCardProps {
|
||||||
|
strategy: IStrategy;
|
||||||
|
onClick: (strategy: IReleasePlanMilestoneStrategy) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MilestoneStrategyMenuCard = ({
|
||||||
|
strategy,
|
||||||
|
onClick,
|
||||||
|
}: IMilestoneStrategyMenuCardProps) => {
|
||||||
|
const StrategyIcon = getFeatureStrategyIcon(strategy.name);
|
||||||
|
const strategyName = formatStrategyName(strategy.name);
|
||||||
|
return (
|
||||||
|
<StyledCard
|
||||||
|
onClick={() => {
|
||||||
|
const strat = createFeatureStrategy('', strategy);
|
||||||
|
onClick({
|
||||||
|
id: uuidv4(),
|
||||||
|
name: strat.name,
|
||||||
|
title: '',
|
||||||
|
constraints: strat.constraints,
|
||||||
|
parameters: strat.parameters,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledIcon>
|
||||||
|
<StrategyIcon />
|
||||||
|
</StyledIcon>
|
||||||
|
<div>
|
||||||
|
<StyledName
|
||||||
|
text={strategy.displayName || strategyName}
|
||||||
|
maxWidth='200'
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
|
<StyledDescription>{strategy.description}</StyledDescription>
|
||||||
|
</div>
|
||||||
|
</StyledCard>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
import { List, ListItem, styled, Typography } from '@mui/material';
|
||||||
|
import { MilestoneStrategyMenuCard } from './MilestoneStrategyMenuCard';
|
||||||
|
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||||
|
import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans';
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
padding: theme.spacing(1, 2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IMilestoneStrategyMenuCardsProps {
|
||||||
|
milestoneId: string;
|
||||||
|
openAddStrategy: (
|
||||||
|
milestoneId: string,
|
||||||
|
strategy: IReleasePlanMilestoneStrategy,
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MilestoneStrategyMenuCards = ({
|
||||||
|
milestoneId,
|
||||||
|
openAddStrategy,
|
||||||
|
}: IMilestoneStrategyMenuCardsProps) => {
|
||||||
|
const { strategies } = useStrategies();
|
||||||
|
|
||||||
|
const preDefinedStrategies = strategies.filter(
|
||||||
|
(strategy) => !strategy.deprecated && !strategy.editable,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClick = (strategy: IReleasePlanMilestoneStrategy) => {
|
||||||
|
openAddStrategy(milestoneId, strategy);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List dense>
|
||||||
|
<>
|
||||||
|
<StyledTypography color='textSecondary'>
|
||||||
|
Predefined strategy types
|
||||||
|
</StyledTypography>
|
||||||
|
{preDefinedStrategies.map((strategy) => (
|
||||||
|
<ListItem key={strategy.name}>
|
||||||
|
<MilestoneStrategyMenuCard
|
||||||
|
strategy={strategy}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,10 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { MilestoneList } from './MilestoneList';
|
import { MilestoneList } from './MilestoneList';
|
||||||
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
import type {
|
||||||
|
IReleasePlanMilestonePayload,
|
||||||
|
IReleasePlanMilestoneStrategy,
|
||||||
|
} from 'interfaces/releasePlans';
|
||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import ReleaseTemplateIcon from '@mui/icons-material/DashboardOutlined';
|
import ReleaseTemplateIcon from '@mui/icons-material/DashboardOutlined';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
@ -56,6 +59,24 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [addStrategyOpen, setAddStrategyOpen] = useState(false);
|
const [addStrategyOpen, setAddStrategyOpen] = useState(false);
|
||||||
|
const [activeMilestoneIndex, setActiveMilestoneIndex] = useState<
|
||||||
|
number | undefined
|
||||||
|
>();
|
||||||
|
const [strategy, setStrategy] = useState<IReleasePlanMilestoneStrategy>({
|
||||||
|
name: 'flexibleRollout',
|
||||||
|
parameters: { rollout: '50' },
|
||||||
|
constraints: [],
|
||||||
|
title: '',
|
||||||
|
id: '',
|
||||||
|
});
|
||||||
|
const openAddStrategyForm = (
|
||||||
|
index: number,
|
||||||
|
strategy: IReleasePlanMilestoneStrategy,
|
||||||
|
) => {
|
||||||
|
setActiveMilestoneIndex(index);
|
||||||
|
setStrategy(strategy);
|
||||||
|
setAddStrategyOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
@ -90,7 +111,7 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
|||||||
<MilestoneList
|
<MilestoneList
|
||||||
milestones={milestones}
|
milestones={milestones}
|
||||||
setMilestones={setMilestones}
|
setMilestones={setMilestones}
|
||||||
setAddStrategyOpen={setAddStrategyOpen}
|
openAddStrategyForm={openAddStrategyForm}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
/>
|
/>
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import type { IFeatureVariant } from './featureToggle';
|
||||||
|
import type { IConstraint, IFeatureStrategyParameters } from './strategy';
|
||||||
|
|
||||||
export interface IReleasePlanTemplate {
|
export interface IReleasePlanTemplate {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -15,6 +18,16 @@ export interface IReleasePlanTemplate {
|
|||||||
milestones: IReleasePlanMilestonePayload[];
|
milestones: IReleasePlanMilestonePayload[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IReleasePlanMilestoneStrategy {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
constraints: IConstraint[];
|
||||||
|
parameters: IFeatureStrategyParameters;
|
||||||
|
variants?: IFeatureVariant[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IReleasePlanMilestone {
|
export interface IReleasePlanMilestone {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user