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,23 +347,34 @@ export const FeatureStrategyMenu = ({ | |||||||
|                 )} |                 )} | ||||||
|             </Dialog> |             </Dialog> | ||||||
|             {selectedTemplate && ( |             {selectedTemplate && ( | ||||||
|                 <LegacyReleasePlanReviewDialog |                 <> | ||||||
|                     open={addReleasePlanOpen} |                     <LegacyReleasePlanReviewDialog | ||||||
|                     setOpen={(open) => { |                         open={addReleasePlanOpen} | ||||||
|                         setAddReleasePlanOpen(open); |                         setOpen={(open) => { | ||||||
|                         if (!open) { |                             setAddReleasePlanOpen(open); | ||||||
|                             setIsStrategyMenuDialogOpen(true); |                             if (!open) { | ||||||
|                         } |                                 setIsStrategyMenuDialogOpen(true); | ||||||
|                     }} |                             } | ||||||
|                     onConfirm={() => { |                         }} | ||||||
|                         addReleasePlan(selectedTemplate); |                         onConfirm={() => { | ||||||
|                     }} |                             addReleasePlan(selectedTemplate); | ||||||
|                     template={selectedTemplate} |                         }} | ||||||
|                     projectId={projectId} |                         template={selectedTemplate} | ||||||
|                     featureName={featureId} |                         projectId={projectId} | ||||||
|                     environment={environmentId} |                         featureName={featureId} | ||||||
|                     crProtected={crProtected} |                         environment={environmentId} | ||||||
|                 /> |                         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