mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01: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 { 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 type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
||||
import type {
|
||||
IReleasePlanMilestonePayload,
|
||||
IReleasePlanMilestoneStrategy,
|
||||
} from 'interfaces/releasePlans';
|
||||
import { useState } from 'react';
|
||||
import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenuCards';
|
||||
|
||||
const StyledEditIcon = styled(Edit)(({ theme }) => ({
|
||||
cursor: 'pointer',
|
||||
@ -53,7 +57,10 @@ interface IMilestoneCardProps {
|
||||
index: number;
|
||||
milestone: IReleasePlanMilestonePayload;
|
||||
milestoneNameChanged: (index: number, name: string) => void;
|
||||
showAddStrategyDialog: (index: number) => void;
|
||||
showAddStrategyDialog: (
|
||||
index: number,
|
||||
strategy: IReleasePlanMilestoneStrategy,
|
||||
) => void;
|
||||
errors: { [key: string]: string };
|
||||
clearErrors: () => void;
|
||||
}
|
||||
@ -67,6 +74,22 @@ export const MilestoneCard = ({
|
||||
clearErrors,
|
||||
}: IMilestoneCardProps) => {
|
||||
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 (
|
||||
<StyledMilestoneCard>
|
||||
@ -111,10 +134,27 @@ export const MilestoneCard = ({
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
onClick={() => showAddStrategyDialog(index)}
|
||||
onClick={(ev) => setAnchor(ev.currentTarget)}
|
||||
>
|
||||
Add strategy
|
||||
</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>
|
||||
</StyledMilestoneCardBody>
|
||||
|
@ -1,4 +1,7 @@
|
||||
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
||||
import type {
|
||||
IReleasePlanMilestonePayload,
|
||||
IReleasePlanMilestoneStrategy,
|
||||
} from 'interfaces/releasePlans';
|
||||
import { MilestoneCard } from './MilestoneCard';
|
||||
import { styled } from '@mui/material';
|
||||
import { Button } from '@mui/material';
|
||||
@ -9,7 +12,10 @@ interface IMilestoneListProps {
|
||||
setMilestones: React.Dispatch<
|
||||
React.SetStateAction<IReleasePlanMilestonePayload[]>
|
||||
>;
|
||||
setAddStrategyOpen: (open: boolean) => void;
|
||||
openAddStrategyForm: (
|
||||
index: number,
|
||||
strategy: IReleasePlanMilestoneStrategy,
|
||||
) => void;
|
||||
errors: { [key: string]: string };
|
||||
clearErrors: () => void;
|
||||
}
|
||||
@ -22,14 +28,10 @@ const StyledAddMilestoneButton = styled(Button)(({ theme }) => ({
|
||||
export const MilestoneList = ({
|
||||
milestones,
|
||||
setMilestones,
|
||||
setAddStrategyOpen,
|
||||
openAddStrategyForm,
|
||||
errors,
|
||||
clearErrors,
|
||||
}: IMilestoneListProps) => {
|
||||
const showAddStrategyDialog = (index: number) => {
|
||||
setAddStrategyOpen(true);
|
||||
};
|
||||
|
||||
const milestoneNameChanged = (index: number, name: string) => {
|
||||
setMilestones((prev) =>
|
||||
prev.map((milestone, i) =>
|
||||
@ -46,7 +48,7 @@ export const MilestoneList = ({
|
||||
index={index}
|
||||
milestone={milestone}
|
||||
milestoneNameChanged={milestoneNameChanged}
|
||||
showAddStrategyDialog={showAddStrategyDialog}
|
||||
showAddStrategyDialog={openAddStrategyForm}
|
||||
errors={errors}
|
||||
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 { styled } from '@mui/material';
|
||||
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 ReleaseTemplateIcon from '@mui/icons-material/DashboardOutlined';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
@ -56,6 +59,24 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
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 (
|
||||
<FormTemplate
|
||||
@ -90,7 +111,7 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
|
||||
<MilestoneList
|
||||
milestones={milestones}
|
||||
setMilestones={setMilestones}
|
||||
setAddStrategyOpen={setAddStrategyOpen}
|
||||
openAddStrategyForm={openAddStrategyForm}
|
||||
errors={errors}
|
||||
clearErrors={clearErrors}
|
||||
/>
|
||||
|
@ -1,3 +1,6 @@
|
||||
import type { IFeatureVariant } from './featureToggle';
|
||||
import type { IConstraint, IFeatureStrategyParameters } from './strategy';
|
||||
|
||||
export interface IReleasePlanTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -15,6 +18,16 @@ export interface IReleasePlanTemplate {
|
||||
milestones: IReleasePlanMilestonePayload[];
|
||||
}
|
||||
|
||||
export interface IReleasePlanMilestoneStrategy {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
constraints: IConstraint[];
|
||||
parameters: IFeatureStrategyParameters;
|
||||
variants?: IFeatureVariant[];
|
||||
}
|
||||
|
||||
export interface IReleasePlanMilestone {
|
||||
id: string;
|
||||
name: string;
|
||||
|
Loading…
Reference in New Issue
Block a user