mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
refactor: consolidate release plan change request dialogs (#10817)
This commit is contained in:
parent
9096340afb
commit
045ef5a20e
@ -1,70 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,131 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { styled, Button, Alert } from '@mui/material';
|
||||
import type {
|
||||
IReleasePlan,
|
||||
IReleasePlanMilestone,
|
||||
} from 'interfaces/releasePlans';
|
||||
import type { CreateMilestoneProgressionSchema } from 'openapi';
|
||||
import { getTimeValueAndUnitFromMinutes } from '../hooks/useMilestoneProgressionForm.js';
|
||||
|
||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
type ChangeRequestAction =
|
||||
| {
|
||||
type: 'removeReleasePlan';
|
||||
environmentActive: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'startMilestone';
|
||||
milestone: IReleasePlanMilestone;
|
||||
}
|
||||
| {
|
||||
type: 'createMilestoneProgression';
|
||||
payload: CreateMilestoneProgressionSchema;
|
||||
};
|
||||
|
||||
interface IReleasePlanChangeRequestDialogProps {
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
releasePlan: IReleasePlan;
|
||||
action: ChangeRequestAction | null;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const ReleasePlanChangeRequestDialog = ({
|
||||
featureId,
|
||||
environmentId,
|
||||
releasePlan,
|
||||
action,
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: IReleasePlanChangeRequestDialogProps) => {
|
||||
if (!action) return null;
|
||||
|
||||
const renderContent = () => {
|
||||
switch (action.type) {
|
||||
case 'removeReleasePlan':
|
||||
return (
|
||||
<>
|
||||
{action.environmentActive && (
|
||||
<Alert severity='error' sx={{ mb: 2 }}>
|
||||
This release plan currently has one active
|
||||
milestone. Removing the release plan will change
|
||||
which users receive access to the feature.
|
||||
</Alert>
|
||||
)}
|
||||
<p>
|
||||
<StyledBoldSpan>Remove</StyledBoldSpan> release plan{' '}
|
||||
<StyledBoldSpan>{releasePlan.name}</StyledBoldSpan>{' '}
|
||||
from <StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
|
||||
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'startMilestone':
|
||||
return (
|
||||
<p>
|
||||
<StyledBoldSpan>Start</StyledBoldSpan> milestone{' '}
|
||||
<StyledBoldSpan>{action.milestone.name}</StyledBoldSpan>{' '}
|
||||
in release plan{' '}
|
||||
<StyledBoldSpan>{releasePlan.name}</StyledBoldSpan> for{' '}
|
||||
<StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
|
||||
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
|
||||
</p>
|
||||
);
|
||||
|
||||
case 'createMilestoneProgression': {
|
||||
const sourceMilestone = releasePlan.milestones.find(
|
||||
(milestone) =>
|
||||
milestone.id === action.payload.sourceMilestone,
|
||||
);
|
||||
const targetMilestone = releasePlan.milestones.find(
|
||||
(milestone) =>
|
||||
milestone.id === action.payload.targetMilestone,
|
||||
);
|
||||
|
||||
const { value, unit } = getTimeValueAndUnitFromMinutes(
|
||||
action.payload.transitionCondition.intervalMinutes,
|
||||
);
|
||||
const timeInterval = `${value} ${unit}`;
|
||||
|
||||
return (
|
||||
<p>
|
||||
Create automation to proceed from{' '}
|
||||
<StyledBoldSpan>{sourceMilestone?.name}</StyledBoldSpan>{' '}
|
||||
to{' '}
|
||||
<StyledBoldSpan>{targetMilestone?.name}</StyledBoldSpan>{' '}
|
||||
after <StyledBoldSpan>{timeInterval}</StyledBoldSpan> in{' '}
|
||||
{environmentId}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialogue
|
||||
title='Request changes'
|
||||
open={isOpen}
|
||||
secondaryButtonText='Cancel'
|
||||
onClose={onClose}
|
||||
customButton={
|
||||
<Button
|
||||
color='primary'
|
||||
variant='contained'
|
||||
onClick={onConfirm}
|
||||
autoFocus={true}
|
||||
>
|
||||
Add suggestion to draft
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{renderContent()}
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
@ -1,62 +0,0 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { styled, Button, Alert } from '@mui/material';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
|
||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
interface IRemoveReleasePlanChangeRequestDialogProps {
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
releasePlan?: IReleasePlan | undefined;
|
||||
environmentActive: boolean;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClosing: () => void;
|
||||
}
|
||||
|
||||
export const RemoveReleasePlanChangeRequestDialog = ({
|
||||
featureId,
|
||||
environmentId,
|
||||
releasePlan,
|
||||
environmentActive,
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClosing,
|
||||
}: IRemoveReleasePlanChangeRequestDialogProps) => {
|
||||
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>
|
||||
}
|
||||
>
|
||||
<>
|
||||
{environmentActive && (
|
||||
<Alert severity='error' sx={{ mb: 2 }}>
|
||||
This release plan currently has one active milestone.
|
||||
Removing the release plan will change which users
|
||||
receive access to the feature.
|
||||
</Alert>
|
||||
)}
|
||||
<p>
|
||||
<StyledBoldSpan>Remove</StyledBoldSpan> release plan{' '}
|
||||
<StyledBoldSpan>{releasePlan?.name}</StyledBoldSpan> from{' '}
|
||||
<StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
|
||||
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
|
||||
</p>
|
||||
</>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
@ -1,57 +0,0 @@
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { styled, Button } from '@mui/material';
|
||||
import type {
|
||||
IReleasePlan,
|
||||
IReleasePlanMilestone,
|
||||
} from 'interfaces/releasePlans';
|
||||
|
||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
interface IStartMilestoneChangeRequestDialogProps {
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
releasePlan?: IReleasePlan | undefined;
|
||||
milestone?: IReleasePlanMilestone | undefined;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClosing: () => void;
|
||||
}
|
||||
|
||||
export const StartMilestoneChangeRequestDialog = ({
|
||||
featureId,
|
||||
environmentId,
|
||||
releasePlan,
|
||||
milestone,
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClosing,
|
||||
}: IStartMilestoneChangeRequestDialogProps) => {
|
||||
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>
|
||||
<StyledBoldSpan>Start</StyledBoldSpan> milestone{' '}
|
||||
<StyledBoldSpan>{milestone?.name}</StyledBoldSpan> in release
|
||||
plan <StyledBoldSpan>{releasePlan?.name}</StyledBoldSpan> for{' '}
|
||||
<StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
|
||||
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
|
||||
</p>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
||||
@ -18,9 +18,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
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 { ReleasePlanChangeRequestDialog } from './ChangeRequest/ReleasePlanChangeRequestDialog.tsx';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
@ -119,22 +117,15 @@ export const ReleasePlan = ({
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
const [changeRequestDialogRemoveOpen, setChangeRequestDialogRemoveOpen] =
|
||||
useState(false);
|
||||
const [
|
||||
changeRequestDialogStartMilestoneOpen,
|
||||
setChangeRequestDialogStartMilestoneOpen,
|
||||
] = useState(false);
|
||||
const [
|
||||
changeRequestDialogCreateProgressionOpen,
|
||||
setChangeRequestDialogCreateProgressionOpen,
|
||||
] = useState(false);
|
||||
const [
|
||||
milestoneForChangeRequestDialog,
|
||||
setMilestoneForChangeRequestDialog,
|
||||
] = useState<IReleasePlanMilestone>();
|
||||
const [progressionDataForCR, setProgressionDataForCR] =
|
||||
useState<CreateMilestoneProgressionSchema | null>(null);
|
||||
const [changeRequestAction, setChangeRequestAction] = useState<
|
||||
| { type: 'removeReleasePlan'; environmentActive: boolean }
|
||||
| { type: 'startMilestone'; milestone: IReleasePlanMilestone }
|
||||
| {
|
||||
type: 'createMilestoneProgression';
|
||||
payload: CreateMilestoneProgressionSchema;
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { addChange } = useChangeRequestApi();
|
||||
const { refetch: refetchChangeRequests } =
|
||||
@ -147,14 +138,40 @@ export const ReleasePlan = ({
|
||||
useState<IReleasePlanMilestone | null>(null);
|
||||
const [isDeletingProgression, setIsDeletingProgression] = useState(false);
|
||||
|
||||
const onAddRemovePlanChangesConfirm = async () => {
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'deleteReleasePlan',
|
||||
payload: {
|
||||
planId: plan.id,
|
||||
},
|
||||
});
|
||||
const onChangeRequestConfirm = async () => {
|
||||
if (!changeRequestAction) return;
|
||||
|
||||
switch (changeRequestAction.type) {
|
||||
case 'removeReleasePlan':
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'deleteReleasePlan',
|
||||
payload: {
|
||||
planId: plan.id,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 'startMilestone':
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'startMilestone',
|
||||
payload: {
|
||||
planId: plan.id,
|
||||
milestoneId: changeRequestAction.milestone.id,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 'createMilestoneProgression':
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'createMilestoneProgression',
|
||||
payload: changeRequestAction.payload,
|
||||
});
|
||||
setProgressionFormOpenIndex(null);
|
||||
break;
|
||||
}
|
||||
|
||||
await refetchChangeRequests();
|
||||
|
||||
@ -163,53 +180,15 @@ export const ReleasePlan = ({
|
||||
text: 'Added to draft',
|
||||
});
|
||||
|
||||
setChangeRequestDialogRemoveOpen(false);
|
||||
};
|
||||
|
||||
const onAddStartMilestoneChangesConfirm = async () => {
|
||||
await addChange(projectId, environment, {
|
||||
feature: featureName,
|
||||
action: 'startMilestone',
|
||||
payload: {
|
||||
planId: plan.id,
|
||||
milestoneId: milestoneForChangeRequestDialog?.id,
|
||||
},
|
||||
});
|
||||
|
||||
await refetchChangeRequests();
|
||||
|
||||
setToastData({
|
||||
type: 'success',
|
||||
text: 'Added to draft',
|
||||
});
|
||||
|
||||
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);
|
||||
setChangeRequestAction(null);
|
||||
};
|
||||
|
||||
const confirmRemoveReleasePlan = () => {
|
||||
if (isChangeRequestConfigured(environment)) {
|
||||
setChangeRequestDialogRemoveOpen(true);
|
||||
setChangeRequestAction({
|
||||
type: 'removeReleasePlan',
|
||||
environmentActive: !environmentIsDisabled,
|
||||
});
|
||||
} else {
|
||||
setRemoveOpen(true);
|
||||
}
|
||||
@ -244,8 +223,10 @@ export const ReleasePlan = ({
|
||||
|
||||
const onStartMilestone = async (milestone: IReleasePlanMilestone) => {
|
||||
if (isChangeRequestConfigured(environment)) {
|
||||
setMilestoneForChangeRequestDialog(milestone);
|
||||
setChangeRequestDialogStartMilestoneOpen(true);
|
||||
setChangeRequestAction({
|
||||
type: 'startMilestone',
|
||||
milestone,
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
await startReleasePlanMilestone(
|
||||
@ -286,8 +267,10 @@ export const ReleasePlan = ({
|
||||
const handleProgressionChangeRequestSubmit = (
|
||||
payload: CreateMilestoneProgressionSchema,
|
||||
) => {
|
||||
setProgressionDataForCR(payload);
|
||||
setChangeRequestDialogCreateProgressionOpen(true);
|
||||
setChangeRequestAction({
|
||||
type: 'createMilestoneProgression',
|
||||
payload,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteProgression = (milestone: IReleasePlanMilestone) => {
|
||||
@ -437,40 +420,15 @@ export const ReleasePlan = ({
|
||||
onConfirm={onRemoveConfirm}
|
||||
environmentActive={!environmentIsDisabled}
|
||||
/>
|
||||
<RemoveReleasePlanChangeRequestDialog
|
||||
environmentId={environment}
|
||||
<ReleasePlanChangeRequestDialog
|
||||
featureId={featureName}
|
||||
isOpen={changeRequestDialogRemoveOpen}
|
||||
onConfirm={onAddRemovePlanChangesConfirm}
|
||||
onClosing={() => setChangeRequestDialogRemoveOpen(false)}
|
||||
releasePlan={plan}
|
||||
environmentActive={!environmentIsDisabled}
|
||||
/>
|
||||
<StartMilestoneChangeRequestDialog
|
||||
environmentId={environment}
|
||||
featureId={featureName}
|
||||
isOpen={changeRequestDialogStartMilestoneOpen}
|
||||
onConfirm={onAddStartMilestoneChangesConfirm}
|
||||
onClosing={() => {
|
||||
setMilestoneForChangeRequestDialog(undefined);
|
||||
setChangeRequestDialogStartMilestoneOpen(false);
|
||||
}}
|
||||
releasePlan={plan}
|
||||
milestone={milestoneForChangeRequestDialog}
|
||||
action={changeRequestAction}
|
||||
isOpen={changeRequestAction !== null}
|
||||
onConfirm={onChangeRequestConfirm}
|
||||
onClose={() => setChangeRequestAction(null)}
|
||||
/>
|
||||
{progressionDataForCR && (
|
||||
<CreateMilestoneProgressionChangeRequestDialog
|
||||
environmentId={environment}
|
||||
isOpen={changeRequestDialogCreateProgressionOpen}
|
||||
onConfirm={onAddCreateProgressionChangesConfirm}
|
||||
onClosing={() => {
|
||||
setChangeRequestDialogCreateProgressionOpen(false);
|
||||
setProgressionDataForCR(null);
|
||||
}}
|
||||
releasePlan={plan}
|
||||
payload={progressionDataForCR}
|
||||
/>
|
||||
)}
|
||||
{milestoneToDeleteProgression && (
|
||||
<DeleteProgressionDialog
|
||||
open={milestoneToDeleteProgression !== null}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user