diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx index 8dfd2436bc..2a2d5763e0 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu.tsx @@ -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 = ({ /> setTemplateForChangeRequestDialog(undefined)} + isOpen={Boolean(templateForChangeRequestDialog)} featureId={featureId} environmentId={environmentId} releaseTemplate={templateForChangeRequestDialog} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddChangeRequestDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/ReleasePlanAddChangeRequestDialog.tsx similarity index 59% rename from frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddChangeRequestDialog.tsx rename to frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/ReleasePlanAddChangeRequestDialog.tsx index 392aa41fc5..248aeb5e74 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddChangeRequestDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/ReleasePlanAddChangeRequestDialog.tsx @@ -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; 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 ( { - onClosing(); - }} + onClose={onClosing} customButton={ + } + > + <> + {environmentActive && ( + + This release plan currently has one active milestone. + Removing the release plan will change which users + receive access to the feature. + + )} +

+ Remove release plan{' '} + {releasePlan?.name} from{' '} + {featureId} in{' '} + {environmentId} +

+ +
+ ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/StartMilestoneChangeRequestDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/StartMilestoneChangeRequestDialog.tsx new file mode 100644 index 0000000000..e14d718bf3 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/StartMilestoneChangeRequestDialog.tsx @@ -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; + onClosing: () => void; +} + +export const StartMilestoneChangeRequestDialog = ({ + featureId, + environmentId, + releasePlan, + milestone, + isOpen, + onConfirm, + onClosing, +}: IStartMilestoneChangeRequestDialogProps) => { + return ( + + Add suggestion to draft + + } + > +

+ Start milestone{' '} + {milestone?.name} in release + plan {releasePlan?.name} for{' '} + {featureId} in{' '} + {environmentId} +

+
+ ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx index 241243cba0..d8c22c8934 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx @@ -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(); + 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,21 +191,29 @@ export const ReleasePlan = ({ }; const onStartMilestone = async (milestone: IReleasePlanMilestone) => { - try { - await startReleasePlanMilestone( - projectId, - featureName, - environment, - id, - milestone.id, - ); - setToastData({ - text: `Milestone "${milestone.name}" has started`, - type: 'success', - }); - refetch(); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); + if ( + releasePlanChangeRequestsEnabled && + isChangeRequestConfigured(environment) + ) { + setMilestoneForChangeRequestDialog(milestone); + setChangeRequestDialogStartMilestoneOpen(true); + } else { + try { + await startReleasePlanMilestone( + projectId, + featureName, + environment, + id, + milestone.id, + ); + setToastData({ + text: `Milestone "${milestone.name}" has started`, + type: 'success', + }); + refetch(); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); + } } }; @@ -153,7 +235,7 @@ export const ReleasePlan = ({ {!readonly && ( setRemoveOpen(true)} + onClick={confirmRemoveReleasePlan} permission={DELETE_FEATURE_STRATEGY} environmentId={environment} projectId={projectId} @@ -196,6 +278,27 @@ export const ReleasePlan = ({ onConfirm={onRemoveConfirm} environmentActive={!environmentIsDisabled} /> + setChangeRequestDialogRemoveOpen(false)} + releasePlan={plan} + environmentActive={!environmentIsDisabled} + /> + { + setMilestoneForChangeRequestDialog(undefined); + setChangeRequestDialogStartMilestoneOpen(false); + }} + releasePlan={plan} + milestone={milestoneForChangeRequestDialog} + /> ); }; diff --git a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts index e7e3257eaa..a617be8f94 100644 --- a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts +++ b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts @@ -18,7 +18,9 @@ export interface IChangeSchema { | 'updateSegment' | 'addDependency' | 'deleteDependency' - | 'addReleasePlan'; + | 'addReleasePlan' + | 'deleteReleasePlan' + | 'startMilestone'; payload: string | boolean | object | number | undefined; }