mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
feat: add change request support for milestone progressions (#10814)
This commit is contained in:
parent
224b842b5b
commit
154dc6f5eb
@ -2,7 +2,10 @@ import type { IFeatureVariant } from 'interfaces/featureToggle';
|
||||
import type { ISegment } from 'interfaces/segment';
|
||||
import type { IFeatureStrategy } from '../../interfaces/strategy.js';
|
||||
import type { IUser } from '../../interfaces/user.js';
|
||||
import type { SetStrategySortOrderSchema } from 'openapi';
|
||||
import type {
|
||||
SetStrategySortOrderSchema,
|
||||
CreateMilestoneProgressionSchema,
|
||||
} from 'openapi';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
|
||||
type BaseChangeRequest = {
|
||||
@ -131,7 +134,8 @@ type ChangeRequestPayload =
|
||||
| ChangeRequestAddDependency
|
||||
| ChangeRequestAddReleasePlan
|
||||
| ChangeRequestDeleteReleasePlan
|
||||
| ChangeRequestStartMilestone;
|
||||
| ChangeRequestStartMilestone
|
||||
| ChangeRequestCreateMilestoneProgression;
|
||||
|
||||
export interface IChangeRequestAddStrategy extends IChangeRequestChangeBase {
|
||||
action: 'addStrategy';
|
||||
@ -188,6 +192,12 @@ export interface IChangeRequestStartMilestone extends IChangeRequestChangeBase {
|
||||
payload: ChangeRequestStartMilestone;
|
||||
}
|
||||
|
||||
export interface IChangeRequestCreateMilestoneProgression
|
||||
extends IChangeRequestChangeBase {
|
||||
action: 'createMilestoneProgression';
|
||||
payload: ChangeRequestCreateMilestoneProgression;
|
||||
}
|
||||
|
||||
export interface IChangeRequestReorderStrategy
|
||||
extends IChangeRequestChangeBase {
|
||||
action: 'reorderStrategy';
|
||||
@ -235,7 +245,8 @@ export type IFeatureChange =
|
||||
| IChangeRequestDeleteDependency
|
||||
| IChangeRequestAddReleasePlan
|
||||
| IChangeRequestDeleteReleasePlan
|
||||
| IChangeRequestStartMilestone;
|
||||
| IChangeRequestStartMilestone
|
||||
| IChangeRequestCreateMilestoneProgression;
|
||||
|
||||
export type ISegmentChange =
|
||||
| IChangeRequestUpdateSegment
|
||||
@ -268,6 +279,8 @@ type ChangeRequestStartMilestone = {
|
||||
snapshot?: IReleasePlan;
|
||||
};
|
||||
|
||||
type ChangeRequestCreateMilestoneProgression = CreateMilestoneProgressionSchema;
|
||||
|
||||
export type ChangeRequestAddStrategy = Pick<
|
||||
IFeatureStrategy,
|
||||
| 'parameters'
|
||||
@ -305,4 +318,5 @@ export type ChangeRequestAction =
|
||||
| 'deleteDependency'
|
||||
| 'addReleasePlan'
|
||||
| 'deleteReleasePlan'
|
||||
| 'startMilestone';
|
||||
| 'startMilestone'
|
||||
| 'createMilestoneProgression';
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { styled, Button } from '@mui/material';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
import type { CreateMilestoneProgressionSchema } from 'openapi';
|
||||
import { getTimeValueAndUnitFromMinutes } from '../hooks/useMilestoneProgressionForm.js';
|
||||
|
||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
interface ICreateMilestoneProgressionChangeRequestDialogProps {
|
||||
environmentId: string;
|
||||
releasePlan: IReleasePlan;
|
||||
payload: CreateMilestoneProgressionSchema;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClosing: () => void;
|
||||
}
|
||||
|
||||
export const CreateMilestoneProgressionChangeRequestDialog = ({
|
||||
environmentId,
|
||||
releasePlan,
|
||||
payload,
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClosing,
|
||||
}: ICreateMilestoneProgressionChangeRequestDialogProps) => {
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sourceMilestone = releasePlan.milestones.find(
|
||||
(milestone) => milestone.id === payload.sourceMilestone,
|
||||
);
|
||||
const targetMilestone = releasePlan.milestones.find(
|
||||
(milestone) => milestone.id === payload.targetMilestone,
|
||||
);
|
||||
|
||||
const { value, unit } = getTimeValueAndUnitFromMinutes(
|
||||
payload.transitionCondition.intervalMinutes,
|
||||
);
|
||||
const timeInterval = `${value} ${unit}`;
|
||||
|
||||
return (
|
||||
<Dialogue
|
||||
title='Request changes'
|
||||
open={isOpen}
|
||||
secondaryButtonText='Cancel'
|
||||
onClose={onClosing}
|
||||
customButton={
|
||||
<Button
|
||||
color='primary'
|
||||
variant='contained'
|
||||
onClick={onConfirm}
|
||||
autoFocus={true}
|
||||
>
|
||||
Add suggestion to draft
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
Create automation to proceed from{' '}
|
||||
<StyledBoldSpan>{sourceMilestone?.name}</StyledBoldSpan> to{' '}
|
||||
<StyledBoldSpan>{targetMilestone?.name}</StyledBoldSpan> after{' '}
|
||||
<StyledBoldSpan>{timeInterval}</StyledBoldSpan> in{' '}
|
||||
{environmentId}
|
||||
</p>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
@ -6,6 +6,8 @@ import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgr
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { MilestoneProgressionTimeInput } from './MilestoneProgressionTimeInput.tsx';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import type { CreateMilestoneProgressionSchema } from 'openapi';
|
||||
|
||||
const StyledFormContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -63,6 +65,9 @@ interface IMilestoneProgressionFormProps {
|
||||
featureName: string;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
onChangeRequestSubmit?: (
|
||||
progressionPayload: CreateMilestoneProgressionSchema,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const MilestoneProgressionForm = ({
|
||||
@ -73,6 +78,7 @@ export const MilestoneProgressionForm = ({
|
||||
featureName,
|
||||
onSave,
|
||||
onCancel,
|
||||
onChangeRequestSubmit,
|
||||
}: IMilestoneProgressionFormProps) => {
|
||||
const form = useMilestoneProgressionForm(
|
||||
sourceMilestoneId,
|
||||
@ -80,16 +86,16 @@ export const MilestoneProgressionForm = ({
|
||||
);
|
||||
const { createMilestoneProgression } = useMilestoneProgressionsApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting) return;
|
||||
|
||||
if (!form.validate()) {
|
||||
return;
|
||||
}
|
||||
const handleChangeRequestSubmit = () => {
|
||||
const progressionPayload = form.getProgressionPayload();
|
||||
onChangeRequestSubmit?.(progressionPayload);
|
||||
};
|
||||
|
||||
const handleDirectSubmit = async () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await createMilestoneProgression(
|
||||
@ -110,6 +116,20 @@ export const MilestoneProgressionForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting) return;
|
||||
|
||||
if (!form.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isChangeRequestConfigured(environment) && onChangeRequestSubmit) {
|
||||
handleChangeRequestSubmit();
|
||||
} else {
|
||||
await handleDirectSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
@ -20,12 +20,14 @@ import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useCh
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { RemoveReleasePlanChangeRequestDialog } from './ChangeRequest/RemoveReleasePlanChangeRequestDialog.tsx';
|
||||
import { StartMilestoneChangeRequestDialog } from './ChangeRequest/StartMilestoneChangeRequestDialog.tsx';
|
||||
import { CreateMilestoneProgressionChangeRequestDialog } from './ChangeRequest/CreateMilestoneProgressionChangeRequestDialog.tsx';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||
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';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
@ -123,10 +125,16 @@ export const ReleasePlan = ({
|
||||
changeRequestDialogStartMilestoneOpen,
|
||||
setChangeRequestDialogStartMilestoneOpen,
|
||||
] = useState(false);
|
||||
const [
|
||||
changeRequestDialogCreateProgressionOpen,
|
||||
setChangeRequestDialogCreateProgressionOpen,
|
||||
] = useState(false);
|
||||
const [
|
||||
milestoneForChangeRequestDialog,
|
||||
setMilestoneForChangeRequestDialog,
|
||||
] = useState<IReleasePlanMilestone>();
|
||||
const [progressionDataForCR, setProgressionDataForCR] =
|
||||
useState<CreateMilestoneProgressionSchema | null>(null);
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { addChange } = useChangeRequestApi();
|
||||
const { refetch: refetchChangeRequests } =
|
||||
@ -178,6 +186,27 @@ export const ReleasePlan = ({
|
||||
setChangeRequestDialogStartMilestoneOpen(false);
|
||||
};
|
||||
|
||||
const onAddCreateProgressionChangesConfirm = async () => {
|
||||
if (!progressionDataForCR) return;
|
||||
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'createMilestoneProgression',
|
||||
payload: progressionDataForCR,
|
||||
});
|
||||
|
||||
await refetchChangeRequests();
|
||||
|
||||
setToastData({
|
||||
type: 'success',
|
||||
text: 'Added to draft',
|
||||
});
|
||||
|
||||
setChangeRequestDialogCreateProgressionOpen(false);
|
||||
setProgressionFormOpenIndex(null);
|
||||
setProgressionDataForCR(null);
|
||||
};
|
||||
|
||||
const confirmRemoveReleasePlan = () => {
|
||||
if (isChangeRequestConfigured(environment)) {
|
||||
setChangeRequestDialogRemoveOpen(true);
|
||||
@ -254,6 +283,13 @@ export const ReleasePlan = ({
|
||||
setProgressionFormOpenIndex(null);
|
||||
};
|
||||
|
||||
const handleProgressionChangeRequestSubmit = (
|
||||
payload: CreateMilestoneProgressionSchema,
|
||||
) => {
|
||||
setProgressionDataForCR(payload);
|
||||
setChangeRequestDialogCreateProgressionOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteProgression = (milestone: IReleasePlanMilestone) => {
|
||||
setMilestoneToDeleteProgression(milestone);
|
||||
};
|
||||
@ -367,6 +403,11 @@ export const ReleasePlan = ({
|
||||
featureName={featureName}
|
||||
onSave={handleProgressionSave}
|
||||
onCancel={handleProgressionCancel}
|
||||
onChangeRequestSubmit={(payload) =>
|
||||
handleProgressionChangeRequestSubmit(
|
||||
payload,
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
@ -417,6 +458,19 @@ export const ReleasePlan = ({
|
||||
releasePlan={plan}
|
||||
milestone={milestoneForChangeRequestDialog}
|
||||
/>
|
||||
{progressionDataForCR && (
|
||||
<CreateMilestoneProgressionChangeRequestDialog
|
||||
environmentId={environment}
|
||||
isOpen={changeRequestDialogCreateProgressionOpen}
|
||||
onConfirm={onAddCreateProgressionChangesConfirm}
|
||||
onClosing={() => {
|
||||
setChangeRequestDialogCreateProgressionOpen(false);
|
||||
setProgressionDataForCR(null);
|
||||
}}
|
||||
releasePlan={plan}
|
||||
payload={progressionDataForCR}
|
||||
/>
|
||||
)}
|
||||
{milestoneToDeleteProgression && (
|
||||
<DeleteProgressionDialog
|
||||
open={milestoneToDeleteProgression !== null}
|
||||
|
||||
@ -21,7 +21,8 @@ export interface IChangeSchema {
|
||||
| 'deleteDependency'
|
||||
| 'addReleasePlan'
|
||||
| 'deleteReleasePlan'
|
||||
| 'startMilestone';
|
||||
| 'startMilestone'
|
||||
| 'createMilestoneProgression';
|
||||
payload: string | boolean | object | number | undefined;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user