From 39c1a963b5c73d9951c31fe372506f66a856be68 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Tue, 21 Oct 2025 13:12:31 +0200 Subject: [PATCH] refactor: composition --- .../Changes/Change/ReleasePlanChange.tsx | 136 ++++++----- .../ReleasePlan/ReleasePlan.tsx | 118 ++------- .../MilestoneAutomationSection.tsx | 115 +-------- .../MilestoneTransitionDisplay.tsx | 12 +- .../ReleasePlanMilestone.tsx | 78 +----- .../ReleasePlanMilestoneItem.tsx | 229 ++++++++++++++++++ 6 files changed, 349 insertions(+), 339 deletions(-) create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx index 4bc8dc4971..9f909680df 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx @@ -30,6 +30,9 @@ import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequ import useToast from 'hooks/useToast'; import type { UpdateMilestoneProgressionSchema } from 'openapi'; import { ReleasePlanProvider } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanContext.tsx'; +import { MilestoneAutomationSection } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx'; +import { MilestoneTransitionDisplay } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx'; +import type { MilestoneStatus } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx'; // Indicates that a change is in draft and not yet part of a change request const PENDING_CHANGE_REQUEST_ID = -1; @@ -373,33 +376,35 @@ const CreateMilestoneProgression: FC<{ const hasProgression = Boolean(milestone.transitionCondition); const showAutomation = isTargetMilestone && isNotLastMilestone && hasProgression; - console.log('[CreateProgression] Milestone:', milestone.name, { - isTargetMilestone, - isNotLastMilestone, - hasProgression, - showAutomation, - transitionCondition: milestone.transitionCondition, - projectId, - environment: environmentName, - featureName, - }); + const readonly = changeRequestState === 'Applied' || changeRequestState === 'Cancelled'; + const status: MilestoneStatus = 'not-started'; // In change request view, always not-started + + // Build automation section for this milestone + const automationSection = showAutomation && milestone.transitionCondition ? ( + + onDeleteChangeRequestSubmit?.(milestone.id)} + milestoneName={milestone.name} + status={status} + projectId={projectId} + environment={environmentName} + featureName={featureName} + sourceMilestoneId={milestone.id} + onUpdate={onUpdate || (() => {})} + onChangeRequestSubmit={onUpdateChangeRequestSubmit} + hasPendingUpdate={false} + hasPendingDelete={false} + /> + + ) : undefined; return (
onDeleteChangeRequestSubmit(milestone.id) - : undefined - } + automationSection={automationSection} allMilestones={modifiedPlan.milestones} activeMilestoneId={modifiedPlan.activeMilestoneId} /> @@ -512,22 +517,35 @@ const UpdateMilestoneProgression: FC<{ const isNotLastMilestone = index < modifiedPlan.milestones.length - 1; const showAutomation = milestone.id === sourceId && isNotLastMilestone && Boolean(milestone.transitionCondition); - return ( -
- + onDeleteChangeRequestSubmit?.(milestone.id)} + milestoneName={milestone.name} + status={status} projectId={projectId} environment={environmentName} featureName={featureName} - onUpdate={onUpdate} - onUpdateChangeRequestSubmit={onUpdateChangeRequestSubmit} - onDeleteAutomation={ - showAutomation && onDeleteChangeRequestSubmit - ? () => onDeleteChangeRequestSubmit(milestone.id) - : undefined - } + sourceMilestoneId={milestone.id} + onUpdate={onUpdate || (() => {})} + onChangeRequestSubmit={onUpdateChangeRequestSubmit} + hasPendingUpdate={false} + hasPendingDelete={false} + /> + + ) : undefined; + + return ( +
+ @@ -712,15 +730,6 @@ const ConsolidatedProgressionChanges: FC<{ ? basePlan.milestones.find(baseMilestone => baseMilestone.id === milestone.id) : null; - // Warn if we can't find the original milestone for a delete change - if (deleteChange && !originalMilestone) { - console.error('[ConsolidatedProgressionChanges] Cannot find original milestone for delete', { - milestoneId: milestone.id, - milestoneName: milestone.name, - basePlanMilestones: basePlan.milestones.map(baseMilestone => ({ id: baseMilestone.id, name: baseMilestone.name })) - }); - } - const displayMilestone = deleteChange && originalMilestone ? originalMilestone : milestone; // Show automation section for any milestone that has a transition condition @@ -728,22 +737,35 @@ const ConsolidatedProgressionChanges: FC<{ const shouldShowAutomationSection = Boolean(displayMilestone.transitionCondition) || Boolean(deleteChange); const showAutomation = isNotLastMilestone && shouldShowAutomationSection; - return ( -
- + onDeleteChangeRequestSubmit?.(displayMilestone.id)} + milestoneName={displayMilestone.name} + status={status} projectId={projectId} environment={environmentName} featureName={featureName} - onUpdate={onUpdate} - onUpdateChangeRequestSubmit={onUpdateChangeRequestSubmit} - onDeleteAutomation={ - showAutomation && onDeleteChangeRequestSubmit - ? () => onDeleteChangeRequestSubmit(displayMilestone.id) - : undefined - } + sourceMilestoneId={displayMilestone.id} + onUpdate={onUpdate || (() => {})} + onChangeRequestSubmit={onUpdateChangeRequestSubmit} + hasPendingUpdate={false} + hasPendingDelete={Boolean(deleteChange)} + /> + + ) : undefined; + + return ( +
+ diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx index 9b149eeaf6..10f7864982 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx @@ -13,8 +13,6 @@ import type { import { useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; import { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog.tsx'; -import { ReleasePlanMilestone } from './ReleasePlanMilestone/ReleasePlanMilestone.tsx'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; @@ -22,7 +20,6 @@ import { ReleasePlanChangeRequestDialog } from './ChangeRequest/ReleasePlanChang import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { Truncator } from 'component/common/Truncator/Truncator'; import { useUiFlag } from 'hooks/useUiFlag'; -import { MilestoneProgressionForm } from './MilestoneProgressionForm/MilestoneProgressionForm.tsx'; import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi'; import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx'; import type { @@ -30,6 +27,7 @@ import type { UpdateMilestoneProgressionSchema, } from 'openapi'; import { ReleasePlanProvider } from './ReleasePlanContext.tsx'; +import { ReleasePlanMilestoneItem } from './ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx'; const StyledContainer = styled('div')(({ theme }) => ({ padding: theme.spacing(2), @@ -76,17 +74,6 @@ const StyledBody = styled('div')(({ theme }) => ({ flexDirection: 'column', })); -const StyledConnection = styled('div', { - shouldForwardProp: (prop) => prop !== 'isCompleted', -})<{ isCompleted: boolean }>(({ theme, isCompleted }) => ({ - width: 2, - height: theme.spacing(2), - backgroundColor: isCompleted - ? theme.palette.divider - : theme.palette.primary.main, - marginLeft: theme.spacing(3.25), -})); - interface IReleasePlanProps { plan: IReleasePlan; environmentIsDisabled?: boolean; @@ -327,10 +314,6 @@ export const ReleasePlan = ({ await refetch(); }; - const handleProgressionCancel = () => { - setProgressionFormOpenIndex(null); - }; - const handleProgressionChangeRequestSubmit = ( payload: CreateMilestoneProgressionSchema, ) => { @@ -429,80 +412,31 @@ export const ReleasePlan = ({ )} - {milestones.map((milestone, index) => { - const isNotLastMilestone = index < milestones.length - 1; - const isProgressionFormOpen = - progressionFormOpenIndex === index; - const nextMilestoneId = milestones[index + 1]?.id || ''; - const handleOpenProgressionForm = () => - setProgressionFormOpenIndex(index); - - return ( -
- - handleDeleteProgression(milestone) - : undefined - } - automationForm={ - isProgressionFormOpen ? ( - - handleProgressionChangeRequestSubmit( - payload, - ) - } - /> - ) : undefined - } - projectId={projectId} - environment={environment} - featureName={featureName} - onUpdate={refetch} - onUpdateChangeRequestSubmit={ - handleUpdateProgressionChangeRequestSubmit - } - allMilestones={milestones} - activeMilestoneId={activeMilestoneId} - /> - - } - /> -
- ); - })} + {milestones.map((milestone, index) => ( + + ))}
prop !== 'status', @@ -26,120 +21,18 @@ const StyledAutomationContainer = styled('div', { }, })); -const StyledAddAutomationButton = styled(Button)(({ theme }) => ({ - textTransform: 'none', - fontWeight: theme.typography.fontWeightBold, - fontSize: theme.typography.body2.fontSize, - padding: 0, - minWidth: 'auto', - gap: theme.spacing(1), - '&:hover': { - backgroundColor: 'transparent', - }, - '& .MuiButton-startIcon': { - margin: 0, - width: 20, - height: 20, - border: `1px solid ${theme.palette.primary.main}`, - backgroundColor: theme.palette.background.elevation2, - borderRadius: '50%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - '& svg': { - fontSize: 14, - color: theme.palette.primary.main, - }, - }, -})); - -const StyledAddAutomationContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), -})); - interface IMilestoneAutomationSectionProps { - showAutomation?: boolean; status?: MilestoneStatus; - onAddAutomation?: () => void; - onDeleteAutomation?: () => void; - automationForm?: React.ReactNode; - transitionCondition?: { - intervalMinutes: number; - } | null; - milestoneName: string; - projectId: string; - environment: string; - featureName: string; - sourceMilestoneId: string; - onUpdate: () => void; - onUpdateChangeRequestSubmit?: ( - sourceMilestoneId: string, - payload: UpdateMilestoneProgressionSchema, - ) => void; + children: React.ReactNode; } export const MilestoneAutomationSection = ({ - showAutomation, status, - onAddAutomation, - onDeleteAutomation, - automationForm, - transitionCondition, - milestoneName, - projectId, - environment, - featureName, - sourceMilestoneId, - onUpdate, - onUpdateChangeRequestSubmit, + children, }: IMilestoneAutomationSectionProps) => { - const { getPendingProgressionChange } = useReleasePlanContext(); - const pendingProgressionChange = getPendingProgressionChange(sourceMilestoneId); - - const hasPendingCreate = pendingProgressionChange?.action === 'createMilestoneProgression'; - - // For pending create changes, use the transition condition from the pending change - const effectiveTransitionCondition = hasPendingCreate && pendingProgressionChange?.payload?.transitionCondition - ? pendingProgressionChange.payload.transitionCondition - : transitionCondition; - - if (!showAutomation) return null; - return ( - {automationForm ? ( - automationForm - ) : effectiveTransitionCondition ? ( - - ) : ( - - } - > - Add automation - - {hasPendingCreate && ( - - Modified in draft - - )} - - )} + {children} ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx index e62bacfdea..8a98deeb01 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx @@ -14,7 +14,6 @@ import { } from '../hooks/useMilestoneProgressionForm.js'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import type { UpdateMilestoneProgressionSchema } from 'openapi'; -import { useReleasePlanContext } from '../ReleasePlanContext.tsx'; const StyledDisplayContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -75,6 +74,8 @@ interface IMilestoneTransitionDisplayProps { sourceMilestoneId: string, payload: UpdateMilestoneProgressionSchema, ) => void; + hasPendingUpdate?: boolean; + hasPendingDelete?: boolean; } export const MilestoneTransitionDisplay = ({ @@ -88,12 +89,12 @@ export const MilestoneTransitionDisplay = ({ sourceMilestoneId, onUpdate, onChangeRequestSubmit, + hasPendingUpdate = false, + hasPendingDelete = false, }: IMilestoneTransitionDisplayProps) => { const { updateMilestoneProgression } = useMilestoneProgressionsApi(); const { setToastData, setToastApiError } = useToast(); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - const { getPendingProgressionChange } = useReleasePlanContext(); - const pendingProgressionChange = getPendingProgressionChange(sourceMilestoneId); const initial = getTimeValueAndUnitFromMinutes(intervalMinutes); const form = useMilestoneProgressionForm( @@ -109,11 +110,6 @@ export const MilestoneTransitionDisplay = ({ const currentIntervalMinutes = form.getIntervalMinutes(); const hasChanged = currentIntervalMinutes !== intervalMinutes; - // Check if there's a pending change request for this progression - const hasPendingUpdate = - pendingProgressionChange?.action === 'updateMilestoneProgression'; - const hasPendingDelete = - pendingProgressionChange?.action === 'deleteMilestoneProgression'; const showDraftBadge = hasPendingUpdate || hasPendingDelete; const handleSave = async () => { diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx index 9e467ce69c..599aa85a2a 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx @@ -17,9 +17,7 @@ import { StrategySeparator } from 'component/common/StrategySeparator/StrategySe import { StrategyItem } from '../../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx'; import { StrategyList } from 'component/common/StrategyList/StrategyList'; import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem'; -import { MilestoneAutomationSection } from './MilestoneAutomationSection.tsx'; import { formatDateYMDHMS } from 'utils/formatDate'; -import type { UpdateMilestoneProgressionSchema } from 'openapi'; const StyledAccordion = styled(Accordion, { shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation', @@ -100,18 +98,7 @@ interface IReleasePlanMilestoneProps { status?: MilestoneStatus; onStartMilestone?: (milestone: IReleasePlanMilestone) => void; readonly?: boolean; - showAutomation?: boolean; - onAddAutomation?: () => void; - onDeleteAutomation?: () => void; - automationForm?: React.ReactNode; - projectId?: string; - environment?: string; - featureName?: string; - onUpdate?: () => void; - onUpdateChangeRequestSubmit?: ( - sourceMilestoneId: string, - payload: UpdateMilestoneProgressionSchema, - ) => void; + automationSection?: React.ReactNode; allMilestones: IReleasePlanMilestone[]; activeMilestoneId?: string; } @@ -121,24 +108,17 @@ export const ReleasePlanMilestone = ({ status = 'not-started', onStartMilestone, readonly, - showAutomation, - onAddAutomation, - onDeleteAutomation, - automationForm, - projectId, - environment, - featureName, - onUpdate, - onUpdateChangeRequestSubmit, + automationSection, allMilestones, activeMilestoneId, }: IReleasePlanMilestoneProps) => { const [expanded, setExpanded] = useState(false); + const hasAutomation = Boolean(automationSection); if (!milestone.strategies.length) { return ( - + @@ -181,29 +161,7 @@ export const ReleasePlanMilestone = ({ - {showAutomation && - projectId && - environment && - featureName && - onUpdate && ( - - )} + {automationSection} ); } @@ -212,7 +170,7 @@ export const ReleasePlanMilestone = ({ setExpanded(expanded)} > }> @@ -274,29 +232,7 @@ export const ReleasePlanMilestone = ({ - {showAutomation && - projectId && - environment && - featureName && - onUpdate && ( - - )} + {automationSection} ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx new file mode 100644 index 0000000000..0441e612ed --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx @@ -0,0 +1,229 @@ +import Add from '@mui/icons-material/Add'; +import { Button, styled } from '@mui/material'; +import { Badge } from 'component/common/Badge/Badge'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import type { IReleasePlanMilestone } from 'interfaces/releasePlans'; +import type { + CreateMilestoneProgressionSchema, + UpdateMilestoneProgressionSchema, +} from 'openapi'; +import { MilestoneAutomationSection } from '../ReleasePlanMilestone/MilestoneAutomationSection.tsx'; +import { MilestoneTransitionDisplay } from '../ReleasePlanMilestone/MilestoneTransitionDisplay.tsx'; +import { ReleasePlanMilestone } from '../ReleasePlanMilestone/ReleasePlanMilestone.tsx'; +import type { MilestoneStatus } from '../ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx'; +import { MilestoneProgressionForm } from '../MilestoneProgressionForm/MilestoneProgressionForm.tsx'; + +const StyledConnection = styled('div', { + shouldForwardProp: (prop) => prop !== 'isCompleted', +})<{ isCompleted: boolean }>(({ theme, isCompleted }) => ({ + width: 2, + height: theme.spacing(2), + backgroundColor: isCompleted + ? theme.palette.divider + : theme.palette.primary.main, + marginLeft: theme.spacing(3.25), +})); + +const StyledAddAutomationButton = styled(Button)(({ theme }) => ({ + textTransform: 'none', + fontWeight: theme.typography.fontWeightBold, + fontSize: theme.typography.body2.fontSize, + padding: 0, + minWidth: 'auto', + gap: theme.spacing(1), + '&:hover': { + backgroundColor: 'transparent', + }, + '& .MuiButton-startIcon': { + margin: 0, + width: 20, + height: 20, + border: `1px solid ${theme.palette.primary.main}`, + backgroundColor: theme.palette.background.elevation2, + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + '& svg': { + fontSize: 14, + color: theme.palette.primary.main, + }, + }, +})); + +const StyledAddAutomationContainer = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +})); + +interface PendingProgressionChange { + action: string; + payload: any; + changeRequestId: number; +} + +interface IReleasePlanMilestoneItemProps { + milestone: IReleasePlanMilestone; + index: number; + milestones: IReleasePlanMilestone[]; + activeMilestoneId?: string; + activeIndex: number; + environmentIsDisabled?: boolean; + readonly?: boolean; + + // Automation-related + milestoneProgressionsEnabled: boolean; + progressionFormOpenIndex: number | null; + onSetProgressionFormOpenIndex: (index: number | null) => void; + + // API callbacks + onStartMilestone?: (milestone: IReleasePlanMilestone) => void; + onDeleteProgression: (milestone: IReleasePlanMilestone) => void; + onProgressionSave: () => Promise; + onProgressionChangeRequestSubmit: ( + payload: CreateMilestoneProgressionSchema, + ) => void; + onUpdateProgressionChangeRequestSubmit: ( + sourceMilestoneId: string, + payload: UpdateMilestoneProgressionSchema, + ) => void; + + // Context + getPendingProgressionChange: ( + sourceMilestoneId: string, + ) => PendingProgressionChange | null; + + // IDs + projectId: string; + environment: string; + featureName: string; + + onUpdate: () => void; +} + +export const ReleasePlanMilestoneItem = ({ + milestone, + index, + milestones, + activeMilestoneId, + activeIndex, + environmentIsDisabled, + readonly, + milestoneProgressionsEnabled, + progressionFormOpenIndex, + onSetProgressionFormOpenIndex, + onStartMilestone, + onDeleteProgression, + onProgressionSave, + onProgressionChangeRequestSubmit, + onUpdateProgressionChangeRequestSubmit, + getPendingProgressionChange, + projectId, + environment, + featureName, + onUpdate, +}: IReleasePlanMilestoneItemProps) => { + const isNotLastMilestone = index < milestones.length - 1; + const isProgressionFormOpen = progressionFormOpenIndex === index; + const nextMilestoneId = milestones[index + 1]?.id || ''; + const handleOpenProgressionForm = () => onSetProgressionFormOpenIndex(index); + const handleCloseProgressionForm = () => onSetProgressionFormOpenIndex(null); + + const status: MilestoneStatus = + milestone.id === activeMilestoneId + ? environmentIsDisabled + ? 'paused' + : 'active' + : index < activeIndex + ? 'completed' + : 'not-started'; + + // Calculate pending progression change for this milestone + const pendingProgressionChange = getPendingProgressionChange(milestone.id); + const hasPendingCreate = + pendingProgressionChange?.action === 'createMilestoneProgression'; + const hasPendingUpdate = + pendingProgressionChange?.action === 'updateMilestoneProgression'; + const hasPendingDelete = + pendingProgressionChange?.action === 'deleteMilestoneProgression'; + + // Determine effective transition condition (use pending create if exists) + let effectiveTransitionCondition = milestone.transitionCondition; + if ( + pendingProgressionChange?.action === 'createMilestoneProgression' && + 'transitionCondition' in pendingProgressionChange.payload && + pendingProgressionChange.payload.transitionCondition + ) { + effectiveTransitionCondition = + pendingProgressionChange.payload.transitionCondition; + } + + // Build automation section + const showAutomation = + milestoneProgressionsEnabled && isNotLastMilestone && !readonly; + const automationSection = showAutomation ? ( + + {isProgressionFormOpen ? ( + + onProgressionChangeRequestSubmit(payload) + } + /> + ) : effectiveTransitionCondition ? ( + onDeleteProgression(milestone)} + milestoneName={milestone.name} + status={status} + projectId={projectId} + environment={environment} + featureName={featureName} + sourceMilestoneId={milestone.id} + onUpdate={onUpdate} + onChangeRequestSubmit={onUpdateProgressionChangeRequestSubmit} + hasPendingUpdate={hasPendingUpdate} + hasPendingDelete={hasPendingDelete} + /> + ) : ( + + } + > + Add automation + + {hasPendingCreate && ( + Modified in draft + )} + + )} + + ) : undefined; + + return ( +
+ + } + /> +
+ ); +};