Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: selector dropdown for milestone new strategy #8841

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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;
}
Expand All @@ -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>
Expand Down Expand Up @@ -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()}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary, also going to use uuidv4() to generate an id for milestones

openAddStrategy={onSelectStrategy}
/>
</Popover>
</Grid>
</Grid>
</StyledMilestoneCardBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -9,7 +12,10 @@ interface IMilestoneListProps {
setMilestones: React.Dispatch<
React.SetStateAction<IReleasePlanMilestonePayload[]>
>;
setAddStrategyOpen: (open: boolean) => void;
openAddStrategyForm: (
index: number,
strategy: IReleasePlanMilestoneStrategy,
Copy link
Member

@nunogois nunogois Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to be increasingly more difficult to maintain. Maybe we should call it something else. I think this is the milestoneIndex, right?

) => void;
errors: { [key: string]: string };
clearErrors: () => void;
}
Expand All @@ -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) =>
Expand All @@ -46,7 +48,7 @@ export const MilestoneList = ({
index={index}
milestone={milestone}
milestoneNameChanged={milestoneNameChanged}
showAddStrategyDialog={showAddStrategyDialog}
showAddStrategyDialog={openAddStrategyForm}
errors={errors}
clearErrors={clearErrors}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
strategyClicked: (strategy: IReleasePlanMilestoneStrategy) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
strategyClicked: (strategy: IReleasePlanMilestoneStrategy) => void;
onClick: (strategy: IReleasePlanMilestoneStrategy) => void;

}

export const MilestoneStrategyMenuCard = ({
strategy,
strategyClicked,
}: IMilestoneStrategyMenuCardProps) => {
const StrategyIcon = getFeatureStrategyIcon(strategy.name);
const strategyName = formatStrategyName(strategy.name);
return (
<StyledCard
onClick={() => {
const strat = createFeatureStrategy('', strategy);
strategyClicked({
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>
);
};
Original file line number Diff line number Diff line change
@@ -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 strategyClicked = (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}
strategyClicked={strategyClicked}
/>
</ListItem>
))}
</>
</List>
);
};
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -90,7 +111,7 @@ export const TemplateForm: React.FC<ITemplateFormProps> = ({
<MilestoneList
milestones={milestones}
setMilestones={setMilestones}
setAddStrategyOpen={setAddStrategyOpen}
openAddStrategyForm={openAddStrategyForm}
errors={errors}
clearErrors={clearErrors}
/>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/interfaces/releasePlans.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { IFeatureVariant } from './featureToggle';
import type { IConstraint, IFeatureStrategyParameters } from './strategy';

export interface IReleasePlanTemplate {
id: string;
name: string;
Expand All @@ -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;
Expand Down
Loading