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

View File

@ -4,7 +4,10 @@ import type {
IReleasePlan, IReleasePlan,
IReleasePlanMilestone, IReleasePlanMilestone,
} from 'interfaces/releasePlans'; } from 'interfaces/releasePlans';
import type { CreateMilestoneProgressionSchema } from 'openapi'; import type {
CreateMilestoneProgressionSchema,
UpdateMilestoneProgressionSchema,
} from 'openapi';
import { getTimeValueAndUnitFromMinutes } from '../hooks/useMilestoneProgressionForm.js'; import { getTimeValueAndUnitFromMinutes } from '../hooks/useMilestoneProgressionForm.js';
const StyledBoldSpan = styled('span')(({ theme }) => ({ const StyledBoldSpan = styled('span')(({ theme }) => ({
@ -23,6 +26,11 @@ type ChangeRequestAction =
| { | {
type: 'createMilestoneProgression'; type: 'createMilestoneProgression';
payload: CreateMilestoneProgressionSchema; payload: CreateMilestoneProgressionSchema;
}
| {
type: 'updateMilestoneProgression';
sourceMilestoneId: string;
payload: UpdateMilestoneProgressionSchema;
}; };
interface IReleasePlanChangeRequestDialogProps { interface IReleasePlanChangeRequestDialogProps {
@ -105,6 +113,27 @@ export const ReleasePlanChangeRequestDialog = ({
</p> </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 { MilestoneProgressionForm } from './MilestoneProgressionForm/MilestoneProgressionForm.tsx';
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi'; import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx'; import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx';
import type { CreateMilestoneProgressionSchema } from 'openapi'; import type {
CreateMilestoneProgressionSchema,
UpdateMilestoneProgressionSchema,
} from 'openapi';
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(2), padding: theme.spacing(2),
@ -124,6 +127,11 @@ export const ReleasePlan = ({
type: 'createMilestoneProgression'; type: 'createMilestoneProgression';
payload: CreateMilestoneProgressionSchema; payload: CreateMilestoneProgressionSchema;
} }
| {
type: 'updateMilestoneProgression';
sourceMilestoneId: string;
payload: UpdateMilestoneProgressionSchema;
}
| null | null
>(null); >(null);
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
@ -171,6 +179,17 @@ export const ReleasePlan = ({
}); });
setProgressionFormOpenIndex(null); setProgressionFormOpenIndex(null);
break; break;
case 'updateMilestoneProgression':
await addChange(projectId, environment, {
feature: featureName,
action: 'updateMilestoneProgression',
payload: {
sourceMilestone: changeRequestAction.sourceMilestoneId,
...changeRequestAction.payload,
},
});
break;
} }
await refetchChangeRequests(); await refetchChangeRequests();
@ -273,6 +292,17 @@ export const ReleasePlan = ({
}); });
}; };
const handleUpdateProgressionChangeRequestSubmit = (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => {
setChangeRequestAction({
type: 'updateMilestoneProgression',
sourceMilestoneId,
payload,
});
};
const handleDeleteProgression = (milestone: IReleasePlanMilestone) => { const handleDeleteProgression = (milestone: IReleasePlanMilestone) => {
setMilestoneToDeleteProgression(milestone); setMilestoneToDeleteProgression(milestone);
}; };
@ -398,6 +428,9 @@ export const ReleasePlan = ({
environment={environment} environment={environment}
featureName={featureName} featureName={featureName}
onUpdate={refetch} onUpdate={refetch}
onUpdateChangeRequestSubmit={
handleUpdateProgressionChangeRequestSubmit
}
allMilestones={milestones} allMilestones={milestones}
activeMilestoneId={activeMilestoneId} activeMilestoneId={activeMilestoneId}
/> />

View File

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

View File

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

View File

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

View File

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