mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
feat: add delete functionality for milestone progressions (#10770)
This commit is contained in:
parent
fce4c5bbab
commit
ce2ef4fe6f
@ -0,0 +1,34 @@
|
|||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
|
||||||
|
interface IDeleteProgressionDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
milestoneName: string;
|
||||||
|
isDeleting?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeleteProgressionDialog = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
milestoneName,
|
||||||
|
isDeleting = false,
|
||||||
|
}: IDeleteProgressionDialogProps) => (
|
||||||
|
<Dialogue
|
||||||
|
title='Remove automation?'
|
||||||
|
open={open}
|
||||||
|
primaryButtonText={isDeleting ? 'Removing...' : 'Remove automation'}
|
||||||
|
secondaryButtonText='Cancel'
|
||||||
|
onClick={onConfirm}
|
||||||
|
onClose={onClose}
|
||||||
|
disabledPrimaryButton={isDeleting}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You are about to remove the automation that progresses from{' '}
|
||||||
|
<strong>{milestoneName}</strong> to the next milestone.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p>This action cannot be undone.</p>
|
||||||
|
</Dialogue>
|
||||||
|
);
|
||||||
@ -24,6 +24,8 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|||||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
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 { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx';
|
||||||
|
|
||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
@ -106,6 +108,7 @@ export const ReleasePlan = ({
|
|||||||
);
|
);
|
||||||
const { removeReleasePlanFromFeature, startReleasePlanMilestone } =
|
const { removeReleasePlanFromFeature, startReleasePlanMilestone } =
|
||||||
useReleasePlansApi();
|
useReleasePlansApi();
|
||||||
|
const { deleteMilestoneProgression } = useMilestoneProgressionsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
@ -128,6 +131,9 @@ export const ReleasePlan = ({
|
|||||||
const [progressionFormOpenIndex, setProgressionFormOpenIndex] = useState<
|
const [progressionFormOpenIndex, setProgressionFormOpenIndex] = useState<
|
||||||
number | null
|
number | null
|
||||||
>(null);
|
>(null);
|
||||||
|
const [milestoneToDeleteProgression, setMilestoneToDeleteProgression] =
|
||||||
|
useState<IReleasePlanMilestone | null>(null);
|
||||||
|
const [isDeletingProgression, setIsDeletingProgression] = useState(false);
|
||||||
|
|
||||||
const onAddRemovePlanChangesConfirm = async () => {
|
const onAddRemovePlanChangesConfirm = async () => {
|
||||||
await addChange(projectId, environment, {
|
await addChange(projectId, environment, {
|
||||||
@ -244,6 +250,40 @@ export const ReleasePlan = ({
|
|||||||
setProgressionFormOpenIndex(null);
|
setProgressionFormOpenIndex(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteProgression = (milestone: IReleasePlanMilestone) => {
|
||||||
|
setMilestoneToDeleteProgression(milestone);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDeleteDialog = () => {
|
||||||
|
if (!isDeletingProgression) {
|
||||||
|
setMilestoneToDeleteProgression(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteProgressionConfirm = async () => {
|
||||||
|
if (!milestoneToDeleteProgression || isDeletingProgression) return;
|
||||||
|
|
||||||
|
setIsDeletingProgression(true);
|
||||||
|
try {
|
||||||
|
await deleteMilestoneProgression(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
milestoneToDeleteProgression.id,
|
||||||
|
);
|
||||||
|
await refetch();
|
||||||
|
setMilestoneToDeleteProgression(null);
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
text: 'Automation removed successfully',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setMilestoneToDeleteProgression(null);
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
} finally {
|
||||||
|
setIsDeletingProgression(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const activeIndex = milestones.findIndex(
|
const activeIndex = milestones.findIndex(
|
||||||
(milestone) => milestone.id === activeMilestoneId,
|
(milestone) => milestone.id === activeMilestoneId,
|
||||||
);
|
);
|
||||||
@ -302,9 +342,16 @@ export const ReleasePlan = ({
|
|||||||
onStartMilestone={onStartMilestone}
|
onStartMilestone={onStartMilestone}
|
||||||
showAutomation={
|
showAutomation={
|
||||||
milestoneProgressionsEnabled &&
|
milestoneProgressionsEnabled &&
|
||||||
isNotLastMilestone
|
isNotLastMilestone &&
|
||||||
|
!readonly
|
||||||
}
|
}
|
||||||
onAddAutomation={handleOpenProgressionForm}
|
onAddAutomation={handleOpenProgressionForm}
|
||||||
|
onDeleteAutomation={
|
||||||
|
milestone.transitionCondition
|
||||||
|
? () =>
|
||||||
|
handleDeleteProgression(milestone)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
automationForm={
|
automationForm={
|
||||||
isProgressionFormOpen ? (
|
isProgressionFormOpen ? (
|
||||||
<MilestoneProgressionForm
|
<MilestoneProgressionForm
|
||||||
@ -354,6 +401,15 @@ export const ReleasePlan = ({
|
|||||||
releasePlan={plan}
|
releasePlan={plan}
|
||||||
milestone={milestoneForChangeRequestDialog}
|
milestone={milestoneForChangeRequestDialog}
|
||||||
/>
|
/>
|
||||||
|
{milestoneToDeleteProgression && (
|
||||||
|
<DeleteProgressionDialog
|
||||||
|
open={milestoneToDeleteProgression !== null}
|
||||||
|
onClose={handleCloseDeleteDialog}
|
||||||
|
onConfirm={onDeleteProgressionConfirm}
|
||||||
|
milestoneName={milestoneToDeleteProgression.name}
|
||||||
|
isDeleting={isDeletingProgression}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -51,18 +51,22 @@ interface IMilestoneAutomationSectionProps {
|
|||||||
showAutomation?: boolean;
|
showAutomation?: boolean;
|
||||||
status?: MilestoneStatus;
|
status?: MilestoneStatus;
|
||||||
onAddAutomation?: () => void;
|
onAddAutomation?: () => void;
|
||||||
|
onDeleteAutomation?: () => void;
|
||||||
automationForm?: React.ReactNode;
|
automationForm?: React.ReactNode;
|
||||||
transitionCondition?: {
|
transitionCondition?: {
|
||||||
intervalMinutes: number;
|
intervalMinutes: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
milestoneName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MilestoneAutomationSection = ({
|
export const MilestoneAutomationSection = ({
|
||||||
showAutomation,
|
showAutomation,
|
||||||
status,
|
status,
|
||||||
onAddAutomation,
|
onAddAutomation,
|
||||||
|
onDeleteAutomation,
|
||||||
automationForm,
|
automationForm,
|
||||||
transitionCondition,
|
transitionCondition,
|
||||||
|
milestoneName,
|
||||||
}: IMilestoneAutomationSectionProps) => {
|
}: IMilestoneAutomationSectionProps) => {
|
||||||
if (!showAutomation) return null;
|
if (!showAutomation) return null;
|
||||||
|
|
||||||
@ -73,6 +77,8 @@ export const MilestoneAutomationSection = ({
|
|||||||
) : transitionCondition ? (
|
) : transitionCondition ? (
|
||||||
<MilestoneTransitionDisplay
|
<MilestoneTransitionDisplay
|
||||||
intervalMinutes={transitionCondition.intervalMinutes}
|
intervalMinutes={transitionCondition.intervalMinutes}
|
||||||
|
onDelete={onDeleteAutomation!}
|
||||||
|
milestoneName={milestoneName}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledAddAutomationButton
|
<StyledAddAutomationButton
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
import { styled } from '@mui/material';
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
|
import { IconButton, styled } from '@mui/material';
|
||||||
import { formatDuration, intervalToDuration } from 'date-fns';
|
import { formatDuration, intervalToDuration } from 'date-fns';
|
||||||
|
|
||||||
const StyledDisplayContainer = styled('div')(({ theme }) => ({
|
const StyledDisplayContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContentGroup = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledIcon = styled(BoltIcon)(({ theme }) => ({
|
const StyledIcon = styled(BoltIcon)(({ theme }) => ({
|
||||||
@ -24,6 +33,8 @@ const StyledText = styled('span')(({ theme }) => ({
|
|||||||
|
|
||||||
interface IMilestoneTransitionDisplayProps {
|
interface IMilestoneTransitionDisplayProps {
|
||||||
intervalMinutes: number;
|
intervalMinutes: number;
|
||||||
|
onDelete: () => void;
|
||||||
|
milestoneName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatInterval = (minutes: number): string => {
|
const formatInterval = (minutes: number): string => {
|
||||||
@ -42,14 +53,26 @@ const formatInterval = (minutes: number): string => {
|
|||||||
|
|
||||||
export const MilestoneTransitionDisplay = ({
|
export const MilestoneTransitionDisplay = ({
|
||||||
intervalMinutes,
|
intervalMinutes,
|
||||||
|
onDelete,
|
||||||
|
milestoneName,
|
||||||
}: IMilestoneTransitionDisplayProps) => {
|
}: IMilestoneTransitionDisplayProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledDisplayContainer>
|
<StyledDisplayContainer>
|
||||||
<StyledIcon />
|
<StyledContentGroup>
|
||||||
<StyledText>
|
<StyledIcon />
|
||||||
Proceed to the next milestone after{' '}
|
<StyledText>
|
||||||
{formatInterval(intervalMinutes)}
|
Proceed to the next milestone after{' '}
|
||||||
</StyledText>
|
{formatInterval(intervalMinutes)}
|
||||||
|
</StyledText>
|
||||||
|
</StyledContentGroup>
|
||||||
|
<IconButton
|
||||||
|
onClick={onDelete}
|
||||||
|
size='small'
|
||||||
|
aria-label={`Delete automation for ${milestoneName}`}
|
||||||
|
sx={{ padding: 0.5 }}
|
||||||
|
>
|
||||||
|
<DeleteOutlineIcon fontSize='small' />
|
||||||
|
</IconButton>
|
||||||
</StyledDisplayContainer>
|
</StyledDisplayContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -78,6 +78,7 @@ interface IReleasePlanMilestoneProps {
|
|||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
showAutomation?: boolean;
|
showAutomation?: boolean;
|
||||||
onAddAutomation?: () => void;
|
onAddAutomation?: () => void;
|
||||||
|
onDeleteAutomation?: () => void;
|
||||||
automationForm?: React.ReactNode;
|
automationForm?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +89,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
readonly,
|
readonly,
|
||||||
showAutomation,
|
showAutomation,
|
||||||
onAddAutomation,
|
onAddAutomation,
|
||||||
|
onDeleteAutomation,
|
||||||
automationForm,
|
automationForm,
|
||||||
}: IReleasePlanMilestoneProps) => {
|
}: IReleasePlanMilestoneProps) => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
@ -117,8 +119,10 @@ export const ReleasePlanMilestone = ({
|
|||||||
showAutomation={showAutomation}
|
showAutomation={showAutomation}
|
||||||
status={status}
|
status={status}
|
||||||
onAddAutomation={onAddAutomation}
|
onAddAutomation={onAddAutomation}
|
||||||
|
onDeleteAutomation={onDeleteAutomation}
|
||||||
automationForm={automationForm}
|
automationForm={automationForm}
|
||||||
transitionCondition={milestone.transitionCondition}
|
transitionCondition={milestone.transitionCondition}
|
||||||
|
milestoneName={milestone.name}
|
||||||
/>
|
/>
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
@ -174,8 +178,10 @@ export const ReleasePlanMilestone = ({
|
|||||||
showAutomation={showAutomation}
|
showAutomation={showAutomation}
|
||||||
status={status}
|
status={status}
|
||||||
onAddAutomation={onAddAutomation}
|
onAddAutomation={onAddAutomation}
|
||||||
|
onDeleteAutomation={onDeleteAutomation}
|
||||||
automationForm={automationForm}
|
automationForm={automationForm}
|
||||||
transitionCondition={milestone.transitionCondition}
|
transitionCondition={milestone.transitionCondition}
|
||||||
|
milestoneName={milestone.name}
|
||||||
/>
|
/>
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,8 +25,27 @@ export const useMilestoneProgressionsApi = () => {
|
|||||||
await makeRequest(req.caller, req.id);
|
await makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteMilestoneProgression = async (
|
||||||
|
projectId: string,
|
||||||
|
environment: string,
|
||||||
|
sourceMilestoneId: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
const requestId = 'deleteMilestoneProgression';
|
||||||
|
const path = `api/admin/projects/${projectId}/environments/${environment}/progressions/${sourceMilestoneId}`;
|
||||||
|
const req = createRequest(
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
requestId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createMilestoneProgression,
|
createMilestoneProgression,
|
||||||
|
deleteMilestoneProgression,
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user