1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

feat: implement dialogs for changerequest milestone handling and removing release plans (#9240)

This commit is contained in:
David Leek 2025-02-06 16:45:24 +01:00 committed by GitHub
parent 61f8236711
commit e689e2e3d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 278 additions and 48 deletions

View File

@ -11,8 +11,11 @@ import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrate
import MoreVert from '@mui/icons-material/MoreVert';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import { ReleasePlanAddChangeRequestDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddChangeRequestDialog';
import { ReleasePlanAddChangeRequestDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/ReleasePlanAddChangeRequestDialog';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import useToast from 'hooks/useToast';
interface IFeatureStrategyMenuProps {
label: string;
@ -57,6 +60,10 @@ export const FeatureStrategyMenu = ({
const isPopoverOpen = Boolean(anchor);
const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
const flagOverviewRedesignEnabled = useUiFlag('flagOverviewRedesign');
const { setToastData } = useToast();
const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const onClose = () => {
setAnchor(undefined);
@ -75,6 +82,25 @@ export const FeatureStrategyMenu = ({
setAnchor(event.currentTarget);
};
const addReleasePlanToChangeRequest = async () => {
addChange(projectId, environmentId, {
feature: featureId,
action: 'addReleasePlan',
payload: {
templateId: templateForChangeRequestDialog?.id,
},
});
refetchChangeRequests();
setToastData({
type: 'success',
text: 'Added to draft',
});
setTemplateForChangeRequestDialog(undefined);
};
const createStrategyPath = formatCreateStrategyPath(
projectId,
featureId,
@ -188,8 +214,9 @@ export const FeatureStrategyMenu = ({
/>
</Popover>
<ReleasePlanAddChangeRequestDialog
projectId={projectId}
onConfirm={addReleasePlanToChangeRequest}
onClosing={() => setTemplateForChangeRequestDialog(undefined)}
isOpen={Boolean(templateForChangeRequestDialog)}
featureId={featureId}
environmentId={environmentId}
releaseTemplate={templateForChangeRequestDialog}

View File

@ -1,60 +1,39 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import useToast from 'hooks/useToast';
import { styled, Button } from '@mui/material';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
const StyledBoldSpan = styled('span')(({ theme }) => ({
fontWeight: theme.typography.fontWeightBold,
}));
interface IReleasePlanAddChangeRequestDialogProps {
projectId: string;
featureId: string;
environmentId: string;
releaseTemplate: IReleasePlanTemplate | undefined;
releaseTemplate?: IReleasePlanTemplate;
isOpen: boolean;
onConfirm: () => Promise<void>;
onClosing: () => void;
}
export const ReleasePlanAddChangeRequestDialog = ({
projectId,
featureId,
environmentId,
releaseTemplate,
isOpen,
onConfirm,
onClosing,
}: IReleasePlanAddChangeRequestDialogProps) => {
const { setToastData } = useToast();
const { addChange } = useChangeRequestApi();
const addReleasePlanToChangeRequest = async () => {
addChange(projectId, environmentId, {
feature: featureId,
action: 'addReleasePlan',
payload: {
templateId: releaseTemplate?.id,
},
});
setToastData({
type: 'success',
text: 'Added to draft',
});
onClosing();
};
return (
<Dialogue
title='Request changes'
open={Boolean(releaseTemplate)}
open={isOpen}
secondaryButtonText='Cancel'
onClose={() => {
onClosing();
}}
onClose={onClosing}
customButton={
<Button
color='primary'
variant='contained'
onClick={addReleasePlanToChangeRequest}
onClick={onConfirm}
autoFocus={true}
>
Add suggestion to draft

View File

@ -0,0 +1,62 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { styled, Button, Alert } from '@mui/material';
import type { IReleasePlan } from 'interfaces/releasePlans';
const StyledBoldSpan = styled('span')(({ theme }) => ({
fontWeight: theme.typography.fontWeightBold,
}));
interface IRemoveReleasePlanChangeRequestDialogProps {
featureId: string;
environmentId: string;
releasePlan?: IReleasePlan | undefined;
environmentActive: boolean;
isOpen: boolean;
onConfirm: () => Promise<void>;
onClosing: () => void;
}
export const RemoveReleasePlanChangeRequestDialog = ({
featureId,
environmentId,
releasePlan,
environmentActive,
isOpen,
onConfirm,
onClosing,
}: IRemoveReleasePlanChangeRequestDialogProps) => {
return (
<Dialogue
title='Request changes'
open={isOpen}
secondaryButtonText='Cancel'
onClose={onClosing}
customButton={
<Button
color='primary'
variant='contained'
onClick={onConfirm}
autoFocus={true}
>
Add suggestion to draft
</Button>
}
>
<>
{environmentActive && (
<Alert severity='error' sx={{ mb: 2 }}>
This release plan currently has one active milestone.
Removing the release plan will change which users
receive access to the feature.
</Alert>
)}
<p>
<StyledBoldSpan>Remove</StyledBoldSpan> release plan{' '}
<StyledBoldSpan>{releasePlan?.name}</StyledBoldSpan> from{' '}
<StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
</p>
</>
</Dialogue>
);
};

View File

@ -0,0 +1,57 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { styled, Button } from '@mui/material';
import type {
IReleasePlan,
IReleasePlanMilestone,
} from 'interfaces/releasePlans';
const StyledBoldSpan = styled('span')(({ theme }) => ({
fontWeight: theme.typography.fontWeightBold,
}));
interface IStartMilestoneChangeRequestDialogProps {
featureId: string;
environmentId: string;
releasePlan?: IReleasePlan | undefined;
milestone?: IReleasePlanMilestone | undefined;
isOpen: boolean;
onConfirm: () => Promise<void>;
onClosing: () => void;
}
export const StartMilestoneChangeRequestDialog = ({
featureId,
environmentId,
releasePlan,
milestone,
isOpen,
onConfirm,
onClosing,
}: IStartMilestoneChangeRequestDialogProps) => {
return (
<Dialogue
title='Request changes'
open={isOpen}
secondaryButtonText='Cancel'
onClose={onClosing}
customButton={
<Button
color='primary'
variant='contained'
onClick={onConfirm}
autoFocus={true}
>
Add suggestion to draft
</Button>
}
>
<p>
<StyledBoldSpan>Start</StyledBoldSpan> milestone{' '}
<StyledBoldSpan>{milestone?.name}</StyledBoldSpan> in release
plan <StyledBoldSpan>{releasePlan?.name}</StyledBoldSpan> for{' '}
<StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
<StyledBoldSpan>{environmentId}</StyledBoldSpan>
</p>
</Dialogue>
);
};

View File

@ -15,6 +15,12 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog';
import { ReleasePlanMilestone } from './ReleasePlanMilestone/ReleasePlanMilestone';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useUiFlag } from 'hooks/useUiFlag';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { RemoveReleasePlanChangeRequestDialog } from './ChangeRequest/RemoveReleasePlanChangeRequestDialog';
import { StartMilestoneChangeRequestDialog } from './ChangeRequest/StartMilestoneChangeRequestDialog';
const StyledContainer = styled('div', {
shouldForwardProp: (prop) => prop !== 'readonly',
@ -96,6 +102,74 @@ export const ReleasePlan = ({
const { setToastData, setToastApiError } = useToast();
const [removeOpen, setRemoveOpen] = useState(false);
const [changeRequestDialogRemoveOpen, setChangeRequestDialogRemoveOpen] =
useState(false);
const [
changeRequestDialogStartMilestoneOpen,
setChangeRequestDialogStartMilestoneOpen,
] = useState(false);
const [
milestoneForChangeRequestDialog,
setMilestoneForChangeRequestDialog,
] = useState<IReleasePlanMilestone>();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const releasePlanChangeRequestsEnabled = useUiFlag(
'releasePlanChangeRequests',
);
const onAddRemovePlanChangesConfirm = async () => {
addChange(projectId, environment, {
feature: featureName,
action: 'deleteReleasePlan',
payload: {
planId: plan.id,
},
});
refetchChangeRequests();
setToastData({
type: 'success',
text: 'Added to draft',
});
setChangeRequestDialogRemoveOpen(false);
};
const onAddStartMilestoneChangesConfirm = async () => {
addChange(projectId, environment, {
feature: featureName,
action: 'startMilestone',
payload: {
planId: plan.id,
milestoneId: milestoneForChangeRequestDialog?.id,
},
});
refetchChangeRequests();
setToastData({
type: 'success',
text: 'Added to draft',
});
setChangeRequestDialogStartMilestoneOpen(false);
};
const confirmRemoveReleasePlan = () => {
if (
releasePlanChangeRequestsEnabled &&
isChangeRequestConfigured(environment)
) {
setChangeRequestDialogRemoveOpen(true);
} else {
setRemoveOpen(true);
}
};
const onRemoveConfirm = async () => {
try {
@ -117,6 +191,13 @@ export const ReleasePlan = ({
};
const onStartMilestone = async (milestone: IReleasePlanMilestone) => {
if (
releasePlanChangeRequestsEnabled &&
isChangeRequestConfigured(environment)
) {
setMilestoneForChangeRequestDialog(milestone);
setChangeRequestDialogStartMilestoneOpen(true);
} else {
try {
await startReleasePlanMilestone(
projectId,
@ -133,6 +214,7 @@ export const ReleasePlan = ({
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
}
};
const activeIndex = milestones.findIndex(
@ -153,7 +235,7 @@ export const ReleasePlan = ({
</StyledHeaderTitleContainer>
{!readonly && (
<PermissionIconButton
onClick={() => setRemoveOpen(true)}
onClick={confirmRemoveReleasePlan}
permission={DELETE_FEATURE_STRATEGY}
environmentId={environment}
projectId={projectId}
@ -196,6 +278,27 @@ export const ReleasePlan = ({
onConfirm={onRemoveConfirm}
environmentActive={!environmentIsDisabled}
/>
<RemoveReleasePlanChangeRequestDialog
environmentId={environment}
featureId={featureName}
isOpen={changeRequestDialogRemoveOpen}
onConfirm={onAddRemovePlanChangesConfirm}
onClosing={() => setChangeRequestDialogRemoveOpen(false)}
releasePlan={plan}
environmentActive={!environmentIsDisabled}
/>
<StartMilestoneChangeRequestDialog
environmentId={environment}
featureId={featureName}
isOpen={changeRequestDialogStartMilestoneOpen}
onConfirm={onAddStartMilestoneChangesConfirm}
onClosing={() => {
setMilestoneForChangeRequestDialog(undefined);
setChangeRequestDialogStartMilestoneOpen(false);
}}
releasePlan={plan}
milestone={milestoneForChangeRequestDialog}
/>
</StyledContainer>
);
};

View File

@ -18,7 +18,9 @@ export interface IChangeSchema {
| 'updateSegment'
| 'addDependency'
| 'deleteDependency'
| 'addReleasePlan';
| 'addReleasePlan'
| 'deleteReleasePlan'
| 'startMilestone';
payload: string | boolean | object | number | undefined;
}