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:
parent
022226dd43
commit
795b674133
@ -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';
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user