mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: improve milestone automation UI positioning and styling (#10758)
This commit is contained in:
		
							parent
							
								
									43fa239e72
								
							
						
					
					
						commit
						1d4f72cf81
					
				| @ -20,41 +20,26 @@ const StyledFormContainer = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     gap: theme.spacing(1.5), | ||||
|     padding: theme.spacing(2), | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     borderRadius: theme.spacing(0.75), | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
|     boxShadow: theme.boxShadows.elevated, | ||||
|     padding: theme.spacing(1.5, 2), | ||||
|     backgroundColor: theme.palette.background.elevation1, | ||||
|     width: '100%', | ||||
|     borderRadius: `${theme.shape.borderRadiusLarge}px`, | ||||
|     position: 'relative', | ||||
|     marginLeft: theme.spacing(3.25), | ||||
|     marginTop: theme.spacing(1.5), | ||||
|     marginBottom: theme.spacing(1.5), | ||||
|     animation: 'slideDown 0.5s ease-out', | ||||
|     '@keyframes slideDown': { | ||||
|         from: { | ||||
|             opacity: 0, | ||||
|             transform: 'translateY(-24px)', | ||||
|         }, | ||||
|         to: { | ||||
|             opacity: 1, | ||||
|             transform: 'translateY(0)', | ||||
|         }, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledTopRow = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     gap: theme.spacing(1.5), | ||||
|     gap: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyledIcon = styled(BoltIcon)(({ theme }) => ({ | ||||
|     color: theme.palette.primary.main, | ||||
|     fontSize: 20, | ||||
|     color: theme.palette.common.white, | ||||
|     fontSize: 18, | ||||
|     flexShrink: 0, | ||||
|     backgroundColor: theme.palette.background.elevation1, | ||||
|     backgroundColor: theme.palette.primary.main, | ||||
|     borderRadius: '50%', | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
|     padding: theme.spacing(0.25), | ||||
| })); | ||||
| 
 | ||||
| const StyledLabel = styled('span')(({ theme }) => ({ | ||||
| @ -105,8 +90,8 @@ const StyledButtonGroup = styled('div')(({ theme }) => ({ | ||||
|     gap: theme.spacing(1), | ||||
|     justifyContent: 'flex-end', | ||||
|     alignItems: 'center', | ||||
|     paddingTop: theme.spacing(1.5), | ||||
|     marginTop: theme.spacing(1), | ||||
|     paddingTop: theme.spacing(1), | ||||
|     marginTop: theme.spacing(0.5), | ||||
|     borderTop: `1px solid ${theme.palette.divider}`, | ||||
| })); | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import Delete from '@mui/icons-material/Delete'; | ||||
| import Add from '@mui/icons-material/Add'; | ||||
| import { styled, IconButton, Button } from '@mui/material'; | ||||
| import { styled } from '@mui/material'; | ||||
| import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; | ||||
| @ -72,47 +71,12 @@ const StyledBody = styled('div')(({ theme }) => ({ | ||||
| })); | ||||
| 
 | ||||
| const StyledConnection = styled('div')(({ theme }) => ({ | ||||
|     width: 4, | ||||
|     height: theme.spacing(6), | ||||
|     backgroundColor: theme.palette.divider, | ||||
|     marginLeft: theme.spacing(3.25), | ||||
| })); | ||||
| 
 | ||||
| const StyledConnectionSimple = styled('div')(({ theme }) => ({ | ||||
|     width: 4, | ||||
|     height: theme.spacing(2), | ||||
|     backgroundColor: theme.palette.divider, | ||||
|     marginLeft: theme.spacing(3.25), | ||||
| })); | ||||
| 
 | ||||
| const StyledConnectionContainer = styled('div')(({ theme }) => ({ | ||||
|     position: 'relative', | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledAddAutomationIconButton = styled(IconButton)(({ theme }) => ({ | ||||
|     position: 'absolute', | ||||
|     left: theme.spacing(2), | ||||
|     top: '12px', | ||||
|     width: 24, | ||||
|     height: 24, | ||||
|     border: `1px solid ${theme.palette.primary.main}`, | ||||
|     backgroundColor: theme.palette.background.elevation2, | ||||
|     zIndex: 1, | ||||
|     '& svg': { | ||||
|         fontSize: 16, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledAddAutomationButton = styled(Button)(({ theme }) => ({ | ||||
|     marginLeft: theme.spacing(3), | ||||
|     textTransform: 'none', | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
|     padding: 0, | ||||
|     minWidth: 'auto', | ||||
| })); | ||||
| 
 | ||||
| interface IReleasePlanProps { | ||||
|     plan: IReleasePlan; | ||||
|     environmentIsDisabled?: boolean; | ||||
| @ -332,63 +296,27 @@ export const ReleasePlan = ({ | ||||
|                                           : 'not-started' | ||||
|                                 } | ||||
|                                 onStartMilestone={onStartMilestone} | ||||
|                                 showAutomation={ | ||||
|                                     milestoneProgressionsEnabled && | ||||
|                                     isNotLastMilestone | ||||
|                                 } | ||||
|                                 onAddAutomation={handleOpenProgressionForm} | ||||
|                                 automationForm={ | ||||
|                                     isProgressionFormOpen ? ( | ||||
|                                         <MilestoneProgressionForm | ||||
|                                             sourceMilestoneId={milestone.id} | ||||
|                                             targetMilestoneId={nextMilestoneId} | ||||
|                                             projectId={projectId} | ||||
|                                             environment={environment} | ||||
|                                             onSave={handleProgressionSave} | ||||
|                                             onCancel={handleProgressionCancel} | ||||
|                                         /> | ||||
|                                     ) : undefined | ||||
|                                 } | ||||
|                             /> | ||||
|                             <ConditionallyRender | ||||
|                                 condition={isNotLastMilestone} | ||||
|                                 show={ | ||||
|                                     <ConditionallyRender | ||||
|                                         condition={milestoneProgressionsEnabled} | ||||
|                                         show={ | ||||
|                                             <ConditionallyRender | ||||
|                                                 condition={ | ||||
|                                                     isProgressionFormOpen | ||||
|                                                 } | ||||
|                                                 show={ | ||||
|                                                     <MilestoneProgressionForm | ||||
|                                                         sourceMilestoneId={ | ||||
|                                                             milestone.id | ||||
|                                                         } | ||||
|                                                         targetMilestoneId={ | ||||
|                                                             nextMilestoneId | ||||
|                                                         } | ||||
|                                                         projectId={projectId} | ||||
|                                                         environment={ | ||||
|                                                             environment | ||||
|                                                         } | ||||
|                                                         onSave={ | ||||
|                                                             handleProgressionSave | ||||
|                                                         } | ||||
|                                                         onCancel={ | ||||
|                                                             handleProgressionCancel | ||||
|                                                         } | ||||
|                                                     /> | ||||
|                                                 } | ||||
|                                                 elseShow={ | ||||
|                                                     <StyledConnectionContainer> | ||||
|                                                         <StyledConnection /> | ||||
|                                                         <StyledAddAutomationIconButton | ||||
|                                                             onClick={ | ||||
|                                                                 handleOpenProgressionForm | ||||
|                                                             } | ||||
|                                                             color='primary' | ||||
|                                                         > | ||||
|                                                             <Add /> | ||||
|                                                         </StyledAddAutomationIconButton> | ||||
|                                                         <StyledAddAutomationButton | ||||
|                                                             onClick={ | ||||
|                                                                 handleOpenProgressionForm | ||||
|                                                             } | ||||
|                                                             color='primary' | ||||
|                                                         > | ||||
|                                                             Add automation | ||||
|                                                         </StyledAddAutomationButton> | ||||
|                                                     </StyledConnectionContainer> | ||||
|                                                 } | ||||
|                                             /> | ||||
|                                         } | ||||
|                                         elseShow={<StyledConnectionSimple />} | ||||
|                                     /> | ||||
|                                 } | ||||
|                                 show={<StyledConnection />} | ||||
|                             /> | ||||
|                         </div> | ||||
|                     ); | ||||
|  | ||||
| @ -0,0 +1,79 @@ | ||||
| import Add from '@mui/icons-material/Add'; | ||||
| import { Button, styled } from '@mui/material'; | ||||
| import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx'; | ||||
| 
 | ||||
| const StyledAutomationContainer = styled('div', { | ||||
|     shouldForwardProp: (prop) => prop !== 'status', | ||||
| })<{ status?: MilestoneStatus }>(({ theme, status }) => ({ | ||||
|     border: `1px solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, | ||||
|     borderTop: `1px solid ${theme.palette.divider}`, | ||||
|     borderRadius: `0 0 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`, | ||||
|     padding: theme.spacing(1.5, 2), | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     alignItems: 'stretch', | ||||
|     gap: theme.spacing(1), | ||||
|     '& > *': { | ||||
|         alignSelf: 'flex-start', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledAddAutomationButton = styled(Button)(({ theme }) => ({ | ||||
|     textTransform: 'none', | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
|     fontSize: theme.typography.body2.fontSize, | ||||
|     padding: 0, | ||||
|     minWidth: 'auto', | ||||
|     gap: theme.spacing(1), | ||||
|     '&:hover': { | ||||
|         backgroundColor: 'transparent', | ||||
|     }, | ||||
|     '& .MuiButton-startIcon': { | ||||
|         margin: 0, | ||||
|         width: 20, | ||||
|         height: 20, | ||||
|         border: `1px solid ${theme.palette.primary.main}`, | ||||
|         backgroundColor: theme.palette.background.elevation2, | ||||
|         borderRadius: '50%', | ||||
|         display: 'flex', | ||||
|         alignItems: 'center', | ||||
|         justifyContent: 'center', | ||||
|         '& svg': { | ||||
|             fontSize: 14, | ||||
|             color: theme.palette.primary.main, | ||||
|         }, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| interface IMilestoneAutomationSectionProps { | ||||
|     showAutomation?: boolean; | ||||
|     status?: MilestoneStatus; | ||||
|     onAddAutomation?: () => void; | ||||
|     automationForm?: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export const MilestoneAutomationSection = ({ | ||||
|     showAutomation, | ||||
|     status, | ||||
|     onAddAutomation, | ||||
|     automationForm, | ||||
| }: IMilestoneAutomationSectionProps) => { | ||||
|     if (!showAutomation) return null; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledAutomationContainer status={status}> | ||||
|             {automationForm ? ( | ||||
|                 automationForm | ||||
|             ) : ( | ||||
|                 <StyledAddAutomationButton | ||||
|                     onClick={onAddAutomation} | ||||
|                     color='primary' | ||||
|                     startIcon={<Add />} | ||||
|                 > | ||||
|                     Add automation | ||||
|                 </StyledAddAutomationButton> | ||||
|             )} | ||||
|         </StyledAutomationContainer> | ||||
|     ); | ||||
| }; | ||||
| @ -16,19 +16,28 @@ import { StrategySeparator } from 'component/common/StrategySeparator/StrategySe | ||||
| import { StrategyItem } from '../../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx'; | ||||
| import { StrategyList } from 'component/common/StrategyList/StrategyList'; | ||||
| import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem'; | ||||
| import { MilestoneAutomationSection } from './MilestoneAutomationSection.tsx'; | ||||
| 
 | ||||
| const StyledAccordion = styled(Accordion, { | ||||
|     shouldForwardProp: (prop) => prop !== 'status', | ||||
| })<{ status: MilestoneStatus }>(({ theme, status }) => ({ | ||||
|     shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation', | ||||
| })<{ status: MilestoneStatus; hasAutomation?: boolean }>( | ||||
|     ({ theme, status, hasAutomation }) => ({ | ||||
|         border: `1px solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, | ||||
|         borderBottom: hasAutomation | ||||
|             ? 'none' | ||||
|             : `1px solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, | ||||
|         overflow: 'hidden', | ||||
|         boxShadow: 'none', | ||||
|         margin: 0, | ||||
|         backgroundColor: theme.palette.background.paper, | ||||
|         borderRadius: hasAutomation | ||||
|             ? `${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0 !important` | ||||
|             : `${theme.shape.borderRadiusLarge}px`, | ||||
|         '&:before': { | ||||
|             display: 'none', | ||||
|         }, | ||||
| })); | ||||
|     }), | ||||
| ); | ||||
| 
 | ||||
| const StyledAccordionSummary = styled(AccordionSummary)({ | ||||
|     '& .MuiAccordionSummary-content': { | ||||
| @ -58,11 +67,18 @@ const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ | ||||
|     padding: 0, | ||||
| })); | ||||
| 
 | ||||
| const StyledMilestoneContainer = styled('div')({ | ||||
|     position: 'relative', | ||||
| }); | ||||
| 
 | ||||
| interface IReleasePlanMilestoneProps { | ||||
|     milestone: IReleasePlanMilestone; | ||||
|     status?: MilestoneStatus; | ||||
|     onStartMilestone?: (milestone: IReleasePlanMilestone) => void; | ||||
|     readonly?: boolean; | ||||
|     showAutomation?: boolean; | ||||
|     onAddAutomation?: () => void; | ||||
|     automationForm?: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export const ReleasePlanMilestone = ({ | ||||
| @ -70,12 +86,16 @@ export const ReleasePlanMilestone = ({ | ||||
|     status = 'not-started', | ||||
|     onStartMilestone, | ||||
|     readonly, | ||||
|     showAutomation, | ||||
|     onAddAutomation, | ||||
|     automationForm, | ||||
| }: IReleasePlanMilestoneProps) => { | ||||
|     const [expanded, setExpanded] = useState(false); | ||||
| 
 | ||||
|     if (!milestone.strategies.length) { | ||||
|         return ( | ||||
|             <StyledAccordion status={status}> | ||||
|             <StyledMilestoneContainer> | ||||
|                 <StyledAccordion status={status} hasAutomation={showAutomation}> | ||||
|                     <StyledAccordionSummary> | ||||
|                         <StyledTitleContainer> | ||||
|                             <StyledTitle>{milestone.name}</StyledTitle> | ||||
| @ -88,15 +108,26 @@ export const ReleasePlanMilestone = ({ | ||||
|                                 /> | ||||
|                             )} | ||||
|                         </StyledTitleContainer> | ||||
|                     <StyledSecondaryLabel>No strategies</StyledSecondaryLabel> | ||||
|                         <StyledSecondaryLabel> | ||||
|                             No strategies | ||||
|                         </StyledSecondaryLabel> | ||||
|                     </StyledAccordionSummary> | ||||
|                 </StyledAccordion> | ||||
|                 <MilestoneAutomationSection | ||||
|                     showAutomation={showAutomation} | ||||
|                     status={status} | ||||
|                     onAddAutomation={onAddAutomation} | ||||
|                     automationForm={automationForm} | ||||
|                 /> | ||||
|             </StyledMilestoneContainer> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledMilestoneContainer> | ||||
|             <StyledAccordion | ||||
|                 status={status} | ||||
|                 hasAutomation={showAutomation} | ||||
|                 onChange={(evt, expanded) => setExpanded(expanded)} | ||||
|             > | ||||
|                 <StyledAccordionSummary expandIcon={<ExpandMore />}> | ||||
| @ -105,7 +136,9 @@ export const ReleasePlanMilestone = ({ | ||||
|                         {!readonly && onStartMilestone && ( | ||||
|                             <ReleasePlanMilestoneStatus | ||||
|                                 status={status} | ||||
|                             onStartMilestone={() => onStartMilestone(milestone)} | ||||
|                                 onStartMilestone={() => | ||||
|                                     onStartMilestone(milestone) | ||||
|                                 } | ||||
|                             /> | ||||
|                         )} | ||||
|                     </StyledTitleContainer> | ||||
| @ -136,5 +169,12 @@ export const ReleasePlanMilestone = ({ | ||||
|                     </StrategyList> | ||||
|                 </StyledAccordionDetails> | ||||
|             </StyledAccordion> | ||||
|             <MilestoneAutomationSection | ||||
|                 showAutomation={showAutomation} | ||||
|                 status={status} | ||||
|                 onAddAutomation={onAddAutomation} | ||||
|                 automationForm={automationForm} | ||||
|             /> | ||||
|         </StyledMilestoneContainer> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user