mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-24 20:06:55 +01:00
refactor: composition
This commit is contained in:
parent
244f6fbd43
commit
39c1a963b5
@ -30,6 +30,9 @@ import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequ
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
||||||
import { ReleasePlanProvider } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanContext.tsx';
|
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
|
// Indicates that a change is in draft and not yet part of a change request
|
||||||
const PENDING_CHANGE_REQUEST_ID = -1;
|
const PENDING_CHANGE_REQUEST_ID = -1;
|
||||||
@ -373,33 +376,35 @@ const CreateMilestoneProgression: FC<{
|
|||||||
const hasProgression = Boolean(milestone.transitionCondition);
|
const hasProgression = Boolean(milestone.transitionCondition);
|
||||||
const showAutomation = isTargetMilestone && isNotLastMilestone && hasProgression;
|
const showAutomation = isTargetMilestone && isNotLastMilestone && hasProgression;
|
||||||
|
|
||||||
console.log('[CreateProgression] Milestone:', milestone.name, {
|
const readonly = changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
|
||||||
isTargetMilestone,
|
const status: MilestoneStatus = 'not-started'; // In change request view, always not-started
|
||||||
isNotLastMilestone,
|
|
||||||
hasProgression,
|
// Build automation section for this milestone
|
||||||
showAutomation,
|
const automationSection = showAutomation && milestone.transitionCondition ? (
|
||||||
transitionCondition: milestone.transitionCondition,
|
<MilestoneAutomationSection status={status}>
|
||||||
projectId,
|
<MilestoneTransitionDisplay
|
||||||
environment: environmentName,
|
intervalMinutes={milestone.transitionCondition.intervalMinutes}
|
||||||
featureName,
|
onDelete={() => 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}
|
||||||
|
/>
|
||||||
|
</MilestoneAutomationSection>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={milestone.id}>
|
<div key={milestone.id}>
|
||||||
<ReleasePlanMilestone
|
<ReleasePlanMilestone
|
||||||
readonly={changeRequestState === 'Applied' || changeRequestState === 'Cancelled'}
|
readonly={readonly}
|
||||||
milestone={milestone}
|
milestone={milestone}
|
||||||
showAutomation={showAutomation}
|
automationSection={automationSection}
|
||||||
projectId={projectId}
|
|
||||||
environment={environmentName}
|
|
||||||
featureName={featureName}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
onUpdateChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
|
||||||
onDeleteAutomation={
|
|
||||||
showAutomation && onDeleteChangeRequestSubmit
|
|
||||||
? () => onDeleteChangeRequestSubmit(milestone.id)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
allMilestones={modifiedPlan.milestones}
|
allMilestones={modifiedPlan.milestones}
|
||||||
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
||||||
/>
|
/>
|
||||||
@ -512,22 +517,35 @@ const UpdateMilestoneProgression: FC<{
|
|||||||
const isNotLastMilestone = index < modifiedPlan.milestones.length - 1;
|
const isNotLastMilestone = index < modifiedPlan.milestones.length - 1;
|
||||||
const showAutomation = milestone.id === sourceId && isNotLastMilestone && Boolean(milestone.transitionCondition);
|
const showAutomation = milestone.id === sourceId && isNotLastMilestone && Boolean(milestone.transitionCondition);
|
||||||
|
|
||||||
return (
|
const readonly = changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
|
||||||
<div key={milestone.id}>
|
const status: MilestoneStatus = 'not-started';
|
||||||
<ReleasePlanMilestone
|
|
||||||
readonly={changeRequestState === 'Applied' || changeRequestState === 'Cancelled'}
|
// Build automation section for this milestone
|
||||||
milestone={milestone}
|
const automationSection = showAutomation && milestone.transitionCondition ? (
|
||||||
showAutomation={showAutomation}
|
<MilestoneAutomationSection status={status}>
|
||||||
|
<MilestoneTransitionDisplay
|
||||||
|
intervalMinutes={milestone.transitionCondition.intervalMinutes}
|
||||||
|
onDelete={() => onDeleteChangeRequestSubmit?.(milestone.id)}
|
||||||
|
milestoneName={milestone.name}
|
||||||
|
status={status}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
environment={environmentName}
|
environment={environmentName}
|
||||||
featureName={featureName}
|
featureName={featureName}
|
||||||
onUpdate={onUpdate}
|
sourceMilestoneId={milestone.id}
|
||||||
onUpdateChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
onUpdate={onUpdate || (() => {})}
|
||||||
onDeleteAutomation={
|
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
||||||
showAutomation && onDeleteChangeRequestSubmit
|
hasPendingUpdate={false}
|
||||||
? () => onDeleteChangeRequestSubmit(milestone.id)
|
hasPendingDelete={false}
|
||||||
: undefined
|
/>
|
||||||
}
|
</MilestoneAutomationSection>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={milestone.id}>
|
||||||
|
<ReleasePlanMilestone
|
||||||
|
readonly={readonly}
|
||||||
|
milestone={milestone}
|
||||||
|
automationSection={automationSection}
|
||||||
allMilestones={modifiedPlan.milestones}
|
allMilestones={modifiedPlan.milestones}
|
||||||
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
||||||
/>
|
/>
|
||||||
@ -712,15 +730,6 @@ const ConsolidatedProgressionChanges: FC<{
|
|||||||
? basePlan.milestones.find(baseMilestone => baseMilestone.id === milestone.id)
|
? basePlan.milestones.find(baseMilestone => baseMilestone.id === milestone.id)
|
||||||
: null;
|
: 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;
|
const displayMilestone = deleteChange && originalMilestone ? originalMilestone : milestone;
|
||||||
|
|
||||||
// Show automation section for any milestone that has a transition condition
|
// 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 shouldShowAutomationSection = Boolean(displayMilestone.transitionCondition) || Boolean(deleteChange);
|
||||||
const showAutomation = isNotLastMilestone && shouldShowAutomationSection;
|
const showAutomation = isNotLastMilestone && shouldShowAutomationSection;
|
||||||
|
|
||||||
return (
|
const readonly = changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
|
||||||
<div key={milestone.id}>
|
const status: MilestoneStatus = 'not-started';
|
||||||
<ReleasePlanMilestone
|
|
||||||
readonly={changeRequestState === 'Applied' || changeRequestState === 'Cancelled'}
|
// Build automation section for this milestone
|
||||||
milestone={displayMilestone}
|
const automationSection = showAutomation && displayMilestone.transitionCondition ? (
|
||||||
showAutomation={showAutomation}
|
<MilestoneAutomationSection status={status}>
|
||||||
|
<MilestoneTransitionDisplay
|
||||||
|
intervalMinutes={displayMilestone.transitionCondition.intervalMinutes}
|
||||||
|
onDelete={() => onDeleteChangeRequestSubmit?.(displayMilestone.id)}
|
||||||
|
milestoneName={displayMilestone.name}
|
||||||
|
status={status}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
environment={environmentName}
|
environment={environmentName}
|
||||||
featureName={featureName}
|
featureName={featureName}
|
||||||
onUpdate={onUpdate}
|
sourceMilestoneId={displayMilestone.id}
|
||||||
onUpdateChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
onUpdate={onUpdate || (() => {})}
|
||||||
onDeleteAutomation={
|
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
||||||
showAutomation && onDeleteChangeRequestSubmit
|
hasPendingUpdate={false}
|
||||||
? () => onDeleteChangeRequestSubmit(displayMilestone.id)
|
hasPendingDelete={Boolean(deleteChange)}
|
||||||
: undefined
|
/>
|
||||||
}
|
</MilestoneAutomationSection>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={milestone.id}>
|
||||||
|
<ReleasePlanMilestone
|
||||||
|
readonly={readonly}
|
||||||
|
milestone={displayMilestone}
|
||||||
|
automationSection={automationSection}
|
||||||
allMilestones={modifiedPlan.milestones}
|
allMilestones={modifiedPlan.milestones}
|
||||||
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
activeMilestoneId={modifiedPlan.activeMilestoneId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import type {
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog.tsx';
|
import { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog.tsx';
|
||||||
import { ReleasePlanMilestone } from './ReleasePlanMilestone/ReleasePlanMilestone.tsx';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||||
@ -22,7 +20,6 @@ import { ReleasePlanChangeRequestDialog } from './ChangeRequest/ReleasePlanChang
|
|||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { MilestoneProgressionForm } from './MilestoneProgressionForm/MilestoneProgressionForm.tsx';
|
|
||||||
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
|
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
|
||||||
import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx';
|
import { DeleteProgressionDialog } from './DeleteProgressionDialog.tsx';
|
||||||
import type {
|
import type {
|
||||||
@ -30,6 +27,7 @@ import type {
|
|||||||
UpdateMilestoneProgressionSchema,
|
UpdateMilestoneProgressionSchema,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { ReleasePlanProvider } from './ReleasePlanContext.tsx';
|
import { ReleasePlanProvider } from './ReleasePlanContext.tsx';
|
||||||
|
import { ReleasePlanMilestoneItem } from './ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx';
|
||||||
|
|
||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
@ -76,17 +74,6 @@ const StyledBody = styled('div')(({ theme }) => ({
|
|||||||
flexDirection: 'column',
|
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 {
|
interface IReleasePlanProps {
|
||||||
plan: IReleasePlan;
|
plan: IReleasePlan;
|
||||||
environmentIsDisabled?: boolean;
|
environmentIsDisabled?: boolean;
|
||||||
@ -327,10 +314,6 @@ export const ReleasePlan = ({
|
|||||||
await refetch();
|
await refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProgressionCancel = () => {
|
|
||||||
setProgressionFormOpenIndex(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleProgressionChangeRequestSubmit = (
|
const handleProgressionChangeRequestSubmit = (
|
||||||
payload: CreateMilestoneProgressionSchema,
|
payload: CreateMilestoneProgressionSchema,
|
||||||
) => {
|
) => {
|
||||||
@ -429,80 +412,31 @@ export const ReleasePlan = ({
|
|||||||
)}
|
)}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
<StyledBody>
|
<StyledBody>
|
||||||
{milestones.map((milestone, index) => {
|
{milestones.map((milestone, index) => (
|
||||||
const isNotLastMilestone = index < milestones.length - 1;
|
<ReleasePlanMilestoneItem
|
||||||
const isProgressionFormOpen =
|
key={milestone.id}
|
||||||
progressionFormOpenIndex === index;
|
milestone={milestone}
|
||||||
const nextMilestoneId = milestones[index + 1]?.id || '';
|
index={index}
|
||||||
const handleOpenProgressionForm = () =>
|
milestones={milestones}
|
||||||
setProgressionFormOpenIndex(index);
|
activeMilestoneId={activeMilestoneId}
|
||||||
|
activeIndex={activeIndex}
|
||||||
return (
|
environmentIsDisabled={environmentIsDisabled}
|
||||||
<div key={milestone.id}>
|
readonly={readonly}
|
||||||
<ReleasePlanMilestone
|
milestoneProgressionsEnabled={milestoneProgressionsEnabled}
|
||||||
readonly={readonly}
|
progressionFormOpenIndex={progressionFormOpenIndex}
|
||||||
milestone={milestone}
|
onSetProgressionFormOpenIndex={setProgressionFormOpenIndex}
|
||||||
status={
|
onStartMilestone={onStartMilestone}
|
||||||
milestone.id === activeMilestoneId
|
onDeleteProgression={handleDeleteProgression}
|
||||||
? environmentIsDisabled
|
onProgressionSave={handleProgressionSave}
|
||||||
? 'paused'
|
onProgressionChangeRequestSubmit={handleProgressionChangeRequestSubmit}
|
||||||
: 'active'
|
onUpdateProgressionChangeRequestSubmit={handleUpdateProgressionChangeRequestSubmit}
|
||||||
: index < activeIndex
|
getPendingProgressionChange={getPendingProgressionChange}
|
||||||
? 'completed'
|
projectId={projectId}
|
||||||
: 'not-started'
|
environment={environment}
|
||||||
}
|
featureName={featureName}
|
||||||
onStartMilestone={onStartMilestone}
|
onUpdate={refetch}
|
||||||
showAutomation={
|
/>
|
||||||
milestoneProgressionsEnabled &&
|
))}
|
||||||
isNotLastMilestone &&
|
|
||||||
!readonly
|
|
||||||
}
|
|
||||||
onAddAutomation={handleOpenProgressionForm}
|
|
||||||
onDeleteAutomation={
|
|
||||||
milestone.transitionCondition
|
|
||||||
? () =>
|
|
||||||
handleDeleteProgression(milestone)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
automationForm={
|
|
||||||
isProgressionFormOpen ? (
|
|
||||||
<MilestoneProgressionForm
|
|
||||||
sourceMilestoneId={milestone.id}
|
|
||||||
targetMilestoneId={nextMilestoneId}
|
|
||||||
projectId={projectId}
|
|
||||||
environment={environment}
|
|
||||||
featureName={featureName}
|
|
||||||
onSave={handleProgressionSave}
|
|
||||||
onCancel={handleProgressionCancel}
|
|
||||||
onChangeRequestSubmit={(payload) =>
|
|
||||||
handleProgressionChangeRequestSubmit(
|
|
||||||
payload,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
projectId={projectId}
|
|
||||||
environment={environment}
|
|
||||||
featureName={featureName}
|
|
||||||
onUpdate={refetch}
|
|
||||||
onUpdateChangeRequestSubmit={
|
|
||||||
handleUpdateProgressionChangeRequestSubmit
|
|
||||||
}
|
|
||||||
allMilestones={milestones}
|
|
||||||
activeMilestoneId={activeMilestoneId}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={isNotLastMilestone}
|
|
||||||
show={
|
|
||||||
<StyledConnection
|
|
||||||
isCompleted={index < activeIndex}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</StyledBody>
|
</StyledBody>
|
||||||
<ReleasePlanRemoveDialog
|
<ReleasePlanRemoveDialog
|
||||||
plan={plan}
|
plan={plan}
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
import Add from '@mui/icons-material/Add';
|
import { styled } from '@mui/material';
|
||||||
import { Button, styled } from '@mui/material';
|
|
||||||
import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx';
|
import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx';
|
||||||
import { MilestoneTransitionDisplay } from './MilestoneTransitionDisplay.tsx';
|
|
||||||
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
|
||||||
import { useReleasePlanContext } from '../ReleasePlanContext.tsx';
|
|
||||||
|
|
||||||
const StyledAutomationContainer = styled('div', {
|
const StyledAutomationContainer = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'status',
|
shouldForwardProp: (prop) => 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 {
|
interface IMilestoneAutomationSectionProps {
|
||||||
showAutomation?: boolean;
|
|
||||||
status?: MilestoneStatus;
|
status?: MilestoneStatus;
|
||||||
onAddAutomation?: () => void;
|
children: React.ReactNode;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MilestoneAutomationSection = ({
|
export const MilestoneAutomationSection = ({
|
||||||
showAutomation,
|
|
||||||
status,
|
status,
|
||||||
onAddAutomation,
|
children,
|
||||||
onDeleteAutomation,
|
|
||||||
automationForm,
|
|
||||||
transitionCondition,
|
|
||||||
milestoneName,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
featureName,
|
|
||||||
sourceMilestoneId,
|
|
||||||
onUpdate,
|
|
||||||
onUpdateChangeRequestSubmit,
|
|
||||||
}: IMilestoneAutomationSectionProps) => {
|
}: 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 (
|
return (
|
||||||
<StyledAutomationContainer status={status}>
|
<StyledAutomationContainer status={status}>
|
||||||
{automationForm ? (
|
{children}
|
||||||
automationForm
|
|
||||||
) : effectiveTransitionCondition ? (
|
|
||||||
<MilestoneTransitionDisplay
|
|
||||||
intervalMinutes={effectiveTransitionCondition.intervalMinutes}
|
|
||||||
onDelete={onDeleteAutomation!}
|
|
||||||
milestoneName={milestoneName}
|
|
||||||
status={status}
|
|
||||||
projectId={projectId}
|
|
||||||
environment={environment}
|
|
||||||
featureName={featureName}
|
|
||||||
sourceMilestoneId={sourceMilestoneId}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<StyledAddAutomationContainer>
|
|
||||||
<StyledAddAutomationButton
|
|
||||||
onClick={onAddAutomation}
|
|
||||||
color='primary'
|
|
||||||
startIcon={<Add />}
|
|
||||||
>
|
|
||||||
Add automation
|
|
||||||
</StyledAddAutomationButton>
|
|
||||||
{hasPendingCreate && (
|
|
||||||
<Badge color='warning'>
|
|
||||||
Modified in draft
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</StyledAddAutomationContainer>
|
|
||||||
)}
|
|
||||||
</StyledAutomationContainer>
|
</StyledAutomationContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import {
|
|||||||
} from '../hooks/useMilestoneProgressionForm.js';
|
} from '../hooks/useMilestoneProgressionForm.js';
|
||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
||||||
import { useReleasePlanContext } from '../ReleasePlanContext.tsx';
|
|
||||||
|
|
||||||
const StyledDisplayContainer = styled('div')(({ theme }) => ({
|
const StyledDisplayContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -75,6 +74,8 @@ interface IMilestoneTransitionDisplayProps {
|
|||||||
sourceMilestoneId: string,
|
sourceMilestoneId: string,
|
||||||
payload: UpdateMilestoneProgressionSchema,
|
payload: UpdateMilestoneProgressionSchema,
|
||||||
) => void;
|
) => void;
|
||||||
|
hasPendingUpdate?: boolean;
|
||||||
|
hasPendingDelete?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MilestoneTransitionDisplay = ({
|
export const MilestoneTransitionDisplay = ({
|
||||||
@ -88,12 +89,12 @@ export const MilestoneTransitionDisplay = ({
|
|||||||
sourceMilestoneId,
|
sourceMilestoneId,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onChangeRequestSubmit,
|
onChangeRequestSubmit,
|
||||||
|
hasPendingUpdate = false,
|
||||||
|
hasPendingDelete = false,
|
||||||
}: IMilestoneTransitionDisplayProps) => {
|
}: IMilestoneTransitionDisplayProps) => {
|
||||||
const { updateMilestoneProgression } = useMilestoneProgressionsApi();
|
const { updateMilestoneProgression } = useMilestoneProgressionsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
const { getPendingProgressionChange } = useReleasePlanContext();
|
|
||||||
const pendingProgressionChange = getPendingProgressionChange(sourceMilestoneId);
|
|
||||||
|
|
||||||
const initial = getTimeValueAndUnitFromMinutes(intervalMinutes);
|
const initial = getTimeValueAndUnitFromMinutes(intervalMinutes);
|
||||||
const form = useMilestoneProgressionForm(
|
const form = useMilestoneProgressionForm(
|
||||||
@ -109,11 +110,6 @@ export const MilestoneTransitionDisplay = ({
|
|||||||
const currentIntervalMinutes = form.getIntervalMinutes();
|
const currentIntervalMinutes = form.getIntervalMinutes();
|
||||||
const hasChanged = currentIntervalMinutes !== intervalMinutes;
|
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 showDraftBadge = hasPendingUpdate || hasPendingDelete;
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
|||||||
@ -17,9 +17,7 @@ import { StrategySeparator } from 'component/common/StrategySeparator/StrategySe
|
|||||||
import { StrategyItem } from '../../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx';
|
import { StrategyItem } from '../../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx';
|
||||||
import { StrategyList } from 'component/common/StrategyList/StrategyList';
|
import { StrategyList } from 'component/common/StrategyList/StrategyList';
|
||||||
import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem';
|
import { StrategyListItem } from 'component/common/StrategyList/StrategyListItem';
|
||||||
import { MilestoneAutomationSection } from './MilestoneAutomationSection.tsx';
|
|
||||||
import { formatDateYMDHMS } from 'utils/formatDate';
|
import { formatDateYMDHMS } from 'utils/formatDate';
|
||||||
import type { UpdateMilestoneProgressionSchema } from 'openapi';
|
|
||||||
|
|
||||||
const StyledAccordion = styled(Accordion, {
|
const StyledAccordion = styled(Accordion, {
|
||||||
shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation',
|
shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation',
|
||||||
@ -100,18 +98,7 @@ interface IReleasePlanMilestoneProps {
|
|||||||
status?: MilestoneStatus;
|
status?: MilestoneStatus;
|
||||||
onStartMilestone?: (milestone: IReleasePlanMilestone) => void;
|
onStartMilestone?: (milestone: IReleasePlanMilestone) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
showAutomation?: boolean;
|
automationSection?: React.ReactNode;
|
||||||
onAddAutomation?: () => void;
|
|
||||||
onDeleteAutomation?: () => void;
|
|
||||||
automationForm?: React.ReactNode;
|
|
||||||
projectId?: string;
|
|
||||||
environment?: string;
|
|
||||||
featureName?: string;
|
|
||||||
onUpdate?: () => void;
|
|
||||||
onUpdateChangeRequestSubmit?: (
|
|
||||||
sourceMilestoneId: string,
|
|
||||||
payload: UpdateMilestoneProgressionSchema,
|
|
||||||
) => void;
|
|
||||||
allMilestones: IReleasePlanMilestone[];
|
allMilestones: IReleasePlanMilestone[];
|
||||||
activeMilestoneId?: string;
|
activeMilestoneId?: string;
|
||||||
}
|
}
|
||||||
@ -121,24 +108,17 @@ export const ReleasePlanMilestone = ({
|
|||||||
status = 'not-started',
|
status = 'not-started',
|
||||||
onStartMilestone,
|
onStartMilestone,
|
||||||
readonly,
|
readonly,
|
||||||
showAutomation,
|
automationSection,
|
||||||
onAddAutomation,
|
|
||||||
onDeleteAutomation,
|
|
||||||
automationForm,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
featureName,
|
|
||||||
onUpdate,
|
|
||||||
onUpdateChangeRequestSubmit,
|
|
||||||
allMilestones,
|
allMilestones,
|
||||||
activeMilestoneId,
|
activeMilestoneId,
|
||||||
}: IReleasePlanMilestoneProps) => {
|
}: IReleasePlanMilestoneProps) => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const hasAutomation = Boolean(automationSection);
|
||||||
|
|
||||||
if (!milestone.strategies.length) {
|
if (!milestone.strategies.length) {
|
||||||
return (
|
return (
|
||||||
<StyledMilestoneContainer>
|
<StyledMilestoneContainer>
|
||||||
<StyledAccordion status={status} hasAutomation={showAutomation}>
|
<StyledAccordion status={status} hasAutomation={hasAutomation}>
|
||||||
<StyledAccordionSummary>
|
<StyledAccordionSummary>
|
||||||
<StyledTitleContainer>
|
<StyledTitleContainer>
|
||||||
<StyledTitle status={status}>
|
<StyledTitle status={status}>
|
||||||
@ -181,29 +161,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
</StyledSecondaryLabel>
|
</StyledSecondaryLabel>
|
||||||
</StyledAccordionSummary>
|
</StyledAccordionSummary>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
{showAutomation &&
|
{automationSection}
|
||||||
projectId &&
|
|
||||||
environment &&
|
|
||||||
featureName &&
|
|
||||||
onUpdate && (
|
|
||||||
<MilestoneAutomationSection
|
|
||||||
showAutomation={showAutomation}
|
|
||||||
status={status}
|
|
||||||
onAddAutomation={onAddAutomation}
|
|
||||||
onDeleteAutomation={onDeleteAutomation}
|
|
||||||
automationForm={automationForm}
|
|
||||||
transitionCondition={milestone.transitionCondition}
|
|
||||||
milestoneName={milestone.name}
|
|
||||||
projectId={projectId}
|
|
||||||
environment={environment}
|
|
||||||
featureName={featureName}
|
|
||||||
sourceMilestoneId={milestone.id}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
onUpdateChangeRequestSubmit={
|
|
||||||
onUpdateChangeRequestSubmit
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -212,7 +170,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
<StyledMilestoneContainer>
|
<StyledMilestoneContainer>
|
||||||
<StyledAccordion
|
<StyledAccordion
|
||||||
status={status}
|
status={status}
|
||||||
hasAutomation={showAutomation}
|
hasAutomation={hasAutomation}
|
||||||
onChange={(evt, expanded) => setExpanded(expanded)}
|
onChange={(evt, expanded) => setExpanded(expanded)}
|
||||||
>
|
>
|
||||||
<StyledAccordionSummary expandIcon={<ExpandMore />}>
|
<StyledAccordionSummary expandIcon={<ExpandMore />}>
|
||||||
@ -274,29 +232,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
</StrategyList>
|
</StrategyList>
|
||||||
</StyledAccordionDetails>
|
</StyledAccordionDetails>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
{showAutomation &&
|
{automationSection}
|
||||||
projectId &&
|
|
||||||
environment &&
|
|
||||||
featureName &&
|
|
||||||
onUpdate && (
|
|
||||||
<MilestoneAutomationSection
|
|
||||||
showAutomation={showAutomation}
|
|
||||||
status={status}
|
|
||||||
onAddAutomation={onAddAutomation}
|
|
||||||
onDeleteAutomation={onDeleteAutomation}
|
|
||||||
automationForm={automationForm}
|
|
||||||
transitionCondition={milestone.transitionCondition}
|
|
||||||
milestoneName={milestone.name}
|
|
||||||
projectId={projectId}
|
|
||||||
environment={environment}
|
|
||||||
featureName={featureName}
|
|
||||||
sourceMilestoneId={milestone.id}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
onUpdateChangeRequestSubmit={
|
|
||||||
onUpdateChangeRequestSubmit
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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<void>;
|
||||||
|
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 ? (
|
||||||
|
<MilestoneAutomationSection status={status}>
|
||||||
|
{isProgressionFormOpen ? (
|
||||||
|
<MilestoneProgressionForm
|
||||||
|
sourceMilestoneId={milestone.id}
|
||||||
|
targetMilestoneId={nextMilestoneId}
|
||||||
|
projectId={projectId}
|
||||||
|
environment={environment}
|
||||||
|
featureName={featureName}
|
||||||
|
onSave={onProgressionSave}
|
||||||
|
onCancel={handleCloseProgressionForm}
|
||||||
|
onChangeRequestSubmit={(payload) =>
|
||||||
|
onProgressionChangeRequestSubmit(payload)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : effectiveTransitionCondition ? (
|
||||||
|
<MilestoneTransitionDisplay
|
||||||
|
intervalMinutes={effectiveTransitionCondition.intervalMinutes}
|
||||||
|
onDelete={() => onDeleteProgression(milestone)}
|
||||||
|
milestoneName={milestone.name}
|
||||||
|
status={status}
|
||||||
|
projectId={projectId}
|
||||||
|
environment={environment}
|
||||||
|
featureName={featureName}
|
||||||
|
sourceMilestoneId={milestone.id}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
onChangeRequestSubmit={onUpdateProgressionChangeRequestSubmit}
|
||||||
|
hasPendingUpdate={hasPendingUpdate}
|
||||||
|
hasPendingDelete={hasPendingDelete}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StyledAddAutomationContainer>
|
||||||
|
<StyledAddAutomationButton
|
||||||
|
onClick={handleOpenProgressionForm}
|
||||||
|
color='primary'
|
||||||
|
startIcon={<Add />}
|
||||||
|
>
|
||||||
|
Add automation
|
||||||
|
</StyledAddAutomationButton>
|
||||||
|
{hasPendingCreate && (
|
||||||
|
<Badge color='warning'>Modified in draft</Badge>
|
||||||
|
)}
|
||||||
|
</StyledAddAutomationContainer>
|
||||||
|
)}
|
||||||
|
</MilestoneAutomationSection>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={milestone.id}>
|
||||||
|
<ReleasePlanMilestone
|
||||||
|
readonly={readonly}
|
||||||
|
milestone={milestone}
|
||||||
|
status={status}
|
||||||
|
onStartMilestone={onStartMilestone}
|
||||||
|
automationSection={automationSection}
|
||||||
|
allMilestones={milestones}
|
||||||
|
activeMilestoneId={activeMilestoneId}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isNotLastMilestone}
|
||||||
|
show={<StyledConnection isCompleted={index < activeIndex} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user