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:
parent
247dd3af51
commit
a922801690
@ -69,7 +69,7 @@ export const EnvironmentAccordionBody = ({
|
|||||||
const { releasePlans } = useFeatureReleasePlans(
|
const { releasePlans } = useFeatureReleasePlans(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
featureEnvironment,
|
featureEnvironment?.name,
|
||||||
);
|
);
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const FeatureOverviewWithReleasePlans: FC<
|
|||||||
const { releasePlans } = useFeatureReleasePlans(
|
const { releasePlans } = useFeatureReleasePlans(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
environment,
|
environment?.name,
|
||||||
);
|
);
|
||||||
const envAddStrategySuggestionEnabled = useUiFlag(
|
const envAddStrategySuggestionEnabled = useUiFlag(
|
||||||
'envAddStrategySuggestion',
|
'envAddStrategySuggestion',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Add from '@mui/icons-material/Add';
|
import Add from '@mui/icons-material/Add';
|
||||||
import { Button, 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';
|
||||||
|
|
||||||
const StyledAutomationContainer = styled('div', {
|
const StyledAutomationContainer = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'status',
|
shouldForwardProp: (prop) => prop !== 'status',
|
||||||
@ -51,6 +52,9 @@ interface IMilestoneAutomationSectionProps {
|
|||||||
status?: MilestoneStatus;
|
status?: MilestoneStatus;
|
||||||
onAddAutomation?: () => void;
|
onAddAutomation?: () => void;
|
||||||
automationForm?: React.ReactNode;
|
automationForm?: React.ReactNode;
|
||||||
|
transitionCondition?: {
|
||||||
|
intervalMinutes: number;
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MilestoneAutomationSection = ({
|
export const MilestoneAutomationSection = ({
|
||||||
@ -58,6 +62,7 @@ export const MilestoneAutomationSection = ({
|
|||||||
status,
|
status,
|
||||||
onAddAutomation,
|
onAddAutomation,
|
||||||
automationForm,
|
automationForm,
|
||||||
|
transitionCondition,
|
||||||
}: IMilestoneAutomationSectionProps) => {
|
}: IMilestoneAutomationSectionProps) => {
|
||||||
if (!showAutomation) return null;
|
if (!showAutomation) return null;
|
||||||
|
|
||||||
@ -65,6 +70,10 @@ export const MilestoneAutomationSection = ({
|
|||||||
<StyledAutomationContainer status={status}>
|
<StyledAutomationContainer status={status}>
|
||||||
{automationForm ? (
|
{automationForm ? (
|
||||||
automationForm
|
automationForm
|
||||||
|
) : transitionCondition ? (
|
||||||
|
<MilestoneTransitionDisplay
|
||||||
|
intervalMinutes={transitionCondition.intervalMinutes}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledAddAutomationButton
|
<StyledAddAutomationButton
|
||||||
onClick={onAddAutomation}
|
onClick={onAddAutomation}
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -118,6 +118,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
status={status}
|
status={status}
|
||||||
onAddAutomation={onAddAutomation}
|
onAddAutomation={onAddAutomation}
|
||||||
automationForm={automationForm}
|
automationForm={automationForm}
|
||||||
|
transitionCondition={milestone.transitionCondition}
|
||||||
/>
|
/>
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
@ -174,6 +175,7 @@ export const ReleasePlanMilestone = ({
|
|||||||
status={status}
|
status={status}
|
||||||
onAddAutomation={onAddAutomation}
|
onAddAutomation={onAddAutomation}
|
||||||
automationForm={automationForm}
|
automationForm={automationForm}
|
||||||
|
transitionCondition={milestone.transitionCondition}
|
||||||
/>
|
/>
|
||||||
</StyledMilestoneContainer>
|
</StyledMilestoneContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,28 +1,28 @@
|
|||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { useReleasePlans } from '../useReleasePlans/useReleasePlans.js';
|
import { useReleasePlans } from '../useReleasePlans/useReleasePlans.js';
|
||||||
import { useFeature } from '../useFeature/useFeature.js';
|
import { useFeature } from '../useFeature/useFeature.js';
|
||||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
|
||||||
|
|
||||||
export const useFeatureReleasePlans = (
|
export const useFeatureReleasePlans = (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
environment?: IFeatureEnvironment | string,
|
environmentName?: string,
|
||||||
) => {
|
) => {
|
||||||
const featureReleasePlansEnabled = useUiFlag('featureReleasePlans');
|
const featureReleasePlansEnabled = useUiFlag('featureReleasePlans');
|
||||||
const envName =
|
|
||||||
typeof environment === 'string' ? environment : environment?.name;
|
|
||||||
const {
|
const {
|
||||||
releasePlans: releasePlansFromHook,
|
releasePlans: releasePlansFromHook,
|
||||||
refetch: refetchReleasePlans,
|
refetch: refetchReleasePlans,
|
||||||
...rest
|
...rest
|
||||||
} = useReleasePlans(projectId, featureId, envName);
|
} = useReleasePlans(projectId, featureId, environmentName);
|
||||||
const { refetchFeature } = useFeature(projectId, featureId);
|
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||||
|
|
||||||
const releasePlans = featureReleasePlansEnabled
|
let releasePlans = releasePlansFromHook;
|
||||||
? typeof environment === 'object'
|
|
||||||
? environment?.releasePlans || []
|
if (featureReleasePlansEnabled) {
|
||||||
: []
|
const matchingEnvironment = feature?.environments?.find(
|
||||||
: releasePlansFromHook;
|
(env) => env.name === environmentName,
|
||||||
|
);
|
||||||
|
releasePlans = matchingEnvironment?.releasePlans || [];
|
||||||
|
}
|
||||||
|
|
||||||
const refetch = featureReleasePlansEnabled
|
const refetch = featureReleasePlansEnabled
|
||||||
? refetchFeature
|
? refetchFeature
|
||||||
|
|||||||
@ -35,6 +35,9 @@ export interface IReleasePlanMilestone {
|
|||||||
name: string;
|
name: string;
|
||||||
releasePlanDefinitionId: string;
|
releasePlanDefinitionId: string;
|
||||||
strategies: IReleasePlanMilestoneStrategy[];
|
strategies: IReleasePlanMilestoneStrategy[];
|
||||||
|
transitionCondition?: {
|
||||||
|
intervalMinutes: number;
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReleasePlanMilestoneStrategy extends IFeatureStrategy {
|
export interface IReleasePlanMilestoneStrategy extends IFeatureStrategy {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user