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