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:
parent
6c6d4c0ccc
commit
df67c041fc
@ -21,12 +21,13 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { LegacyReleasePlanReviewDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlanReviewDialog.tsx';
|
import { LegacyReleasePlanReviewDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlanReviewDialog.tsx';
|
||||||
import { ReleasePlanPreview } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlanPreview.tsx';
|
import { ReleasePlanPreview } from './ReleasePlanPreview.tsx';
|
||||||
import {
|
import {
|
||||||
FeatureStrategyMenuCards,
|
FeatureStrategyMenuCards,
|
||||||
type StrategyFilterValue,
|
type StrategyFilterValue,
|
||||||
} from './FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx';
|
} from './FeatureStrategyMenuCards/FeatureStrategyMenuCards.tsx';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||||
|
import { ReleasePlanConfirmationDialog } from './ReleasePlanConfirmationDialog.tsx';
|
||||||
|
|
||||||
interface IFeatureStrategyMenuProps {
|
interface IFeatureStrategyMenuProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -78,6 +79,8 @@ export const FeatureStrategyMenu = ({
|
|||||||
useState<IReleasePlanTemplate>();
|
useState<IReleasePlanTemplate>();
|
||||||
const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false);
|
const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false);
|
||||||
const [releasePlanPreview, setReleasePlanPreview] = useState(false);
|
const [releasePlanPreview, setReleasePlanPreview] = useState(false);
|
||||||
|
const [addReleasePlanConfirmationOpen, setAddReleasePlanConfirmationOpen] =
|
||||||
|
useState(false);
|
||||||
const dialogId = isStrategyMenuDialogOpen
|
const dialogId = isStrategyMenuDialogOpen
|
||||||
? 'FeatureStrategyMenuDialog'
|
? 'FeatureStrategyMenuDialog'
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -86,13 +89,19 @@ export const FeatureStrategyMenu = ({
|
|||||||
const { addChange } = useChangeRequestApi();
|
const { addChange } = useChangeRequestApi();
|
||||||
const { refetch: refetchChangeRequests } =
|
const { refetch: refetchChangeRequests } =
|
||||||
usePendingChangeRequests(projectId);
|
usePendingChangeRequests(projectId);
|
||||||
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
|
const { refetch, releasePlans } = useReleasePlans(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
environmentId,
|
||||||
|
);
|
||||||
const { addReleasePlanToFeature } = useReleasePlansApi();
|
const { addReleasePlanToFeature } = useReleasePlansApi();
|
||||||
const { isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
const displayReleasePlanButton = isEnterprise();
|
const displayReleasePlanButton = isEnterprise();
|
||||||
const crProtected = isChangeRequestConfigured(environmentId);
|
const crProtected = isChangeRequestConfigured(environmentId);
|
||||||
const newStrategyModalEnabled = useUiFlag('newStrategyModal');
|
const newStrategyModalEnabled = useUiFlag('newStrategyModal');
|
||||||
|
|
||||||
|
const activeReleasePlan = releasePlans[0];
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setIsStrategyMenuDialogOpen(false);
|
setIsStrategyMenuDialogOpen(false);
|
||||||
};
|
};
|
||||||
@ -121,8 +130,15 @@ export const FeatureStrategyMenu = ({
|
|||||||
setIsStrategyMenuDialogOpen(true);
|
setIsStrategyMenuDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addReleasePlan = async (template: IReleasePlanTemplate) => {
|
const addReleasePlan = async (
|
||||||
|
template: IReleasePlanTemplate,
|
||||||
|
confirmed?: boolean,
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
|
if (newStrategyModalEnabled && !confirmed && activeReleasePlan) {
|
||||||
|
setAddReleasePlanConfirmationOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (crProtected) {
|
if (crProtected) {
|
||||||
await addChange(projectId, environmentId, {
|
await addChange(projectId, environmentId, {
|
||||||
feature: featureId,
|
feature: featureId,
|
||||||
@ -153,18 +169,19 @@ export const FeatureStrategyMenu = ({
|
|||||||
|
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
trackEvent('release-management', {
|
trackEvent('release-management', {
|
||||||
props: {
|
props: {
|
||||||
eventType: 'add-plan',
|
eventType: 'add-plan',
|
||||||
plan: template.name,
|
plan: template.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
setAddReleasePlanConfirmationOpen(false);
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
} finally {
|
|
||||||
setAddReleasePlanOpen(false);
|
setAddReleasePlanOpen(false);
|
||||||
setSelectedTemplate(undefined);
|
setSelectedTemplate(undefined);
|
||||||
onClose();
|
onClose();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -284,6 +301,7 @@ export const FeatureStrategyMenu = ({
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
featureName={featureId}
|
featureName={featureId}
|
||||||
environment={environmentId}
|
environment={environmentId}
|
||||||
|
activeReleasePlan={activeReleasePlan}
|
||||||
crProtected={crProtected}
|
crProtected={crProtected}
|
||||||
onBack={() => setReleasePlanPreview(false)}
|
onBack={() => setReleasePlanPreview(false)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
@ -329,6 +347,7 @@ export const FeatureStrategyMenu = ({
|
|||||||
)}
|
)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{selectedTemplate && (
|
{selectedTemplate && (
|
||||||
|
<>
|
||||||
<LegacyReleasePlanReviewDialog
|
<LegacyReleasePlanReviewDialog
|
||||||
open={addReleasePlanOpen}
|
open={addReleasePlanOpen}
|
||||||
setOpen={(open) => {
|
setOpen={(open) => {
|
||||||
@ -346,6 +365,16 @@ export const FeatureStrategyMenu = ({
|
|||||||
environment={environmentId}
|
environment={environmentId}
|
||||||
crProtected={crProtected}
|
crProtected={crProtected}
|
||||||
/>
|
/>
|
||||||
|
<ReleasePlanConfirmationDialog
|
||||||
|
template={selectedTemplate}
|
||||||
|
crProtected={crProtected}
|
||||||
|
open={addReleasePlanConfirmationOpen}
|
||||||
|
setOpen={setAddReleasePlanConfirmationOpen}
|
||||||
|
onConfirm={() => {
|
||||||
|
addReleasePlan(selectedTemplate, true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledStrategyMenu>
|
</StyledStrategyMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
@ -1,5 +1,8 @@
|
|||||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
import type {
|
||||||
import { ReleasePlan } from './ReleasePlan.tsx';
|
IReleasePlan,
|
||||||
|
IReleasePlanTemplate,
|
||||||
|
} from 'interfaces/releasePlans';
|
||||||
|
import { ReleasePlan } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx';
|
||||||
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
|
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
|
||||||
import {
|
import {
|
||||||
styled,
|
styled,
|
||||||
@ -9,9 +12,8 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
Button,
|
Button,
|
||||||
} from '@mui/material';
|
} 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 ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature.ts';
|
||||||
|
|
||||||
const StyledScrollableContent = styled(Box)(({ theme }) => ({
|
const StyledScrollableContent = styled(Box)(({ theme }) => ({
|
||||||
width: theme.breakpoints.values.md,
|
width: theme.breakpoints.values.md,
|
||||||
@ -38,6 +40,8 @@ interface IReleasePlanPreviewProps {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
featureName: string;
|
featureName: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
environmentEnabled?: boolean;
|
||||||
|
activeReleasePlan?: IReleasePlan;
|
||||||
crProtected?: boolean;
|
crProtected?: boolean;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
@ -48,19 +52,12 @@ export const ReleasePlanPreview = ({
|
|||||||
projectId,
|
projectId,
|
||||||
featureName,
|
featureName,
|
||||||
environment,
|
environment,
|
||||||
|
activeReleasePlan,
|
||||||
crProtected,
|
crProtected,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onBack,
|
onBack,
|
||||||
}: IReleasePlanPreviewProps) => {
|
}: IReleasePlanPreviewProps) => {
|
||||||
const { feature } = useFeature(projectId, featureName);
|
const { feature } = useFeature(projectId, featureName);
|
||||||
const { releasePlans, loading } = useReleasePlans(
|
|
||||||
projectId,
|
|
||||||
featureName,
|
|
||||||
environment,
|
|
||||||
);
|
|
||||||
|
|
||||||
const activeReleasePlan = releasePlans[0];
|
|
||||||
|
|
||||||
const environmentData = feature?.environments.find(
|
const environmentData = feature?.environments.find(
|
||||||
({ name }) => name === environment,
|
({ name }) => name === environment,
|
||||||
);
|
);
|
||||||
@ -72,8 +69,6 @@ export const ReleasePlanPreview = ({
|
|||||||
environment,
|
environment,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledSubHeader>
|
<StyledSubHeader>
|
||||||
@ -85,13 +80,17 @@ export const ReleasePlanPreview = ({
|
|||||||
<StyledScrollableContent>
|
<StyledScrollableContent>
|
||||||
{activeReleasePlan && (
|
{activeReleasePlan && (
|
||||||
<Box sx={{ px: 4, pb: 2 }}>
|
<Box sx={{ px: 4, pb: 2 }}>
|
||||||
<Alert severity='error'>
|
<Alert severity='warning'>
|
||||||
This feature environment currently has{' '}
|
This feature environment currently has{' '}
|
||||||
<strong>{activeReleasePlan.name}</strong> -{' '}
|
<strong>{activeReleasePlan.name}</strong> (
|
||||||
<strong>
|
<strong>
|
||||||
{activeReleasePlan.milestones[0].name}
|
{activeReleasePlan.milestones.find(
|
||||||
|
({ id }) =>
|
||||||
|
activeReleasePlan.activeMilestoneId ===
|
||||||
|
id,
|
||||||
|
)?.name ?? activeReleasePlan.milestones[0].name}
|
||||||
</strong>
|
</strong>
|
||||||
{environmentEnabled ? ' running' : ' paused'}.
|
){environmentEnabled ? ' running' : ' paused'}.
|
||||||
Adding a new release plan will replace the existing
|
Adding a new release plan will replace the existing
|
||||||
release plan.
|
release plan.
|
||||||
</Alert>
|
</Alert>
|
||||||
Loading…
Reference in New Issue
Block a user