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',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(1.5),
|
gap: theme.spacing(1.5),
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(1.5, 2),
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.elevation1,
|
||||||
borderRadius: theme.spacing(0.75),
|
width: '100%',
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
boxShadow: theme.boxShadows.elevated,
|
|
||||||
position: 'relative',
|
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 }) => ({
|
const StyledTopRow = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing(1.5),
|
gap: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledIcon = styled(BoltIcon)(({ theme }) => ({
|
const StyledIcon = styled(BoltIcon)(({ theme }) => ({
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.common.white,
|
||||||
fontSize: 20,
|
fontSize: 18,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
backgroundColor: theme.palette.background.elevation1,
|
backgroundColor: theme.palette.primary.main,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
padding: theme.spacing(0.25),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledLabel = styled('span')(({ theme }) => ({
|
const StyledLabel = styled('span')(({ theme }) => ({
|
||||||
@ -105,8 +90,8 @@ const StyledButtonGroup = styled('div')(({ theme }) => ({
|
|||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingTop: theme.spacing(1.5),
|
paddingTop: theme.spacing(1),
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(0.5),
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import Delete from '@mui/icons-material/Delete';
|
import Delete from '@mui/icons-material/Delete';
|
||||||
import Add from '@mui/icons-material/Add';
|
import { styled } from '@mui/material';
|
||||||
import { styled, IconButton, Button } from '@mui/material';
|
|
||||||
import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions';
|
import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions';
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
|
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
|
||||||
@ -72,47 +71,12 @@ const StyledBody = styled('div')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledConnection = 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,
|
width: 4,
|
||||||
height: theme.spacing(2),
|
height: theme.spacing(2),
|
||||||
backgroundColor: theme.palette.divider,
|
backgroundColor: theme.palette.divider,
|
||||||
marginLeft: theme.spacing(3.25),
|
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 {
|
interface IReleasePlanProps {
|
||||||
plan: IReleasePlan;
|
plan: IReleasePlan;
|
||||||
environmentIsDisabled?: boolean;
|
environmentIsDisabled?: boolean;
|
||||||
@ -332,63 +296,27 @@ export const ReleasePlan = ({
|
|||||||
: 'not-started'
|
: 'not-started'
|
||||||
}
|
}
|
||||||
onStartMilestone={onStartMilestone}
|
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
|
<ConditionallyRender
|
||||||
condition={isNotLastMilestone}
|
condition={isNotLastMilestone}
|
||||||
show={
|
show={<StyledConnection />}
|
||||||
<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 />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { StrategyItem } from '../../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx';
|
||||||
import { StrategyList } from 'component/common/StrategyList/StrategyList';
|
import { StrategyList } from 'component/common/StrategyList/StrategyList';
|
||||||
import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem';
|
import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem';
|
||||||
|
import { MilestoneAutomationSection } from './MilestoneAutomationSection.tsx';
|
||||||
|
|
||||||
const StyledAccordion = styled(Accordion, {
|
const StyledAccordion = styled(Accordion, {
|
||||||
shouldForwardProp: (prop) => prop !== 'status',
|
shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation',
|
||||||
})<{ status: MilestoneStatus }>(({ theme, status }) => ({
|
})<{ status: MilestoneStatus; hasAutomation?: boolean }>(
|
||||||
|
({ theme, status, hasAutomation }) => ({
|
||||||
border: `1px solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`,
|
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',
|
overflow: 'hidden',
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: hasAutomation
|
||||||
|
? `${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0 !important`
|
||||||
|
: `${theme.shape.borderRadiusLarge}px`,
|
||||||
'&:before': {
|
'&:before': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const StyledAccordionSummary = styled(AccordionSummary)({
|
const StyledAccordionSummary = styled(AccordionSummary)({
|
||||||
'& .MuiAccordionSummary-content': {
|
'& .MuiAccordionSummary-content': {
|
||||||
@ -58,11 +67,18 @@ const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledMilestoneContainer = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
|
||||||
interface IReleasePlanMilestoneProps {
|
interface IReleasePlanMilestoneProps {
|
||||||
milestone: IReleasePlanMilestone;
|
milestone: IReleasePlanMilestone;
|
||||||
status?: MilestoneStatus;
|
status?: MilestoneStatus;
|
||||||
onStartMilestone?: (milestone: IReleasePlanMilestone) => void;
|
onStartMilestone?: (milestone: IReleasePlanMilestone) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
showAutomation?: boolean;
|
||||||
|
onAddAutomation?: () => void;
|
||||||
|
automationForm?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReleasePlanMilestone = ({
|
export const ReleasePlanMilestone = ({
|
||||||
@ -70,12 +86,16 @@ export const ReleasePlanMilestone = ({
|
|||||||
status = 'not-started',
|
status = 'not-started',
|
||||||
onStartMilestone,
|
onStartMilestone,
|
||||||
readonly,
|
readonly,
|
||||||
|
showAutomation,
|
||||||
|
onAddAutomation,
|
||||||
|
automationForm,
|
||||||
}: IReleasePlanMilestoneProps) => {
|
}: IReleasePlanMilestoneProps) => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
if (!milestone.strategies.length) {
|
if (!milestone.strategies.length) {
|
||||||
return (
|
return (
|
||||||
<StyledAccordion status={status}>
|
<StyledMilestoneContainer>
|
||||||
|
<StyledAccordion status={status} hasAutomation={showAutomation}>
|
||||||
<StyledAccordionSummary>
|
<StyledAccordionSummary>
|
||||||
<StyledTitleContainer>
|
<StyledTitleContainer>
|
||||||
<StyledTitle>{milestone.name}</StyledTitle>
|
<StyledTitle>{milestone.name}</StyledTitle>
|
||||||
@ -88,15 +108,26 @@ export const ReleasePlanMilestone = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
<StyledSecondaryLabel>No strategies</StyledSecondaryLabel>
|
<StyledSecondaryLabel>
|
||||||
|
No strategies
|
||||||
|
</StyledSecondaryLabel>
|
||||||
</StyledAccordionSummary>
|
</StyledAccordionSummary>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
|
<MilestoneAutomationSection
|
||||||
|
showAutomation={showAutomation}
|
||||||
|
status={status}
|
||||||
|
onAddAutomation={onAddAutomation}
|
||||||
|
automationForm={automationForm}
|
||||||
|
/>
|
||||||
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<StyledMilestoneContainer>
|
||||||
<StyledAccordion
|
<StyledAccordion
|
||||||
status={status}
|
status={status}
|
||||||
|
hasAutomation={showAutomation}
|
||||||
onChange={(evt, expanded) => setExpanded(expanded)}
|
onChange={(evt, expanded) => setExpanded(expanded)}
|
||||||
>
|
>
|
||||||
<StyledAccordionSummary expandIcon={<ExpandMore />}>
|
<StyledAccordionSummary expandIcon={<ExpandMore />}>
|
||||||
@ -105,7 +136,9 @@ export const ReleasePlanMilestone = ({
|
|||||||
{!readonly && onStartMilestone && (
|
{!readonly && onStartMilestone && (
|
||||||
<ReleasePlanMilestoneStatus
|
<ReleasePlanMilestoneStatus
|
||||||
status={status}
|
status={status}
|
||||||
onStartMilestone={() => onStartMilestone(milestone)}
|
onStartMilestone={() =>
|
||||||
|
onStartMilestone(milestone)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
@ -136,5 +169,12 @@ export const ReleasePlanMilestone = ({
|
|||||||
</StrategyList>
|
</StrategyList>
|
||||||
</StyledAccordionDetails>
|
</StyledAccordionDetails>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
|
<MilestoneAutomationSection
|
||||||
|
showAutomation={showAutomation}
|
||||||
|
status={status}
|
||||||
|
onAddAutomation={onAddAutomation}
|
||||||
|
automationForm={automationForm}
|
||||||
|
/>
|
||||||
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user