1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-23 13:46:45 +02:00

feat: release plan review dialogue (#9712)

This commit is contained in:
Jaanus Sellin 2025-04-08 12:24:09 +03:00 committed by GitHub
parent 1930c0f408
commit 6c74c994aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 192 additions and 23 deletions

View File

@ -68,7 +68,7 @@ const StyledTopRow = styled('div')(({ theme }) => ({
interface IFeatureReleasePlanCardProps { interface IFeatureReleasePlanCardProps {
template: IReleasePlanTemplate; template: IReleasePlanTemplate;
onClick: () => void; onClick: () => void;
onPreviewClick?: (e: React.MouseEvent) => void; onPreviewClick: (e: React.MouseEvent) => void;
} }
export const FeatureReleasePlanCard = ({ export const FeatureReleasePlanCard = ({
@ -85,9 +85,7 @@ export const FeatureReleasePlanCard = ({
const handlePreviewClick = (e: React.MouseEvent) => { const handlePreviewClick = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
if (onPreviewClick) { onPreviewClick(e);
onPreviewClick(e);
}
}; };
return ( return (

View File

@ -14,7 +14,6 @@ import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { ReleasePlanAddDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddDialog';
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
@ -22,6 +21,8 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { OldFeatureStrategyMenuCards } from './FeatureStrategyMenuCards/OldFeatureStrategyMenuCards'; import { OldFeatureStrategyMenuCards } from './FeatureStrategyMenuCards/OldFeatureStrategyMenuCards';
import { ReleasePlanReviewDialog } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlanReviewDialog';
import { ReleasePlanAddDialog } from '../../FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddDialog';
interface IFeatureStrategyMenuProps { interface IFeatureStrategyMenuProps {
label: string; label: string;
@ -104,16 +105,14 @@ export const FeatureStrategyMenu = ({
setAnchor(event.currentTarget); setAnchor(event.currentTarget);
}; };
const addReleasePlan = async () => { const addReleasePlan = async (template: IReleasePlanTemplate) => {
if (!selectedTemplate) return;
try { try {
if (crProtected) { if (crProtected) {
await addChange(projectId, environmentId, { await addChange(projectId, environmentId, {
feature: featureId, feature: featureId,
action: 'addReleasePlan', action: 'addReleasePlan',
payload: { payload: {
templateId: selectedTemplate.id, templateId: template.id,
}, },
}); });
@ -126,7 +125,7 @@ export const FeatureStrategyMenu = ({
} else { } else {
await addReleasePlanToFeature( await addReleasePlanToFeature(
featureId, featureId,
selectedTemplate.id, template.id,
projectId, projectId,
environmentId, environmentId,
); );
@ -141,7 +140,7 @@ export const FeatureStrategyMenu = ({
trackEvent('release-management', { trackEvent('release-management', {
props: { props: {
eventType: 'add-plan', eventType: 'add-plan',
plan: selectedTemplate.name, plan: template.name,
}, },
}); });
} catch (error: unknown) { } catch (error: unknown) {
@ -240,6 +239,10 @@ export const FeatureStrategyMenu = ({
environmentId={environmentId} environmentId={environmentId}
onlyReleasePlans={onlyReleasePlans} onlyReleasePlans={onlyReleasePlans}
onAddReleasePlan={(template) => { onAddReleasePlan={(template) => {
setSelectedTemplate(template);
addReleasePlan(template);
}}
onReviewReleasePlan={(template) => {
setSelectedTemplate(template); setSelectedTemplate(template);
setAddReleasePlanOpen(true); setAddReleasePlanOpen(true);
}} }}
@ -258,18 +261,33 @@ export const FeatureStrategyMenu = ({
/> />
)} )}
</Popover> </Popover>
{selectedTemplate && ( {selectedTemplate &&
<ReleasePlanAddDialog (newStrategyDropdownEnabled ? (
open={addReleasePlanOpen} <ReleasePlanReviewDialog
setOpen={setAddReleasePlanOpen} open={addReleasePlanOpen}
onConfirm={addReleasePlan} setOpen={setAddReleasePlanOpen}
template={selectedTemplate} onConfirm={() => {
projectId={projectId} addReleasePlan(selectedTemplate);
featureName={featureId} }}
environment={environmentId} template={selectedTemplate}
crProtected={crProtected} projectId={projectId}
/> featureName={featureId}
)} environment={environmentId}
crProtected={crProtected}
/>
) : (
<ReleasePlanAddDialog
open={addReleasePlanOpen}
setOpen={setAddReleasePlanOpen}
onConfirm={() => {
addReleasePlan(selectedTemplate);
}}
template={selectedTemplate}
projectId={projectId}
featureName={featureId}
environment={environmentId}
/>
))}
</StyledStrategyMenu> </StyledStrategyMenu>
); );
}; };

View File

@ -23,6 +23,7 @@ interface IFeatureStrategyMenuCardsProps {
environmentId: string; environmentId: string;
onlyReleasePlans: boolean; onlyReleasePlans: boolean;
onAddReleasePlan: (template: IReleasePlanTemplate) => void; onAddReleasePlan: (template: IReleasePlanTemplate) => void;
onReviewReleasePlan: (template: IReleasePlanTemplate) => void;
onClose?: () => void; onClose?: () => void;
} }
@ -141,6 +142,7 @@ export const FeatureStrategyMenuCards = ({
environmentId, environmentId,
onlyReleasePlans, onlyReleasePlans,
onAddReleasePlan, onAddReleasePlan,
onReviewReleasePlan,
onClose, onClose,
}: IFeatureStrategyMenuCardsProps) => { }: IFeatureStrategyMenuCardsProps) => {
const { strategies } = useStrategies(); const { strategies } = useStrategies();
@ -239,6 +241,9 @@ export const FeatureStrategyMenuCards = ({
onClick={() => onClick={() =>
onAddReleasePlan(template) onAddReleasePlan(template)
} }
onPreviewClick={() =>
onReviewReleasePlan(template)
}
/> />
</CardWrapper> </CardWrapper>
))} ))}

View File

@ -0,0 +1,148 @@
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { ReleasePlan } from './ReleasePlan';
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
import {
styled,
Typography,
Alert,
Box,
IconButton,
Dialog,
DialogContent,
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 CloseIcon from '@mui/icons-material/Close';
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: theme.shape.borderRadiusLarge,
maxWidth: theme.spacing(85),
},
}));
const StyledDialogActions = styled(DialogActions)(({ theme }) => ({
padding: theme.spacing(2, 4, 4),
}));
const TopRow = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginBottom: theme.spacing(2),
}));
const BackButton = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
}));
const StyledBackIcon = styled(ArrowBackIcon)(({ theme }) => ({
marginRight: theme.spacing(1),
color: theme.palette.primary.main,
display: 'flex',
alignSelf: 'center',
}));
const BackText = styled(Typography)(({ theme }) => ({
fontWeight: theme.typography.fontWeightMedium,
display: 'flex',
alignItems: 'center',
lineHeight: 1,
}));
interface IReleasePlanAddDialogProps {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
onConfirm: () => void;
template: IReleasePlanTemplate;
projectId: string;
featureName: string;
environment: string;
crProtected?: boolean;
}
export const ReleasePlanReviewDialog = ({
open,
setOpen,
onConfirm,
template,
projectId,
featureName,
environment,
crProtected,
}: IReleasePlanAddDialogProps) => {
const { feature } = useFeature(projectId, featureName);
const { releasePlans } = useReleasePlans(
projectId,
featureName,
environment,
);
const activeReleasePlan = releasePlans[0];
const environmentData = feature?.environments.find(
({ name }) => name === environment,
);
const environmentEnabled = environmentData?.enabled;
const planPreview = useReleasePlanPreview(
template.id,
featureName,
environment,
);
const handleClose = () => setOpen(false);
return (
<StyledDialog open={open} onClose={handleClose} fullWidth maxWidth='md'>
<DialogContent>
<TopRow>
<BackButton onClick={handleClose}>
<StyledBackIcon />
<BackText variant='body2' color='primary'>
Go back
</BackText>
</BackButton>
<IconButton
size='small'
onClick={handleClose}
edge='end'
aria-label='close'
>
<CloseIcon fontSize='small' />
</IconButton>
</TopRow>
{activeReleasePlan && (
<Alert severity='error' sx={{ mb: 1 }}>
This feature environment currently has{' '}
<strong>{activeReleasePlan.name}</strong> -{' '}
<strong>{activeReleasePlan.milestones[0].name}</strong>
{environmentEnabled ? ' running' : ' paused'}. Adding a
new release plan will replace the existing release plan.
</Alert>
)}
<div>
<ReleasePlan plan={planPreview} readonly />
</div>
{crProtected && (
<Typography sx={{ mt: 4 }}>
<strong>Adding</strong> release template{' '}
<strong>{template?.name}</strong> to{' '}
<strong>{featureName}</strong> in{' '}
<strong>{environment}</strong>.
</Typography>
)}
</DialogContent>
<StyledDialogActions>
<Button variant='contained' color='primary' onClick={onConfirm}>
{crProtected ? 'Add suggestion to draft' : 'Use template'}
</Button>
</StyledDialogActions>
</StyledDialog>
);
};