1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

feat: Add transition condition UI for release plan milestones (#10768)

This commit is contained in:
Fredrik Strand Oseberg 2025-10-09 11:41:58 +02:00 committed by GitHub
parent 247dd3af51
commit a922801690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 82 additions and 13 deletions

View File

@ -69,7 +69,7 @@ export const EnvironmentAccordionBody = ({
const { releasePlans } = useFeatureReleasePlans(
projectId,
featureId,
featureEnvironment,
featureEnvironment?.name,
);
const { trackEvent } = usePlausibleTracker();

View File

@ -21,7 +21,7 @@ const FeatureOverviewWithReleasePlans: FC<
const { releasePlans } = useFeatureReleasePlans(
projectId,
featureId,
environment,
environment?.name,
);
const envAddStrategySuggestionEnabled = useUiFlag(
'envAddStrategySuggestion',

View File

@ -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 = ({
<StyledAutomationContainer status={status}>
{automationForm ? (
automationForm
) : transitionCondition ? (
<MilestoneTransitionDisplay
intervalMinutes={transitionCondition.intervalMinutes}
/>
) : (
<StyledAddAutomationButton
onClick={onAddAutomation}

View File

@ -0,0 +1,55 @@
import BoltIcon from '@mui/icons-material/Bolt';
import { styled } from '@mui/material';
import { formatDuration, intervalToDuration } from 'date-fns';
const StyledDisplayContainer = styled('div')(({ theme }) => ({
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 (
<StyledDisplayContainer>
<StyledIcon />
<StyledText>
Proceed to the next milestone after{' '}
{formatInterval(intervalMinutes)}
</StyledText>
</StyledDisplayContainer>
);
};

View File

@ -118,6 +118,7 @@ export const ReleasePlanMilestone = ({
status={status}
onAddAutomation={onAddAutomation}
automationForm={automationForm}
transitionCondition={milestone.transitionCondition}
/>
</StyledMilestoneContainer>
);
@ -174,6 +175,7 @@ export const ReleasePlanMilestone = ({
status={status}
onAddAutomation={onAddAutomation}
automationForm={automationForm}
transitionCondition={milestone.transitionCondition}
/>
</StyledMilestoneContainer>
);

View File

@ -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

View File

@ -35,6 +35,9 @@ export interface IReleasePlanMilestone {
name: string;
releasePlanDefinitionId: string;
strategies: IReleasePlanMilestoneStrategy[];
transitionCondition?: {
intervalMinutes: number;
} | null;
}
export interface IReleasePlanMilestoneStrategy extends IFeatureStrategy {