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:
parent
1930c0f408
commit
6c74c994aa
@ -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 (
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user