mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: new add release plan dialog (#9389)
https://linear.app/unleash/issue/2-3249/adding-a-release-plan-to-a-non-cr-environment-feels-too-immediate This adds a new confirmation / preview dialog when adding a release plan. What's cool about it is that it will describe what will happen before you confirm. It also acts as the "add to CR" dialog, so we now only have 1 dialog instead of 2 separate ones. This also refactors quite a bit of our code here, hopefully simplifying it. ### Simple (env disabled)  ### CR protected (env enabled) 
This commit is contained in:
		
							parent
							
								
									2064cae20f
								
							
						
					
					
						commit
						da91ae6afe
					
				@ -7,7 +7,7 @@ import type {
 | 
			
		||||
    IChangeRequestDeleteReleasePlan,
 | 
			
		||||
    IChangeRequestStartMilestone,
 | 
			
		||||
} from 'component/changeRequest/changeRequest.types';
 | 
			
		||||
import { useReleasePlanTemplate } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplate';
 | 
			
		||||
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
 | 
			
		||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
 | 
			
		||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
 | 
			
		||||
import EventDiff from 'component/events/EventDiff/EventDiff';
 | 
			
		||||
@ -181,32 +181,11 @@ const AddReleasePlan: FC<{
 | 
			
		||||
    featureName: string;
 | 
			
		||||
    actions?: ReactNode;
 | 
			
		||||
}> = ({ change, environmentName, featureName, actions }) => {
 | 
			
		||||
    const { template } = useReleasePlanTemplate(change.payload.templateId);
 | 
			
		||||
 | 
			
		||||
    if (!template) return;
 | 
			
		||||
 | 
			
		||||
    const tentativeReleasePlan = {
 | 
			
		||||
        ...template,
 | 
			
		||||
        environment: environmentName,
 | 
			
		||||
    const planPreview = useReleasePlanPreview(
 | 
			
		||||
        change.payload.templateId,
 | 
			
		||||
        featureName,
 | 
			
		||||
        milestones: template.milestones.map((milestone) => ({
 | 
			
		||||
            ...milestone,
 | 
			
		||||
            releasePlanDefinitionId: template.id,
 | 
			
		||||
            strategies: (milestone.strategies || []).map((strategy) => ({
 | 
			
		||||
                ...strategy,
 | 
			
		||||
                parameters: {
 | 
			
		||||
                    ...strategy.parameters,
 | 
			
		||||
                    ...(strategy.parameters.groupId && {
 | 
			
		||||
                        groupId: String(strategy.parameters.groupId).replaceAll(
 | 
			
		||||
                            '{{featureName}}',
 | 
			
		||||
                            featureName,
 | 
			
		||||
                        ),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
                milestoneId: milestone.id,
 | 
			
		||||
            })),
 | 
			
		||||
        })),
 | 
			
		||||
    };
 | 
			
		||||
        environmentName,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
@ -215,11 +194,11 @@ const AddReleasePlan: FC<{
 | 
			
		||||
                    <Typography color='success.dark'>
 | 
			
		||||
                        + Adding release plan:
 | 
			
		||||
                    </Typography>
 | 
			
		||||
                    <Typography>{template.name}</Typography>
 | 
			
		||||
                    <Typography>{planPreview.name}</Typography>
 | 
			
		||||
                </ChangeItemInfo>
 | 
			
		||||
                <div>{actions}</div>
 | 
			
		||||
            </ChangeItemCreateEditDeleteWrapper>
 | 
			
		||||
            <ReleasePlan plan={tentativeReleasePlan} readonly />
 | 
			
		||||
            <ReleasePlan plan={planPreview} readonly />
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,7 @@
 | 
			
		||||
import { getFeatureStrategyIcon } from 'utils/strategyNames';
 | 
			
		||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
 | 
			
		||||
import { Link, styled } from '@mui/material';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
import { Button, styled } from '@mui/material';
 | 
			
		||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
 | 
			
		||||
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
			
		||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
 | 
			
		||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
 | 
			
		||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
			
		||||
 | 
			
		||||
const StyledIcon = styled('div')(({ theme }) => ({
 | 
			
		||||
    width: theme.spacing(4),
 | 
			
		||||
@ -25,13 +18,14 @@ const StyledIcon = styled('div')(({ theme }) => ({
 | 
			
		||||
 | 
			
		||||
const StyledDescription = styled('div')(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
    fontWeight: theme.fontWeight.medium,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledName = styled(StringTruncator)(({ theme }) => ({
 | 
			
		||||
    fontWeight: theme.fontWeight.bold,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledCard = styled(Link)(({ theme }) => ({
 | 
			
		||||
const StyledCard = styled(Button)(({ theme }) => ({
 | 
			
		||||
    display: 'grid',
 | 
			
		||||
    gridTemplateColumns: '3rem 1fr',
 | 
			
		||||
    width: '20rem',
 | 
			
		||||
@ -43,82 +37,31 @@ const StyledCard = styled(Link)(({ theme }) => ({
 | 
			
		||||
    borderStyle: 'solid',
 | 
			
		||||
    borderColor: theme.palette.divider,
 | 
			
		||||
    borderRadius: theme.spacing(1),
 | 
			
		||||
    textAlign: 'left',
 | 
			
		||||
    '&:hover, &:focus': {
 | 
			
		||||
        borderColor: theme.palette.primary.main,
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
interface IFeatureReleasePlanCardProps {
 | 
			
		||||
    projectId: string;
 | 
			
		||||
    featureId: string;
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    releasePlanTemplate: IReleasePlanTemplate;
 | 
			
		||||
    setTemplateForChangeRequestDialog: (template: IReleasePlanTemplate) => void;
 | 
			
		||||
    template: IReleasePlanTemplate;
 | 
			
		||||
    onClick: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FeatureReleasePlanCard = ({
 | 
			
		||||
    projectId,
 | 
			
		||||
    featureId,
 | 
			
		||||
    environmentId,
 | 
			
		||||
    releasePlanTemplate,
 | 
			
		||||
    setTemplateForChangeRequestDialog,
 | 
			
		||||
    template: { name, description },
 | 
			
		||||
    onClick,
 | 
			
		||||
}: IFeatureReleasePlanCardProps) => {
 | 
			
		||||
    const Icon = getFeatureStrategyIcon('releasePlanTemplate');
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
    const { refetch } = useReleasePlans(projectId, featureId, environmentId);
 | 
			
		||||
    const { addReleasePlanToFeature } = useReleasePlansApi();
 | 
			
		||||
    const { setToastApiError, setToastData } = useToast();
 | 
			
		||||
    const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
 | 
			
		||||
    const releasePlanChangeRequestsEnabled = useUiFlag(
 | 
			
		||||
        'releasePlanChangeRequests',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const addReleasePlan = async () => {
 | 
			
		||||
        try {
 | 
			
		||||
            if (
 | 
			
		||||
                releasePlanChangeRequestsEnabled &&
 | 
			
		||||
                isChangeRequestConfigured(environmentId)
 | 
			
		||||
            ) {
 | 
			
		||||
                setTemplateForChangeRequestDialog(releasePlanTemplate);
 | 
			
		||||
            } else {
 | 
			
		||||
                await addReleasePlanToFeature(
 | 
			
		||||
                    featureId,
 | 
			
		||||
                    releasePlanTemplate.id,
 | 
			
		||||
                    projectId,
 | 
			
		||||
                    environmentId,
 | 
			
		||||
                );
 | 
			
		||||
                setToastData({
 | 
			
		||||
                    type: 'success',
 | 
			
		||||
                    text: 'Release plan added',
 | 
			
		||||
                });
 | 
			
		||||
                refetch();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            trackEvent('release-management', {
 | 
			
		||||
                props: {
 | 
			
		||||
                    eventType: 'add-plan',
 | 
			
		||||
                    plan: releasePlanTemplate.name,
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error: unknown) {
 | 
			
		||||
            setToastApiError(formatUnknownError(error));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledCard onClick={addReleasePlan}>
 | 
			
		||||
        <StyledCard onClick={onClick}>
 | 
			
		||||
            <StyledIcon>
 | 
			
		||||
                <Icon />
 | 
			
		||||
            </StyledIcon>
 | 
			
		||||
            <div>
 | 
			
		||||
                <StyledName
 | 
			
		||||
                    text={releasePlanTemplate.name}
 | 
			
		||||
                    maxWidth='200'
 | 
			
		||||
                    maxLength={25}
 | 
			
		||||
                />
 | 
			
		||||
                <StyledDescription>
 | 
			
		||||
                    {releasePlanTemplate.description}
 | 
			
		||||
                </StyledDescription>
 | 
			
		||||
                <StyledName text={name} maxWidth='200' maxLength={25} />
 | 
			
		||||
                <StyledDescription>{description}</StyledDescription>
 | 
			
		||||
            </div>
 | 
			
		||||
        </StyledCard>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -10,11 +10,16 @@ import { FeatureStrategyMenuCards } from './FeatureStrategyMenuCards/FeatureStra
 | 
			
		||||
import { formatCreateStrategyPath } from '../FeatureStrategyCreate/FeatureStrategyCreate';
 | 
			
		||||
import MoreVert from '@mui/icons-material/MoreVert';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
import { ReleasePlanAddChangeRequestDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ChangeRequest/ReleasePlanAddChangeRequestDialog';
 | 
			
		||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
 | 
			
		||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
 | 
			
		||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { ReleasePlanAddDialog } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanAddDialog';
 | 
			
		||||
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
 | 
			
		||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
 | 
			
		||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
 | 
			
		||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
			
		||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
			
		||||
 | 
			
		||||
interface IFeatureStrategyMenuProps {
 | 
			
		||||
    label: string;
 | 
			
		||||
@ -54,14 +59,25 @@ export const FeatureStrategyMenu = ({
 | 
			
		||||
    const [anchor, setAnchor] = useState<Element>();
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
    const [templateForChangeRequestDialog, setTemplateForChangeRequestDialog] =
 | 
			
		||||
        useState<IReleasePlanTemplate | undefined>();
 | 
			
		||||
    const [selectedTemplate, setSelectedTemplate] =
 | 
			
		||||
        useState<IReleasePlanTemplate>();
 | 
			
		||||
    const [addReleasePlanOpen, setAddReleasePlanOpen] = useState(false);
 | 
			
		||||
    const isPopoverOpen = Boolean(anchor);
 | 
			
		||||
    const popoverId = isPopoverOpen ? 'FeatureStrategyMenuPopover' : undefined;
 | 
			
		||||
    const { setToastData } = useToast();
 | 
			
		||||
    const { setToastApiError, setToastData } = useToast();
 | 
			
		||||
    const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
 | 
			
		||||
    const { addChange } = useChangeRequestApi();
 | 
			
		||||
    const { refetch: refetchChangeRequests } =
 | 
			
		||||
        usePendingChangeRequests(projectId);
 | 
			
		||||
    const { refetch } = useReleasePlans(projectId, featureId, environmentId);
 | 
			
		||||
    const { addReleasePlanToFeature } = useReleasePlansApi();
 | 
			
		||||
    const releasePlanChangeRequestsEnabled = useUiFlag(
 | 
			
		||||
        'releasePlanChangeRequests',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const crProtected =
 | 
			
		||||
        releasePlanChangeRequestsEnabled &&
 | 
			
		||||
        isChangeRequestConfigured(environmentId);
 | 
			
		||||
 | 
			
		||||
    const onClose = () => {
 | 
			
		||||
        setAnchor(undefined);
 | 
			
		||||
@ -80,23 +96,52 @@ export const FeatureStrategyMenu = ({
 | 
			
		||||
        setAnchor(event.currentTarget);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const addReleasePlanToChangeRequest = async () => {
 | 
			
		||||
        await addChange(projectId, environmentId, {
 | 
			
		||||
            feature: featureId,
 | 
			
		||||
            action: 'addReleasePlan',
 | 
			
		||||
            payload: {
 | 
			
		||||
                templateId: templateForChangeRequestDialog?.id,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    const addReleasePlan = async () => {
 | 
			
		||||
        if (!selectedTemplate) return;
 | 
			
		||||
 | 
			
		||||
        await refetchChangeRequests();
 | 
			
		||||
        try {
 | 
			
		||||
            if (crProtected) {
 | 
			
		||||
                await addChange(projectId, environmentId, {
 | 
			
		||||
                    feature: featureId,
 | 
			
		||||
                    action: 'addReleasePlan',
 | 
			
		||||
                    payload: {
 | 
			
		||||
                        templateId: selectedTemplate.id,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        setToastData({
 | 
			
		||||
            type: 'success',
 | 
			
		||||
            text: 'Added to draft',
 | 
			
		||||
        });
 | 
			
		||||
                setToastData({
 | 
			
		||||
                    type: 'success',
 | 
			
		||||
                    text: 'Added to draft',
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        setTemplateForChangeRequestDialog(undefined);
 | 
			
		||||
                refetchChangeRequests();
 | 
			
		||||
            } else {
 | 
			
		||||
                await addReleasePlanToFeature(
 | 
			
		||||
                    featureId,
 | 
			
		||||
                    selectedTemplate.id,
 | 
			
		||||
                    projectId,
 | 
			
		||||
                    environmentId,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                setToastData({
 | 
			
		||||
                    type: 'success',
 | 
			
		||||
                    text: 'Release plan added',
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                refetch();
 | 
			
		||||
            }
 | 
			
		||||
            trackEvent('release-management', {
 | 
			
		||||
                props: {
 | 
			
		||||
                    eventType: 'add-plan',
 | 
			
		||||
                    plan: selectedTemplate.name,
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error: unknown) {
 | 
			
		||||
            setToastApiError(formatUnknownError(error));
 | 
			
		||||
        } finally {
 | 
			
		||||
            setAddReleasePlanOpen(false);
 | 
			
		||||
            setSelectedTemplate(undefined);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const createStrategyPath = formatCreateStrategyPath(
 | 
			
		||||
@ -156,19 +201,24 @@ export const FeatureStrategyMenu = ({
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                    featureId={featureId}
 | 
			
		||||
                    environmentId={environmentId}
 | 
			
		||||
                    setTemplateForChangeRequestDialog={
 | 
			
		||||
                        setTemplateForChangeRequestDialog
 | 
			
		||||
                    }
 | 
			
		||||
                    onAddReleasePlan={(template) => {
 | 
			
		||||
                        setSelectedTemplate(template);
 | 
			
		||||
                        setAddReleasePlanOpen(true);
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            </Popover>
 | 
			
		||||
            <ReleasePlanAddChangeRequestDialog
 | 
			
		||||
                onConfirm={addReleasePlanToChangeRequest}
 | 
			
		||||
                onClosing={() => setTemplateForChangeRequestDialog(undefined)}
 | 
			
		||||
                isOpen={Boolean(templateForChangeRequestDialog)}
 | 
			
		||||
                featureId={featureId}
 | 
			
		||||
                environmentId={environmentId}
 | 
			
		||||
                releaseTemplate={templateForChangeRequestDialog}
 | 
			
		||||
            />
 | 
			
		||||
            {selectedTemplate && (
 | 
			
		||||
                <ReleasePlanAddDialog
 | 
			
		||||
                    open={addReleasePlanOpen}
 | 
			
		||||
                    setOpen={setAddReleasePlanOpen}
 | 
			
		||||
                    onConfirm={addReleasePlan}
 | 
			
		||||
                    template={selectedTemplate}
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                    featureName={featureId}
 | 
			
		||||
                    environment={environmentId}
 | 
			
		||||
                    crProtected={crProtected}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
        </StyledStrategyMenu>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ interface IFeatureStrategyMenuCardsProps {
 | 
			
		||||
    projectId: string;
 | 
			
		||||
    featureId: string;
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    setTemplateForChangeRequestDialog: (template: IReleasePlanTemplate) => void;
 | 
			
		||||
    onAddReleasePlan: (template: IReleasePlanTemplate) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const StyledTypography = styled(Typography)(({ theme }) => ({
 | 
			
		||||
@ -22,7 +22,7 @@ export const FeatureStrategyMenuCards = ({
 | 
			
		||||
    projectId,
 | 
			
		||||
    featureId,
 | 
			
		||||
    environmentId,
 | 
			
		||||
    setTemplateForChangeRequestDialog,
 | 
			
		||||
    onAddReleasePlan,
 | 
			
		||||
}: IFeatureStrategyMenuCardsProps) => {
 | 
			
		||||
    const { strategies } = useStrategies();
 | 
			
		||||
    const { templates } = useReleasePlanTemplates();
 | 
			
		||||
@ -67,13 +67,8 @@ export const FeatureStrategyMenuCards = ({
 | 
			
		||||
                        {templates.map((template) => (
 | 
			
		||||
                            <ListItem key={template.id}>
 | 
			
		||||
                                <FeatureReleasePlanCard
 | 
			
		||||
                                    projectId={projectId}
 | 
			
		||||
                                    featureId={featureId}
 | 
			
		||||
                                    environmentId={environmentId}
 | 
			
		||||
                                    releasePlanTemplate={template}
 | 
			
		||||
                                    setTemplateForChangeRequestDialog={
 | 
			
		||||
                                        setTemplateForChangeRequestDialog
 | 
			
		||||
                                    }
 | 
			
		||||
                                    template={template}
 | 
			
		||||
                                    onClick={() => onAddReleasePlan(template)}
 | 
			
		||||
                                />
 | 
			
		||||
                            </ListItem>
 | 
			
		||||
                        ))}
 | 
			
		||||
 | 
			
		||||
@ -1,51 +0,0 @@
 | 
			
		||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
 | 
			
		||||
import { styled, Button } from '@mui/material';
 | 
			
		||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
 | 
			
		||||
 | 
			
		||||
const StyledBoldSpan = styled('span')(({ theme }) => ({
 | 
			
		||||
    fontWeight: theme.typography.fontWeightBold,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
interface IReleasePlanAddChangeRequestDialogProps {
 | 
			
		||||
    featureId: string;
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    releaseTemplate?: IReleasePlanTemplate;
 | 
			
		||||
    isOpen: boolean;
 | 
			
		||||
    onConfirm: () => Promise<void>;
 | 
			
		||||
    onClosing: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ReleasePlanAddChangeRequestDialog = ({
 | 
			
		||||
    featureId,
 | 
			
		||||
    environmentId,
 | 
			
		||||
    releaseTemplate,
 | 
			
		||||
    isOpen,
 | 
			
		||||
    onConfirm,
 | 
			
		||||
    onClosing,
 | 
			
		||||
}: IReleasePlanAddChangeRequestDialogProps) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialogue
 | 
			
		||||
            title='Request changes'
 | 
			
		||||
            open={isOpen}
 | 
			
		||||
            secondaryButtonText='Cancel'
 | 
			
		||||
            onClose={onClosing}
 | 
			
		||||
            customButton={
 | 
			
		||||
                <Button
 | 
			
		||||
                    color='primary'
 | 
			
		||||
                    variant='contained'
 | 
			
		||||
                    onClick={onConfirm}
 | 
			
		||||
                    autoFocus={true}
 | 
			
		||||
                >
 | 
			
		||||
                    Add suggestion to draft
 | 
			
		||||
                </Button>
 | 
			
		||||
            }
 | 
			
		||||
        >
 | 
			
		||||
            <p>
 | 
			
		||||
                <StyledBoldSpan>Add</StyledBoldSpan> release template{' '}
 | 
			
		||||
                <StyledBoldSpan>{releaseTemplate?.name}</StyledBoldSpan> to{' '}
 | 
			
		||||
                <StyledBoldSpan>{featureId}</StyledBoldSpan> in{' '}
 | 
			
		||||
                <StyledBoldSpan>{environmentId}</StyledBoldSpan>
 | 
			
		||||
            </p>
 | 
			
		||||
        </Dialogue>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,92 @@
 | 
			
		||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
 | 
			
		||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
 | 
			
		||||
import { ReleasePlan } from './ReleasePlan';
 | 
			
		||||
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
 | 
			
		||||
import { styled, Typography, Alert } from '@mui/material';
 | 
			
		||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
 | 
			
		||||
const StyledReleasePlanContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    margin: theme.spacing(2, 0),
 | 
			
		||||
}));
 | 
			
		||||
interface IReleasePlanAddDialogProps {
 | 
			
		||||
    open: boolean;
 | 
			
		||||
    setOpen: React.Dispatch<React.SetStateAction<boolean>>;
 | 
			
		||||
    onConfirm: () => void;
 | 
			
		||||
    template: IReleasePlanTemplate;
 | 
			
		||||
    projectId: string;
 | 
			
		||||
    featureName: string;
 | 
			
		||||
    environment: string;
 | 
			
		||||
    crProtected?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ReleasePlanAddDialog = ({
 | 
			
		||||
    open,
 | 
			
		||||
    setOpen,
 | 
			
		||||
    onConfirm,
 | 
			
		||||
    template,
 | 
			
		||||
    projectId,
 | 
			
		||||
    featureName,
 | 
			
		||||
    environment,
 | 
			
		||||
    crProtected,
 | 
			
		||||
}: IReleasePlanAddDialogProps) => {
 | 
			
		||||
    const { feature } = useFeature(projectId, featureName);
 | 
			
		||||
 | 
			
		||||
    const environmentData = feature?.environments.find(
 | 
			
		||||
        ({ name }) => name === environment,
 | 
			
		||||
    );
 | 
			
		||||
    const environmentEnabled = environmentData?.enabled;
 | 
			
		||||
 | 
			
		||||
    const planPreview = useReleasePlanPreview(
 | 
			
		||||
        template.id,
 | 
			
		||||
        featureName,
 | 
			
		||||
        environment,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const firstMilestone = planPreview.milestones[0];
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialogue
 | 
			
		||||
            title='Add release plan?'
 | 
			
		||||
            open={open}
 | 
			
		||||
            primaryButtonText={
 | 
			
		||||
                crProtected ? 'Add suggestion to draft' : 'Add release plan'
 | 
			
		||||
            }
 | 
			
		||||
            secondaryButtonText='Cancel'
 | 
			
		||||
            onClick={onConfirm}
 | 
			
		||||
            onClose={() => setOpen(false)}
 | 
			
		||||
        >
 | 
			
		||||
            {environmentEnabled ? (
 | 
			
		||||
                <Alert severity='info'>
 | 
			
		||||
                    This environment is currently <strong>enabled</strong>.
 | 
			
		||||
                    {firstMilestone && (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            The first milestone will be started as soon as the
 | 
			
		||||
                            release plan is added:{' '}
 | 
			
		||||
                            <strong>{planPreview.milestones[0].name}</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )}
 | 
			
		||||
                </Alert>
 | 
			
		||||
            ) : (
 | 
			
		||||
                <Alert severity='warning'>
 | 
			
		||||
                    This environment is currently <strong>disabled</strong>.
 | 
			
		||||
                    <p>
 | 
			
		||||
                        The milestones will not start automatically after adding
 | 
			
		||||
                        the release plan. They will remain paused until the
 | 
			
		||||
                        environment is enabled.
 | 
			
		||||
                    </p>
 | 
			
		||||
                </Alert>
 | 
			
		||||
            )}
 | 
			
		||||
            <StyledReleasePlanContainer>
 | 
			
		||||
                <ReleasePlan plan={planPreview} readonly />
 | 
			
		||||
            </StyledReleasePlanContainer>
 | 
			
		||||
            {crProtected && (
 | 
			
		||||
                <Typography sx={{ mt: 4 }}>
 | 
			
		||||
                    <strong>Adding</strong> release plan template{' '}
 | 
			
		||||
                    <strong>{template?.name}</strong> to{' '}
 | 
			
		||||
                    <strong>{featureName}</strong> in{' '}
 | 
			
		||||
                    <strong>{environment}</strong>.
 | 
			
		||||
                </Typography>
 | 
			
		||||
            )}
 | 
			
		||||
        </Dialogue>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								frontend/src/hooks/useReleasePlanPreview.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/src/hooks/useReleasePlanPreview.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
import type { IReleasePlan } from 'interfaces/releasePlans';
 | 
			
		||||
import { useReleasePlanTemplate } from './api/getters/useReleasePlanTemplates/useReleasePlanTemplate';
 | 
			
		||||
 | 
			
		||||
export const useReleasePlanPreview = (
 | 
			
		||||
    templateId: string,
 | 
			
		||||
    featureName: string,
 | 
			
		||||
    environment: string,
 | 
			
		||||
): IReleasePlan => {
 | 
			
		||||
    const { template } = useReleasePlanTemplate(templateId);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        ...template,
 | 
			
		||||
        featureName,
 | 
			
		||||
        environment,
 | 
			
		||||
        milestones: template.milestones.map((milestone) => ({
 | 
			
		||||
            ...milestone,
 | 
			
		||||
            releasePlanDefinitionId: template.id,
 | 
			
		||||
            strategies: (milestone.strategies || []).map((strategy) => ({
 | 
			
		||||
                ...strategy,
 | 
			
		||||
                parameters: {
 | 
			
		||||
                    ...strategy.parameters,
 | 
			
		||||
                    ...(strategy.parameters.groupId && {
 | 
			
		||||
                        groupId: String(strategy.parameters.groupId).replaceAll(
 | 
			
		||||
                            '{{featureName}}',
 | 
			
		||||
                            featureName,
 | 
			
		||||
                        ),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
                milestoneId: milestone.id,
 | 
			
		||||
            })),
 | 
			
		||||
        })),
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user