mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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