1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

chore: new confirmation dialog for replacing release plans (#10720)

https://linear.app/unleash/issue/2-3931/add-a-confirmation-dialog-when-replacing-existing-release-plan

Adds a confirmation dialog when replacing an already active release
plan.

<img width="706" height="325" alt="image"
src="https://github.com/user-attachments/assets/f682809c-f563-4dca-9924-be1e9188c698"
/>
This commit is contained in:
Nuno Góis 2025-10-02 15:48:27 +01:00 committed by GitHub
parent 6c6d4c0ccc
commit df67c041fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 104 additions and 41 deletions

View File

@ -21,12 +21,13 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { formatUnknownError } from 'utils/formatUnknownError';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { LegacyReleasePlanReviewDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlanReviewDialog.tsx';
import { ReleasePlanPreview } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlanPreview.tsx';
import { ReleasePlanPreview } from './ReleasePlanPreview.tsx';
import {
FeatureStrategyMenuCards,
type StrategyFilterValue,
} from './FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx';
import { useUiFlag } from 'hooks/useUiFlag.ts';
import { ReleasePlanConfirmationDialog } from './ReleasePlanConfirmationDialog.tsx';
interface IFeatureStrategyMenuProps {
label: string;
@ -78,6 +79,8 @@ export const FeatureStrategyMenu = ({
useState<IReleasePlanTemplate>();
const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false);
const [releasePlanPreview, setReleasePlanPreview] = useState(false);
const [addReleasePlanConfirmationOpen, setAddReleasePlanConfirmationOpen] =
useState(false);
const dialogId = isStrategyMenuDialogOpen
? 'FeatureStrategyMenuDialog'
: undefined;
@ -86,13 +89,19 @@ export const FeatureStrategyMenu = ({
const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
const { refetch, releasePlans } = useReleasePlans(
projectId,
featureId,
environmentId,
);
const { addReleasePlanToFeature } = useReleasePlansApi();
const { isEnterprise } = useUiConfig();
const displayReleasePlanButton = isEnterprise();
const crProtected = isChangeRequestConfigured(environmentId);
const newStrategyModalEnabled = useUiFlag('newStrategyModal');
const activeReleasePlan = releasePlans[0];
const onClose = () => {
setIsStrategyMenuDialogOpen(false);
};
@ -121,8 +130,15 @@ export const FeatureStrategyMenu = ({
setIsStrategyMenuDialogOpen(true);
};
const addReleasePlan = async (template: IReleasePlanTemplate) => {
const addReleasePlan = async (
template: IReleasePlanTemplate,
confirmed?: boolean,
) => {
try {
if (newStrategyModalEnabled && !confirmed && activeReleasePlan) {
setAddReleasePlanConfirmationOpen(true);
return;
}
if (crProtected) {
await addChange(projectId, environmentId, {
feature: featureId,
@ -153,18 +169,19 @@ export const FeatureStrategyMenu = ({
refetch();
}
trackEvent('release-management', {
props: {
eventType: 'add-plan',
plan: template.name,
},
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
} finally {
setAddReleasePlanConfirmationOpen(false);
setAddReleasePlanOpen(false);
setSelectedTemplate(undefined);
onClose();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
@ -284,6 +301,7 @@ export const FeatureStrategyMenu = ({
projectId={projectId}
featureName={featureId}
environment={environmentId}
activeReleasePlan={activeReleasePlan}
crProtected={crProtected}
onBack={() => setReleasePlanPreview(false)}
onConfirm={() => {
@ -329,6 +347,7 @@ export const FeatureStrategyMenu = ({
)}
</Dialog>
{selectedTemplate && (
<>
<LegacyReleasePlanReviewDialog
open={addReleasePlanOpen}
setOpen={(open) => {
@ -346,6 +365,16 @@ export const FeatureStrategyMenu = ({
environment={environmentId}
crProtected={crProtected}
/>
<ReleasePlanConfirmationDialog
template={selectedTemplate}
crProtected={crProtected}
open={addReleasePlanConfirmationOpen}
setOpen={setAddReleasePlanConfirmationOpen}
onConfirm={() => {
addReleasePlan(selectedTemplate, true);
}}
/>
</>
)}
</StyledStrategyMenu>
);

View File

@ -0,0 +1,35 @@
import type React from 'react';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
interface IReleasePlanConfirmationDialogProps {
template: IReleasePlanTemplate;
crProtected: boolean;
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
onConfirm: () => void;
}
export const ReleasePlanConfirmationDialog = ({
template,
crProtected,
open,
setOpen,
onConfirm,
}: IReleasePlanConfirmationDialogProps) => (
<Dialogue
title='Replace release plan?'
open={open}
primaryButtonText={
crProtected ? 'Add suggestion to draft' : 'Add release plan'
}
secondaryButtonText='Close'
onClick={onConfirm}
onClose={() => {
setOpen(false);
}}
>
This environment currently has a release plan added. Do you want to
replace it with <strong>{template.name}</strong>?
</Dialogue>
);

View File

@ -1,5 +1,8 @@
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { ReleasePlan } from './ReleasePlan.tsx';
import type {
IReleasePlan,
IReleasePlanTemplate,
} from 'interfaces/releasePlans';
import { ReleasePlan } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx';
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
import {
styled,
@ -9,9 +12,8 @@ import {
DialogActions,
Button,
} from '@mui/material';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature.ts';
const StyledScrollableContent = styled(Box)(({ theme }) => ({
width: theme.breakpoints.values.md,
@ -38,6 +40,8 @@ interface IReleasePlanPreviewProps {
projectId: string;
featureName: string;
environment: string;
environmentEnabled?: boolean;
activeReleasePlan?: IReleasePlan;
crProtected?: boolean;
onConfirm: () => void;
onBack: () => void;
@ -48,19 +52,12 @@ export const ReleasePlanPreview = ({
projectId,
featureName,
environment,
activeReleasePlan,
crProtected,
onConfirm,
onBack,
}: IReleasePlanPreviewProps) => {
const { feature } = useFeature(projectId, featureName);
const { releasePlans, loading } = useReleasePlans(
projectId,
featureName,
environment,
);
const activeReleasePlan = releasePlans[0];
const environmentData = feature?.environments.find(
({ name }) => name === environment,
);
@ -72,8 +69,6 @@ export const ReleasePlanPreview = ({
environment,
);
if (loading) return null;
return (
<>
<StyledSubHeader>
@ -85,13 +80,17 @@ export const ReleasePlanPreview = ({
<StyledScrollableContent>
{activeReleasePlan && (
<Box sx={{ px: 4, pb: 2 }}>
<Alert severity='error'>
<Alert severity='warning'>
This feature environment currently has{' '}
<strong>{activeReleasePlan.name}</strong> -{' '}
<strong>{activeReleasePlan.name}</strong> (
<strong>
{activeReleasePlan.milestones[0].name}
{activeReleasePlan.milestones.find(
({ id }) =>
activeReleasePlan.activeMilestoneId ===
id,
)?.name ?? activeReleasePlan.milestones[0].name}
</strong>
{environmentEnabled ? ' running' : ' paused'}.
){environmentEnabled ? ' running' : ' paused'}.
Adding a new release plan will replace the existing
release plan.
</Alert>