1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

feat: add change request support for updating milestone progressions (#10819)

This commit is contained in:
Fredrik Strand Oseberg 2025-10-16 16:52:02 +02:00 committed by GitHub
parent 022226dd43
commit 795b674133
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 11 deletions

View File

@ -5,6 +5,7 @@ import type { IUser } from '../../interfaces/user.js';
import type {
SetStrategySortOrderSchema,
CreateMilestoneProgressionSchema,
UpdateMilestoneProgressionSchema,
} from 'openapi';
import type { IReleasePlan } from 'interfaces/releasePlans';
@ -135,7 +136,8 @@ type ChangeRequestPayload =
| ChangeRequestAddReleasePlan
| ChangeRequestDeleteReleasePlan
| ChangeRequestStartMilestone
| ChangeRequestCreateMilestoneProgression;
| ChangeRequestCreateMilestoneProgression
| ChangeRequestUpdateMilestoneProgression;
export interface IChangeRequestAddStrategy extends IChangeRequestChangeBase {
action: 'addStrategy';
@ -198,6 +200,12 @@ export interface IChangeRequestCreateMilestoneProgression
payload: ChangeRequestCreateMilestoneProgression;
}
export interface IChangeRequestUpdateMilestoneProgression
extends IChangeRequestChangeBase {
action: 'updateMilestoneProgression';
payload: ChangeRequestUpdateMilestoneProgression;
}
export interface IChangeRequestReorderStrategy
extends IChangeRequestChangeBase {
action: 'reorderStrategy';
@ -246,7 +254,8 @@ export type IFeatureChange =
| IChangeRequestAddReleasePlan
| IChangeRequestDeleteReleasePlan
| IChangeRequestStartMilestone
| IChangeRequestCreateMilestoneProgression;
| IChangeRequestCreateMilestoneProgression
| IChangeRequestUpdateMilestoneProgression;
export type ISegmentChange =
| IChangeRequestUpdateSegment
@ -281,6 +290,11 @@ type ChangeRequestStartMilestone = {
type ChangeRequestCreateMilestoneProgression = CreateMilestoneProgressionSchema;
type ChangeRequestUpdateMilestoneProgression =
UpdateMilestoneProgressionSchema & {
sourceMilestoneId: string;
};
export type ChangeRequestAddStrategy = Pick<
IFeatureStrategy,
| 'parameters'
@ -319,4 +333,5 @@ export type ChangeRequestAction =
| 'addReleasePlan'
| 'deleteReleasePlan'
| 'startMilestone'
| 'createMilestoneProgression';
| 'createMilestoneProgression'
| 'updateMilestoneProgression';

View File

@ -4,7 +4,10 @@ import type {
IReleasePlan,
IReleasePlanMilestone,
} from 'interfaces/releasePlans';
import type { CreateMilestoneProgressionSchema } from 'openapi';
import type {
CreateMilestoneProgressionSchema,
UpdateMilestoneProgressionSchema,
} from 'openapi';
import { getTimeValueAndUnitFromMinutes } from '../hooks/useMilestoneProgressionForm.js';
const StyledBoldSpan = styled('span')(({ theme }) => ({
@ -23,6 +26,11 @@ type ChangeRequestAction =
| {
type: 'createMilestoneProgression';
payload: CreateMilestoneProgressionSchema;
}
| {
type: 'updateMilestoneProgression';
sourceMilestoneId: string;
payload: UpdateMilestoneProgressionSchema;
};
interface IReleasePlanChangeRequestDialogProps {
@ -105,6 +113,27 @@ export const ReleasePlanChangeRequestDialog = ({
</p>
);
}
case 'updateMilestoneProgression': {
const milestone = releasePlan.milestones.find(
(milestone) => milestone.id === action.sourceMilestoneId,
);
const { value, unit } = getTimeValueAndUnitFromMinutes(
action.payload.transitionCondition.intervalMinutes,
);
const timeInterval = `${value} ${unit}`;
return (
<p>
Update automation for{' '}
<StyledBoldSpan>{milestone?.name}</StyledBoldSpan> to
proceed after{' '}
<StyledBoldSpan>{timeInterval}</StyledBoldSpan> in{' '}
{environmentId}
</p>
);
}
}
};

View File

@ -25,7 +25,10 @@ import { useUiFlag } from 'hooks/useUiFlag';
import { MilestoneProgressionForm } from './MilestoneProgressionForm/MilestoneProgressionForm.tsx';
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx';
import type { CreateMilestoneProgressionSchema } from 'openapi';
import type {
CreateMilestoneProgressionSchema,
UpdateMilestoneProgressionSchema,
} from 'openapi';
const StyledContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(2),
@ -124,6 +127,11 @@ export const ReleasePlan = ({
type: 'createMilestoneProgression';
payload: CreateMilestoneProgressionSchema;
}
| {
type: 'updateMilestoneProgression';
sourceMilestoneId: string;
payload: UpdateMilestoneProgressionSchema;
}
| null
>(null);
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
@ -171,6 +179,17 @@ export const ReleasePlan = ({
});
setProgressionFormOpenIndex(null);
break;
case 'updateMilestoneProgression':
await addChange(projectId, environment, {
feature: featureName,
action: 'updateMilestoneProgression',
payload: {
sourceMilestone: changeRequestAction.sourceMilestoneId,
...changeRequestAction.payload,
},
});
break;
}
await refetchChangeRequests();
@ -273,6 +292,17 @@ export const ReleasePlan = ({
});
};
const handleUpdateProgressionChangeRequestSubmit = (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => {
setChangeRequestAction({
type: 'updateMilestoneProgression',
sourceMilestoneId,
payload,
});
};
const handleDeleteProgression = (milestone: IReleasePlanMilestone) => {
setMilestoneToDeleteProgression(milestone);
};
@ -398,6 +428,9 @@ export const ReleasePlan = ({
environment={environment}
featureName={featureName}
onUpdate={refetch}
onUpdateChangeRequestSubmit={
handleUpdateProgressionChangeRequestSubmit
}
allMilestones={milestones}
activeMilestoneId={activeMilestoneId}
/>

View File

@ -2,6 +2,7 @@ import Add from '@mui/icons-material/Add';
import { Button, styled } from '@mui/material';
import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx';
import { MilestoneTransitionDisplay } from './MilestoneTransitionDisplay.tsx';
import type { UpdateMilestoneProgressionSchema } from 'openapi';
const StyledAutomationContainer = styled('div', {
shouldForwardProp: (prop) => prop !== 'status',
@ -65,6 +66,10 @@ interface IMilestoneAutomationSectionProps {
featureName: string;
sourceMilestoneId: string;
onUpdate: () => void;
onUpdateChangeRequestSubmit?: (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => void;
}
export const MilestoneAutomationSection = ({
@ -80,6 +85,7 @@ export const MilestoneAutomationSection = ({
featureName,
sourceMilestoneId,
onUpdate,
onUpdateChangeRequestSubmit,
}: IMilestoneAutomationSectionProps) => {
if (!showAutomation) return null;
@ -98,6 +104,7 @@ export const MilestoneAutomationSection = ({
featureName={featureName}
sourceMilestoneId={sourceMilestoneId}
onUpdate={onUpdate}
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
/>
) : (
<StyledAddAutomationButton

View File

@ -11,6 +11,8 @@ import {
useMilestoneProgressionForm,
getTimeValueAndUnitFromMinutes,
} from '../hooks/useMilestoneProgressionForm.js';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import type { UpdateMilestoneProgressionSchema } from 'openapi';
const StyledDisplayContainer = styled('div')(({ theme }) => ({
display: 'flex',
@ -67,6 +69,10 @@ interface IMilestoneTransitionDisplayProps {
featureName: string;
sourceMilestoneId: string;
onUpdate: () => void;
onChangeRequestSubmit?: (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => void;
}
export const MilestoneTransitionDisplay = ({
@ -79,9 +85,11 @@ export const MilestoneTransitionDisplay = ({
featureName,
sourceMilestoneId,
onUpdate,
onChangeRequestSubmit,
}: IMilestoneTransitionDisplayProps) => {
const { updateMilestoneProgression } = useMilestoneProgressionsApi();
const { setToastData, setToastApiError } = useToast();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const initial = getTimeValueAndUnitFromMinutes(intervalMinutes);
const form = useMilestoneProgressionForm(
@ -100,6 +108,17 @@ export const MilestoneTransitionDisplay = ({
const handleSave = async () => {
if (isSubmitting || !hasChanged) return;
const payload: UpdateMilestoneProgressionSchema = {
transitionCondition: {
intervalMinutes: currentIntervalMinutes,
},
};
if (isChangeRequestConfigured(environment) && onChangeRequestSubmit) {
onChangeRequestSubmit(sourceMilestoneId, payload);
return;
}
setIsSubmitting(true);
try {
await updateMilestoneProgression(
@ -107,11 +126,7 @@ export const MilestoneTransitionDisplay = ({
environment,
featureName,
sourceMilestoneId,
{
transitionCondition: {
intervalMinutes: currentIntervalMinutes,
},
},
payload,
);
setToastData({
type: 'success',

View File

@ -19,6 +19,7 @@ import { StrategyList } from 'component/common/StrategyList/StrategyList';
import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem';
import { MilestoneAutomationSection } from './MilestoneAutomationSection.tsx';
import { formatDateYMDHMS } from 'utils/formatDate';
import type { UpdateMilestoneProgressionSchema } from 'openapi';
const StyledAccordion = styled(Accordion, {
shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation',
@ -107,6 +108,10 @@ interface IReleasePlanMilestoneProps {
environment?: string;
featureName?: string;
onUpdate?: () => void;
onUpdateChangeRequestSubmit?: (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => void;
allMilestones: IReleasePlanMilestone[];
activeMilestoneId?: string;
}
@ -124,6 +129,7 @@ export const ReleasePlanMilestone = ({
environment,
featureName,
onUpdate,
onUpdateChangeRequestSubmit,
allMilestones,
activeMilestoneId,
}: IReleasePlanMilestoneProps) => {
@ -193,6 +199,9 @@ export const ReleasePlanMilestone = ({
featureName={featureName}
sourceMilestoneId={milestone.id}
onUpdate={onUpdate}
onUpdateChangeRequestSubmit={
onUpdateChangeRequestSubmit
}
/>
)}
</StyledMilestoneContainer>
@ -283,6 +292,9 @@ export const ReleasePlanMilestone = ({
featureName={featureName}
sourceMilestoneId={milestone.id}
onUpdate={onUpdate}
onUpdateChangeRequestSubmit={
onUpdateChangeRequestSubmit
}
/>
)}
</StyledMilestoneContainer>

View File

@ -22,7 +22,8 @@ export interface IChangeSchema {
| 'addReleasePlan'
| 'deleteReleasePlan'
| 'startMilestone'
| 'createMilestoneProgression';
| 'createMilestoneProgression'
| 'updateMilestoneProgression';
payload: string | boolean | object | number | undefined;
}