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:
parent
61f8236711
commit
e689e2e3d2
@ -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}
|
||||
|
@ -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
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -18,7 +18,9 @@ export interface IChangeSchema {
|
||||
| 'updateSegment'
|
||||
| 'addDependency'
|
||||
| 'deleteDependency'
|
||||
| 'addReleasePlan';
|
||||
| 'addReleasePlan'
|
||||
| 'deleteReleasePlan'
|
||||
| 'startMilestone';
|
||||
payload: string | boolean | object | number | undefined;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user