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 MoreVert from '@mui/icons-material/MoreVert';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
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 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 {
|
interface IFeatureStrategyMenuProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -57,6 +60,10 @@ export const FeatureStrategyMenu = ({
|
|||||||
const isPopoverOpen = Boolean(anchor);
|
const isPopoverOpen = Boolean(anchor);
|
||||||
const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
|
const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
|
||||||
const flagOverviewRedesignEnabled = useUiFlag('flagOverviewRedesign');
|
const flagOverviewRedesignEnabled = useUiFlag('flagOverviewRedesign');
|
||||||
|
const { setToastData } = useToast();
|
||||||
|
const { addChange } = useChangeRequestApi();
|
||||||
|
const { refetch: refetchChangeRequests } =
|
||||||
|
usePendingChangeRequests(projectId);
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setAnchor(undefined);
|
setAnchor(undefined);
|
||||||
@ -75,6 +82,25 @@ export const FeatureStrategyMenu = ({
|
|||||||
setAnchor(event.currentTarget);
|
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(
|
const createStrategyPath = formatCreateStrategyPath(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
@ -188,8 +214,9 @@ export const FeatureStrategyMenu = ({
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
<ReleasePlanAddChangeRequestDialog
|
<ReleasePlanAddChangeRequestDialog
|
||||||
projectId={projectId}
|
onConfirm={addReleasePlanToChangeRequest}
|
||||||
onClosing={() => setTemplateForChangeRequestDialog(undefined)}
|
onClosing={() => setTemplateForChangeRequestDialog(undefined)}
|
||||||
|
isOpen={Boolean(templateForChangeRequestDialog)}
|
||||||
featureId={featureId}
|
featureId={featureId}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
releaseTemplate={templateForChangeRequestDialog}
|
releaseTemplate={templateForChangeRequestDialog}
|
||||||
|
@ -1,60 +1,39 @@
|
|||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { styled, Button } from '@mui/material';
|
import { styled, Button } from '@mui/material';
|
||||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
|
||||||
|
|
||||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
const StyledBoldSpan = styled('span')(({ theme }) => ({
|
||||||
fontWeight: theme.typography.fontWeightBold,
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface IReleasePlanAddChangeRequestDialogProps {
|
interface IReleasePlanAddChangeRequestDialogProps {
|
||||||
projectId: string;
|
|
||||||
featureId: string;
|
featureId: string;
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
releaseTemplate: IReleasePlanTemplate | undefined;
|
releaseTemplate?: IReleasePlanTemplate;
|
||||||
|
isOpen: boolean;
|
||||||
|
onConfirm: () => Promise<void>;
|
||||||
onClosing: () => void;
|
onClosing: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReleasePlanAddChangeRequestDialog = ({
|
export const ReleasePlanAddChangeRequestDialog = ({
|
||||||
projectId,
|
|
||||||
featureId,
|
featureId,
|
||||||
environmentId,
|
environmentId,
|
||||||
releaseTemplate,
|
releaseTemplate,
|
||||||
|
isOpen,
|
||||||
|
onConfirm,
|
||||||
onClosing,
|
onClosing,
|
||||||
}: IReleasePlanAddChangeRequestDialogProps) => {
|
}: 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 (
|
return (
|
||||||
<Dialogue
|
<Dialogue
|
||||||
title='Request changes'
|
title='Request changes'
|
||||||
open={Boolean(releaseTemplate)}
|
open={isOpen}
|
||||||
secondaryButtonText='Cancel'
|
secondaryButtonText='Cancel'
|
||||||
onClose={() => {
|
onClose={onClosing}
|
||||||
onClosing();
|
|
||||||
}}
|
|
||||||
customButton={
|
customButton={
|
||||||
<Button
|
<Button
|
||||||
color='primary'
|
color='primary'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
onClick={addReleasePlanToChangeRequest}
|
onClick={onConfirm}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
>
|
>
|
||||||
Add suggestion to draft
|
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 { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog';
|
||||||
import { ReleasePlanMilestone } from './ReleasePlanMilestone/ReleasePlanMilestone';
|
import { ReleasePlanMilestone } from './ReleasePlanMilestone/ReleasePlanMilestone';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
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', {
|
const StyledContainer = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'readonly',
|
shouldForwardProp: (prop) => prop !== 'readonly',
|
||||||
@ -96,6 +102,74 @@ export const ReleasePlan = ({
|
|||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
const [removeOpen, setRemoveOpen] = useState(false);
|
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 () => {
|
const onRemoveConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
@ -117,6 +191,13 @@ export const ReleasePlan = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onStartMilestone = async (milestone: IReleasePlanMilestone) => {
|
const onStartMilestone = async (milestone: IReleasePlanMilestone) => {
|
||||||
|
if (
|
||||||
|
releasePlanChangeRequestsEnabled &&
|
||||||
|
isChangeRequestConfigured(environment)
|
||||||
|
) {
|
||||||
|
setMilestoneForChangeRequestDialog(milestone);
|
||||||
|
setChangeRequestDialogStartMilestoneOpen(true);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
await startReleasePlanMilestone(
|
await startReleasePlanMilestone(
|
||||||
projectId,
|
projectId,
|
||||||
@ -133,6 +214,7 @@ export const ReleasePlan = ({
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeIndex = milestones.findIndex(
|
const activeIndex = milestones.findIndex(
|
||||||
@ -153,7 +235,7 @@ export const ReleasePlan = ({
|
|||||||
</StyledHeaderTitleContainer>
|
</StyledHeaderTitleContainer>
|
||||||
{!readonly && (
|
{!readonly && (
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
onClick={() => setRemoveOpen(true)}
|
onClick={confirmRemoveReleasePlan}
|
||||||
permission={DELETE_FEATURE_STRATEGY}
|
permission={DELETE_FEATURE_STRATEGY}
|
||||||
environmentId={environment}
|
environmentId={environment}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -196,6 +278,27 @@ export const ReleasePlan = ({
|
|||||||
onConfirm={onRemoveConfirm}
|
onConfirm={onRemoveConfirm}
|
||||||
environmentActive={!environmentIsDisabled}
|
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>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,9 @@ export interface IChangeSchema {
|
|||||||
| 'updateSegment'
|
| 'updateSegment'
|
||||||
| 'addDependency'
|
| 'addDependency'
|
||||||
| 'deleteDependency'
|
| 'deleteDependency'
|
||||||
| 'addReleasePlan';
|
| 'addReleasePlan'
|
||||||
|
| 'deleteReleasePlan'
|
||||||
|
| 'startMilestone';
|
||||||
payload: string | boolean | object | number | undefined;
|
payload: string | boolean | object | number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user