1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-11-24 20:06:55 +01:00

refactor: simplify components

This commit is contained in:
FredrikOseberg 2025-10-21 14:07:19 +02:00
parent d9503de15c
commit 5869c7e04f
No known key found for this signature in database
GPG Key ID: 282FD8A6D8F9BCF0
4 changed files with 87 additions and 148 deletions

View File

@ -340,15 +340,12 @@ const CreateMilestoneProgression: FC<{
<MilestoneAutomationSection status={status}> <MilestoneAutomationSection status={status}>
<MilestoneTransitionDisplay <MilestoneTransitionDisplay
intervalMinutes={milestone.transitionCondition.intervalMinutes} intervalMinutes={milestone.transitionCondition.intervalMinutes}
onSave={async (payload) => {
onUpdateChangeRequestSubmit?.(milestone.id, payload);
}}
onDelete={() => onDeleteChangeRequestSubmit?.(milestone.id)} onDelete={() => onDeleteChangeRequestSubmit?.(milestone.id)}
milestoneName={milestone.name} milestoneName={milestone.name}
status={status} status={status}
projectId={projectId}
environment={environmentName}
featureName={featureName}
sourceMilestoneId={milestone.id}
onUpdate={onUpdate || (() => {})}
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
hasPendingUpdate={false} hasPendingUpdate={false}
hasPendingDelete={false} hasPendingDelete={false}
/> />
@ -467,15 +464,12 @@ const UpdateMilestoneProgression: FC<{
<MilestoneAutomationSection status={status}> <MilestoneAutomationSection status={status}>
<MilestoneTransitionDisplay <MilestoneTransitionDisplay
intervalMinutes={milestone.transitionCondition.intervalMinutes} intervalMinutes={milestone.transitionCondition.intervalMinutes}
onSave={async (payload) => {
onUpdateChangeRequestSubmit?.(milestone.id, payload);
}}
onDelete={() => onDeleteChangeRequestSubmit?.(milestone.id)} onDelete={() => onDeleteChangeRequestSubmit?.(milestone.id)}
milestoneName={milestone.name} milestoneName={milestone.name}
status={status} status={status}
projectId={projectId}
environment={environmentName}
featureName={featureName}
sourceMilestoneId={milestone.id}
onUpdate={onUpdate || (() => {})}
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
hasPendingUpdate={false} hasPendingUpdate={false}
hasPendingDelete={false} hasPendingDelete={false}
/> />
@ -682,15 +676,12 @@ const ConsolidatedProgressionChanges: FC<{
<MilestoneAutomationSection status={status}> <MilestoneAutomationSection status={status}>
<MilestoneTransitionDisplay <MilestoneTransitionDisplay
intervalMinutes={displayMilestone.transitionCondition.intervalMinutes} intervalMinutes={displayMilestone.transitionCondition.intervalMinutes}
onSave={async (payload) => {
onUpdateChangeRequestSubmit?.(displayMilestone.id, payload);
}}
onDelete={() => onDeleteChangeRequestSubmit?.(displayMilestone.id)} onDelete={() => onDeleteChangeRequestSubmit?.(displayMilestone.id)}
milestoneName={displayMilestone.name} milestoneName={displayMilestone.name}
status={status} status={status}
projectId={projectId}
environment={environmentName}
featureName={featureName}
sourceMilestoneId={displayMilestone.id}
onUpdate={onUpdate || (() => {})}
onChangeRequestSubmit={onUpdateChangeRequestSubmit}
hasPendingUpdate={false} hasPendingUpdate={false}
hasPendingDelete={Boolean(deleteChange)} hasPendingDelete={Boolean(deleteChange)}
/> />

View File

@ -1,12 +1,7 @@
import { useState } from 'react';
import { Button, styled } from '@mui/material'; import { Button, styled } from '@mui/material';
import BoltIcon from '@mui/icons-material/Bolt'; import BoltIcon from '@mui/icons-material/Bolt';
import { useMilestoneProgressionForm } from '../hooks/useMilestoneProgressionForm.js'; import { useMilestoneProgressionForm } from '../hooks/useMilestoneProgressionForm.js';
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { MilestoneProgressionTimeInput } from './MilestoneProgressionTimeInput.tsx'; import { MilestoneProgressionTimeInput } from './MilestoneProgressionTimeInput.tsx';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import type { CreateMilestoneProgressionSchema } from 'openapi'; import type { CreateMilestoneProgressionSchema } from 'openapi';
const StyledFormContainer = styled('div')(({ theme }) => ({ const StyledFormContainer = styled('div')(({ theme }) => ({
@ -60,74 +55,27 @@ const StyledErrorMessage = styled('span')(({ theme }) => ({
interface IMilestoneProgressionFormProps { interface IMilestoneProgressionFormProps {
sourceMilestoneId: string; sourceMilestoneId: string;
targetMilestoneId: string; targetMilestoneId: string;
projectId: string; onSubmit: (payload: CreateMilestoneProgressionSchema) => Promise<void>;
environment: string;
featureName: string;
onSave: () => void;
onCancel: () => void; onCancel: () => void;
onChangeRequestSubmit?: (
progressionPayload: CreateMilestoneProgressionSchema,
) => void;
} }
export const MilestoneProgressionForm = ({ export const MilestoneProgressionForm = ({
sourceMilestoneId, sourceMilestoneId,
targetMilestoneId, targetMilestoneId,
projectId, onSubmit,
environment,
featureName,
onSave,
onCancel, onCancel,
onChangeRequestSubmit,
}: IMilestoneProgressionFormProps) => { }: IMilestoneProgressionFormProps) => {
const form = useMilestoneProgressionForm( const form = useMilestoneProgressionForm(
sourceMilestoneId, sourceMilestoneId,
targetMilestoneId, targetMilestoneId,
); );
const { createMilestoneProgression } = useMilestoneProgressionsApi();
const { setToastData, setToastApiError } = useToast();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChangeRequestSubmit = () => {
const progressionPayload = form.getProgressionPayload();
onChangeRequestSubmit?.(progressionPayload);
};
const handleDirectSubmit = async () => {
setIsSubmitting(true);
try {
await createMilestoneProgression(
projectId,
environment,
featureName,
form.getProgressionPayload(),
);
setToastData({
type: 'success',
text: 'Automation configured successfully',
});
onSave();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
} finally {
setIsSubmitting(false);
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (isSubmitting) return;
if (!form.validate()) { if (!form.validate()) {
return; return;
} }
if (isChangeRequestConfigured(environment) && onChangeRequestSubmit) { await onSubmit(form.getProgressionPayload());
handleChangeRequestSubmit();
} else {
await handleDirectSubmit();
}
}; };
const handleKeyDown = (event: React.KeyboardEvent) => { const handleKeyDown = (event: React.KeyboardEvent) => {
@ -150,7 +98,6 @@ export const MilestoneProgressionForm = ({
timeUnit={form.timeUnit} timeUnit={form.timeUnit}
onTimeValueChange={form.handleTimeValueChange} onTimeValueChange={form.handleTimeValueChange}
onTimeUnitChange={form.handleTimeUnitChange} onTimeUnitChange={form.handleTimeUnitChange}
disabled={isSubmitting}
/> />
</StyledTopRow> </StyledTopRow>
<StyledButtonGroup> <StyledButtonGroup>
@ -161,7 +108,6 @@ export const MilestoneProgressionForm = ({
variant='outlined' variant='outlined'
onClick={onCancel} onClick={onCancel}
size='small' size='small'
disabled={isSubmitting}
> >
Cancel Cancel
</Button> </Button>
@ -170,9 +116,8 @@ export const MilestoneProgressionForm = ({
color='primary' color='primary'
onClick={handleSubmit} onClick={handleSubmit}
size='small' size='small'
disabled={isSubmitting}
> >
{isSubmitting ? 'Saving...' : 'Save'} Save
</Button> </Button>
</StyledButtonGroup> </StyledButtonGroup>
</StyledFormContainer> </StyledFormContainer>

View File

@ -3,16 +3,11 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import { Button, IconButton, styled } from '@mui/material'; import { Button, IconButton, styled } from '@mui/material';
import { Badge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx'; import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx';
import { useState } from 'react';
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { MilestoneProgressionTimeInput } from '../MilestoneProgressionForm/MilestoneProgressionTimeInput.tsx'; import { MilestoneProgressionTimeInput } from '../MilestoneProgressionForm/MilestoneProgressionTimeInput.tsx';
import { import {
useMilestoneProgressionForm, useMilestoneProgressionForm,
getTimeValueAndUnitFromMinutes, getTimeValueAndUnitFromMinutes,
} from '../hooks/useMilestoneProgressionForm.js'; } from '../hooks/useMilestoneProgressionForm.js';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import type { UpdateMilestoneProgressionSchema } from 'openapi'; import type { UpdateMilestoneProgressionSchema } from 'openapi';
const StyledDisplayContainer = styled('div')(({ theme }) => ({ const StyledDisplayContainer = styled('div')(({ theme }) => ({
@ -62,50 +57,32 @@ const StyledButtonGroup = styled('div')(({ theme }) => ({
interface IMilestoneTransitionDisplayProps { interface IMilestoneTransitionDisplayProps {
intervalMinutes: number; intervalMinutes: number;
onSave: (payload: UpdateMilestoneProgressionSchema) => Promise<void>;
onDelete: () => void; onDelete: () => void;
milestoneName: string; milestoneName: string;
status?: MilestoneStatus; status?: MilestoneStatus;
projectId: string;
environment: string;
featureName: string;
sourceMilestoneId: string;
onUpdate: () => void;
onChangeRequestSubmit?: (
sourceMilestoneId: string,
payload: UpdateMilestoneProgressionSchema,
) => void;
hasPendingUpdate?: boolean; hasPendingUpdate?: boolean;
hasPendingDelete?: boolean; hasPendingDelete?: boolean;
} }
export const MilestoneTransitionDisplay = ({ export const MilestoneTransitionDisplay = ({
intervalMinutes, intervalMinutes,
onSave,
onDelete, onDelete,
milestoneName, milestoneName,
status, status,
projectId,
environment,
featureName,
sourceMilestoneId,
onUpdate,
onChangeRequestSubmit,
hasPendingUpdate = false, hasPendingUpdate = false,
hasPendingDelete = false, hasPendingDelete = false,
}: IMilestoneTransitionDisplayProps) => { }: IMilestoneTransitionDisplayProps) => {
const { updateMilestoneProgression } = useMilestoneProgressionsApi();
const { setToastData, setToastApiError } = useToast();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const initial = getTimeValueAndUnitFromMinutes(intervalMinutes); const initial = getTimeValueAndUnitFromMinutes(intervalMinutes);
const form = useMilestoneProgressionForm( const form = useMilestoneProgressionForm(
sourceMilestoneId, '', // sourceMilestoneId not needed for display
sourceMilestoneId, // We don't need targetMilestone for edit, just reuse source '', // targetMilestoneId not needed for display
{ {
timeValue: initial.value, timeValue: initial.value,
timeUnit: initial.unit, timeUnit: initial.unit,
}, },
); );
const [isSubmitting, setIsSubmitting] = useState(false);
const currentIntervalMinutes = form.getIntervalMinutes(); const currentIntervalMinutes = form.getIntervalMinutes();
const hasChanged = currentIntervalMinutes !== intervalMinutes; const hasChanged = currentIntervalMinutes !== intervalMinutes;
@ -113,7 +90,7 @@ export const MilestoneTransitionDisplay = ({
const showDraftBadge = hasPendingUpdate || hasPendingDelete; const showDraftBadge = hasPendingUpdate || hasPendingDelete;
const handleSave = async () => { const handleSave = async () => {
if (isSubmitting || !hasChanged) return; if (!hasChanged) return;
const payload: UpdateMilestoneProgressionSchema = { const payload: UpdateMilestoneProgressionSchema = {
transitionCondition: { transitionCondition: {
@ -121,32 +98,9 @@ export const MilestoneTransitionDisplay = ({
}, },
}; };
if (isChangeRequestConfigured(environment) && onChangeRequestSubmit) { await onSave(payload);
onChangeRequestSubmit(sourceMilestoneId, payload); // Reset the form after save
// Reset the form after submitting to change request handleReset();
handleReset();
return;
}
setIsSubmitting(true);
try {
await updateMilestoneProgression(
projectId,
environment,
featureName,
sourceMilestoneId,
payload,
);
setToastData({
type: 'success',
text: 'Automation updated successfully',
});
onUpdate();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
} finally {
setIsSubmitting(false);
}
}; };
const handleReset = () => { const handleReset = () => {
@ -177,7 +131,6 @@ export const MilestoneTransitionDisplay = ({
timeUnit={form.timeUnit} timeUnit={form.timeUnit}
onTimeValueChange={form.handleTimeValueChange} onTimeValueChange={form.handleTimeValueChange}
onTimeUnitChange={form.handleTimeUnitChange} onTimeUnitChange={form.handleTimeUnitChange}
disabled={isSubmitting}
/> />
</StyledContentGroup> </StyledContentGroup>
<StyledButtonGroup> <StyledButtonGroup>
@ -187,9 +140,8 @@ export const MilestoneTransitionDisplay = ({
color='primary' color='primary'
onClick={handleSave} onClick={handleSave}
size='small' size='small'
disabled={isSubmitting}
> >
{isSubmitting ? 'Saving...' : 'Save'} Save
</Button> </Button>
)} )}
{showDraftBadge && ( {showDraftBadge && (
@ -202,7 +154,6 @@ export const MilestoneTransitionDisplay = ({
size='small' size='small'
aria-label={`Delete automation for ${milestoneName}`} aria-label={`Delete automation for ${milestoneName}`}
sx={{ padding: 0.5 }} sx={{ padding: 0.5 }}
disabled={isSubmitting}
> >
<DeleteOutlineIcon fontSize='small' /> <DeleteOutlineIcon fontSize='small' />
</IconButton> </IconButton>

View File

@ -12,6 +12,10 @@ import { MilestoneTransitionDisplay } from '../ReleasePlanMilestone/MilestoneTra
import { ReleasePlanMilestone } from '../ReleasePlanMilestone/ReleasePlanMilestone.tsx'; import { ReleasePlanMilestone } from '../ReleasePlanMilestone/ReleasePlanMilestone.tsx';
import type { MilestoneStatus } from '../ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx'; import type { MilestoneStatus } from '../ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx';
import { MilestoneProgressionForm } from '../MilestoneProgressionForm/MilestoneProgressionForm.tsx'; import { MilestoneProgressionForm } from '../MilestoneProgressionForm/MilestoneProgressionForm.tsx';
import { useMilestoneProgressionsApi } from 'hooks/api/actions/useMilestoneProgressionsApi/useMilestoneProgressionsApi';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
const StyledConnection = styled('div', { const StyledConnection = styled('div', {
shouldForwardProp: (prop) => prop !== 'isCompleted', shouldForwardProp: (prop) => prop !== 'isCompleted',
@ -124,12 +128,71 @@ export const ReleasePlanMilestoneItem = ({
featureName, featureName,
onUpdate, onUpdate,
}: IReleasePlanMilestoneItemProps) => { }: IReleasePlanMilestoneItemProps) => {
const { createMilestoneProgression, updateMilestoneProgression } =
useMilestoneProgressionsApi();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { setToastData, setToastApiError } = useToast();
const isNotLastMilestone = index < milestones.length - 1; const isNotLastMilestone = index < milestones.length - 1;
const isProgressionFormOpen = progressionFormOpenIndex === index; const isProgressionFormOpen = progressionFormOpenIndex === index;
const nextMilestoneId = milestones[index + 1]?.id || ''; const nextMilestoneId = milestones[index + 1]?.id || '';
const handleOpenProgressionForm = () => onSetProgressionFormOpenIndex(index); const handleOpenProgressionForm = () => onSetProgressionFormOpenIndex(index);
const handleCloseProgressionForm = () => onSetProgressionFormOpenIndex(null); const handleCloseProgressionForm = () => onSetProgressionFormOpenIndex(null);
// Unified handler for creating progression
const handleCreateProgression = async (
payload: CreateMilestoneProgressionSchema,
) => {
if (isChangeRequestConfigured(environment)) {
onProgressionChangeRequestSubmit(payload);
handleCloseProgressionForm();
return;
}
try {
await createMilestoneProgression(
projectId,
environment,
featureName,
payload,
);
setToastData({
type: 'success',
text: 'Automation configured successfully',
});
await onProgressionSave();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
// Unified handler for updating progression
const handleUpdateProgression = async (
payload: UpdateMilestoneProgressionSchema,
) => {
if (isChangeRequestConfigured(environment)) {
onUpdateProgressionChangeRequestSubmit(milestone.id, payload);
return;
}
try {
await updateMilestoneProgression(
projectId,
environment,
featureName,
milestone.id,
payload,
);
setToastData({
type: 'success',
text: 'Automation updated successfully',
});
await onUpdate();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
const status: MilestoneStatus = const status: MilestoneStatus =
milestone.id === activeMilestoneId milestone.id === activeMilestoneId
? environmentIsDisabled ? environmentIsDisabled
@ -168,27 +231,16 @@ export const ReleasePlanMilestoneItem = ({
<MilestoneProgressionForm <MilestoneProgressionForm
sourceMilestoneId={milestone.id} sourceMilestoneId={milestone.id}
targetMilestoneId={nextMilestoneId} targetMilestoneId={nextMilestoneId}
projectId={projectId} onSubmit={handleCreateProgression}
environment={environment}
featureName={featureName}
onSave={onProgressionSave}
onCancel={handleCloseProgressionForm} onCancel={handleCloseProgressionForm}
onChangeRequestSubmit={(payload) =>
onProgressionChangeRequestSubmit(payload)
}
/> />
) : effectiveTransitionCondition ? ( ) : effectiveTransitionCondition ? (
<MilestoneTransitionDisplay <MilestoneTransitionDisplay
intervalMinutes={effectiveTransitionCondition.intervalMinutes} intervalMinutes={effectiveTransitionCondition.intervalMinutes}
onSave={handleUpdateProgression}
onDelete={() => onDeleteProgression(milestone)} onDelete={() => onDeleteProgression(milestone)}
milestoneName={milestone.name} milestoneName={milestone.name}
status={status} status={status}
projectId={projectId}
environment={environment}
featureName={featureName}
sourceMilestoneId={milestone.id}
onUpdate={onUpdate}
onChangeRequestSubmit={onUpdateProgressionChangeRequestSubmit}
hasPendingUpdate={hasPendingUpdate} hasPendingUpdate={hasPendingUpdate}
hasPendingDelete={hasPendingDelete} hasPendingDelete={hasPendingDelete}
/> />