From a922801690c64d667fcd13a9a661a5d1a2cb6bdb Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Thu, 9 Oct 2025 11:41:58 +0200 Subject: [PATCH] feat: Add transition condition UI for release plan milestones (#10768) --- .../EnvironmentAccordionBody.tsx | 2 +- .../FeatureOverviewEnvironments.tsx | 2 +- .../MilestoneAutomationSection.tsx | 9 +++ .../MilestoneTransitionDisplay.tsx | 55 +++++++++++++++++++ .../ReleasePlanMilestone.tsx | 2 + .../useFeatureReleasePlans.ts | 22 ++++---- frontend/src/interfaces/releasePlans.ts | 3 + 7 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx index 6a6ac10abf..acfe27e1a7 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx @@ -69,7 +69,7 @@ export const EnvironmentAccordionBody = ({ const { releasePlans } = useFeatureReleasePlans( projectId, featureId, - featureEnvironment, + featureEnvironment?.name, ); const { trackEvent } = usePlausibleTracker(); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx index a74af7990d..bee2b0291c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx @@ -21,7 +21,7 @@ const FeatureOverviewWithReleasePlans: FC< const { releasePlans } = useFeatureReleasePlans( projectId, featureId, - environment, + environment?.name, ); const envAddStrategySuggestionEnabled = useUiFlag( 'envAddStrategySuggestion', diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx index 9beb220e24..db241ee876 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx @@ -1,6 +1,7 @@ import Add from '@mui/icons-material/Add'; import { Button, styled } from '@mui/material'; import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx'; +import { MilestoneTransitionDisplay } from './MilestoneTransitionDisplay.tsx'; const StyledAutomationContainer = styled('div', { shouldForwardProp: (prop) => prop !== 'status', @@ -51,6 +52,9 @@ interface IMilestoneAutomationSectionProps { status?: MilestoneStatus; onAddAutomation?: () => void; automationForm?: React.ReactNode; + transitionCondition?: { + intervalMinutes: number; + } | null; } export const MilestoneAutomationSection = ({ @@ -58,6 +62,7 @@ export const MilestoneAutomationSection = ({ status, onAddAutomation, automationForm, + transitionCondition, }: IMilestoneAutomationSectionProps) => { if (!showAutomation) return null; @@ -65,6 +70,10 @@ export const MilestoneAutomationSection = ({ {automationForm ? ( automationForm + ) : transitionCondition ? ( + ) : ( ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +})); + +const StyledIcon = styled(BoltIcon)(({ theme }) => ({ + color: theme.palette.common.white, + fontSize: 18, + flexShrink: 0, + backgroundColor: theme.palette.primary.main, + borderRadius: '50%', + padding: theme.spacing(0.25), +})); + +const StyledText = styled('span')(({ theme }) => ({ + color: theme.palette.text.primary, + fontSize: theme.typography.body2.fontSize, +})); + +interface IMilestoneTransitionDisplayProps { + intervalMinutes: number; +} + +const formatInterval = (minutes: number): string => { + if (minutes === 0) return '0 minutes'; + + const duration = intervalToDuration({ + start: 0, + end: minutes * 60 * 1000, + }); + + return formatDuration(duration, { + format: ['days', 'hours', 'minutes'], + delimiter: ', ', + }); +}; + +export const MilestoneTransitionDisplay = ({ + intervalMinutes, +}: IMilestoneTransitionDisplayProps) => { + return ( + + + + Proceed to the next milestone after{' '} + {formatInterval(intervalMinutes)} + + + ); +}; 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 ba7289d7ae..69b4e086e6 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx @@ -118,6 +118,7 @@ export const ReleasePlanMilestone = ({ status={status} onAddAutomation={onAddAutomation} automationForm={automationForm} + transitionCondition={milestone.transitionCondition} /> ); @@ -174,6 +175,7 @@ export const ReleasePlanMilestone = ({ status={status} onAddAutomation={onAddAutomation} automationForm={automationForm} + transitionCondition={milestone.transitionCondition} /> ); diff --git a/frontend/src/hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans.ts b/frontend/src/hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans.ts index 4931bca4ab..e50fe9fec3 100644 --- a/frontend/src/hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans.ts +++ b/frontend/src/hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans.ts @@ -1,28 +1,28 @@ import { useUiFlag } from 'hooks/useUiFlag'; import { useReleasePlans } from '../useReleasePlans/useReleasePlans.js'; import { useFeature } from '../useFeature/useFeature.js'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; export const useFeatureReleasePlans = ( projectId: string, featureId: string, - environment?: IFeatureEnvironment | string, + environmentName?: string, ) => { const featureReleasePlansEnabled = useUiFlag('featureReleasePlans'); - const envName = - typeof environment === 'string' ? environment : environment?.name; const { releasePlans: releasePlansFromHook, refetch: refetchReleasePlans, ...rest - } = useReleasePlans(projectId, featureId, envName); - const { refetchFeature } = useFeature(projectId, featureId); + } = useReleasePlans(projectId, featureId, environmentName); + const { feature, refetchFeature } = useFeature(projectId, featureId); - const releasePlans = featureReleasePlansEnabled - ? typeof environment === 'object' - ? environment?.releasePlans || [] - : [] - : releasePlansFromHook; + let releasePlans = releasePlansFromHook; + + if (featureReleasePlansEnabled) { + const matchingEnvironment = feature?.environments?.find( + (env) => env.name === environmentName, + ); + releasePlans = matchingEnvironment?.releasePlans || []; + } const refetch = featureReleasePlansEnabled ? refetchFeature diff --git a/frontend/src/interfaces/releasePlans.ts b/frontend/src/interfaces/releasePlans.ts index 63f879372b..6fa060e140 100644 --- a/frontend/src/interfaces/releasePlans.ts +++ b/frontend/src/interfaces/releasePlans.ts @@ -35,6 +35,9 @@ export interface IReleasePlanMilestone { name: string; releasePlanDefinitionId: string; strategies: IReleasePlanMilestoneStrategy[]; + transitionCondition?: { + intervalMinutes: number; + } | null; } export interface IReleasePlanMilestoneStrategy extends IFeatureStrategy {