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

refactor: better type safety

This commit is contained in:
FredrikOseberg 2025-10-23 10:44:47 +02:00
parent e1d21adc9f
commit a3e4c1095d
No known key found for this signature in database
GPG Key ID: 282FD8A6D8F9BCF0
5 changed files with 123 additions and 45 deletions

View File

@ -15,7 +15,10 @@ import {
Deleted,
} from './Change.styles.tsx';
import type { ChangeMilestoneProgressionSchema } from 'openapi';
import { MilestoneListRenderer } from './MilestoneListRenderer.tsx';
import {
ReadonlyMilestoneListRenderer,
EditableMilestoneListRenderer,
} from './MilestoneListRenderer.tsx';
import { applyProgressionChanges } from './applyProgressionChanges.js';
import { EventDiff } from 'component/events/EventDiff/EventDiff';
@ -72,11 +75,11 @@ export const ConsolidatedProgressionChanges: FC<{
feature: IChangeRequestFeature;
currentReleasePlan?: IReleasePlan;
changeRequestState: ChangeRequestState;
onUpdateChangeRequestSubmit?: (
onUpdateChangeRequestSubmit: (
sourceMilestoneId: string,
payload: ChangeMilestoneProgressionSchema,
) => Promise<void>;
onDeleteChangeRequestSubmit?: (sourceMilestoneId: string) => Promise<void>;
onDeleteChangeRequestSubmit: (sourceMilestoneId: string) => Promise<void>;
}> = ({
feature,
currentReleasePlan,
@ -113,6 +116,9 @@ export const ConsolidatedProgressionChanges: FC<{
basePlan,
);
const readonly =
changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
return (
<StyledTabs>
<ChangeItemWrapper>
@ -137,16 +143,25 @@ export const ConsolidatedProgressionChanges: FC<{
</div>
</ChangeItemWrapper>
<TabPanel>
<MilestoneListRenderer
plan={modifiedPlan}
changeRequestState={changeRequestState}
milestonesWithAutomation={milestonesWithAutomation}
milestonesWithDeletedAutomation={
milestonesWithDeletedAutomation
}
onUpdateAutomation={onUpdateChangeRequestSubmit}
onDeleteAutomation={onDeleteChangeRequestSubmit}
/>
{readonly ? (
<ReadonlyMilestoneListRenderer
plan={modifiedPlan}
milestonesWithAutomation={milestonesWithAutomation}
milestonesWithDeletedAutomation={
milestonesWithDeletedAutomation
}
/>
) : (
<EditableMilestoneListRenderer
plan={modifiedPlan}
milestonesWithAutomation={milestonesWithAutomation}
milestonesWithDeletedAutomation={
milestonesWithDeletedAutomation
}
onUpdateAutomation={onUpdateChangeRequestSubmit}
onDeleteAutomation={onDeleteChangeRequestSubmit}
/>
)}
</TabPanel>
<TabPanel variant='diff'>
<EventDiff

View File

@ -1,7 +1,6 @@
import { styled } from '@mui/material';
import type { IReleasePlan } from 'interfaces/releasePlans';
import type { ChangeMilestoneProgressionSchema } from 'openapi';
import type { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone';
import { MilestoneAutomationSection } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx';
import { MilestoneTransitionDisplay } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx';
@ -15,29 +14,26 @@ const StyledConnection = styled('div')(({ theme }) => ({
marginLeft: theme.spacing(3.25),
}));
interface MilestoneListRendererProps {
interface MilestoneListRendererCoreProps {
plan: IReleasePlan;
changeRequestState: ChangeRequestState;
milestonesWithAutomation?: Set<string>;
milestonesWithDeletedAutomation?: Set<string>;
onUpdateAutomation?: (
readonly: boolean;
milestonesWithAutomation: Set<string>;
milestonesWithDeletedAutomation: Set<string>;
onUpdateAutomation: (
sourceMilestoneId: string,
payload: ChangeMilestoneProgressionSchema,
) => Promise<void>;
onDeleteAutomation?: (sourceMilestoneId: string) => void;
onDeleteAutomation: (sourceMilestoneId: string) => void;
}
export const MilestoneListRenderer = ({
const MilestoneListRendererCore = ({
plan,
changeRequestState,
milestonesWithAutomation = new Set(),
milestonesWithDeletedAutomation = new Set(),
readonly,
milestonesWithAutomation,
milestonesWithDeletedAutomation,
onUpdateAutomation,
onDeleteAutomation,
}: MilestoneListRendererProps) => {
// TODO: Split into read and write model at the type level to avoid having optional handlers
const readonly =
changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
}: MilestoneListRendererCoreProps) => {
const status: MilestoneStatus = 'not-started';
return (
@ -70,14 +66,14 @@ export const MilestoneListRenderer = ({
}
targetMilestoneId={nextMilestoneId}
onSave={async (payload) => {
await onUpdateAutomation?.(
await onUpdateAutomation(
milestone.id,
payload,
);
return { shouldReset: true };
}}
onDelete={() =>
onDeleteAutomation?.(milestone.id)
onDeleteAutomation(milestone.id)
}
milestoneName={milestone.name}
status={status}
@ -102,3 +98,56 @@ export const MilestoneListRenderer = ({
</>
);
};
interface ReadonlyMilestoneListRendererProps {
plan: IReleasePlan;
milestonesWithAutomation?: Set<string>;
milestonesWithDeletedAutomation?: Set<string>;
}
export const ReadonlyMilestoneListRenderer = ({
plan,
milestonesWithAutomation = new Set(),
milestonesWithDeletedAutomation = new Set(),
}: ReadonlyMilestoneListRendererProps) => {
return (
<MilestoneListRendererCore
plan={plan}
readonly={true}
milestonesWithAutomation={milestonesWithAutomation}
milestonesWithDeletedAutomation={milestonesWithDeletedAutomation}
onUpdateAutomation={async () => {}}
onDeleteAutomation={() => {}}
/>
);
};
interface EditableMilestoneListRendererProps {
plan: IReleasePlan;
milestonesWithAutomation?: Set<string>;
milestonesWithDeletedAutomation?: Set<string>;
onUpdateAutomation: (
sourceMilestoneId: string,
payload: ChangeMilestoneProgressionSchema,
) => Promise<void>;
onDeleteAutomation: (sourceMilestoneId: string) => void;
}
export const EditableMilestoneListRenderer = ({
plan,
milestonesWithAutomation = new Set(),
milestonesWithDeletedAutomation = new Set(),
onUpdateAutomation,
onDeleteAutomation,
}: EditableMilestoneListRendererProps) => {
return (
<MilestoneListRendererCore
plan={plan}
readonly={false}
milestonesWithAutomation={milestonesWithAutomation}
milestonesWithDeletedAutomation={milestonesWithDeletedAutomation}
onUpdateAutomation={onUpdateAutomation}
onDeleteAutomation={onDeleteAutomation}
/>
);
};

View File

@ -10,7 +10,10 @@ import { EventDiff } from 'component/events/EventDiff/EventDiff';
import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx';
import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles.tsx';
import { styled } from '@mui/material';
import { MilestoneListRenderer } from './MilestoneListRenderer.tsx';
import {
ReadonlyMilestoneListRenderer,
EditableMilestoneListRenderer,
} from './MilestoneListRenderer.tsx';
import { applyProgressionChanges } from './applyProgressionChanges.ts';
const StyledTabs = styled(Tabs)(({ theme }) => ({
@ -24,11 +27,11 @@ interface ProgressionChangeProps {
currentReleasePlan?: IReleasePlan;
actions?: ReactNode;
changeRequestState: ChangeRequestState;
onUpdateChangeRequestSubmit?: (
onUpdateChangeRequestSubmit: (
sourceMilestoneId: string,
payload: ChangeMilestoneProgressionSchema,
) => Promise<void>;
onDeleteChangeRequestSubmit?: (sourceMilestoneId: string) => void;
onDeleteChangeRequestSubmit: (sourceMilestoneId: string) => void;
}
export const ProgressionChange: FC<ProgressionChangeProps> = ({
@ -62,6 +65,9 @@ export const ProgressionChange: FC<ProgressionChangeProps> = ({
(milestone) => milestone.id === sourceId,
);
const readonly =
changeRequestState === 'Applied' || changeRequestState === 'Cancelled';
return (
<StyledTabs>
<ChangeItemWrapper>
@ -80,15 +86,23 @@ export const ProgressionChange: FC<ProgressionChangeProps> = ({
</div>
</ChangeItemWrapper>
<TabPanel>
<MilestoneListRenderer
plan={modifiedPlan}
changeRequestState={changeRequestState}
milestonesWithAutomation={
new Set([sourceId].filter(Boolean))
}
onUpdateAutomation={onUpdateChangeRequestSubmit}
onDeleteAutomation={onDeleteChangeRequestSubmit}
/>
{readonly ? (
<ReadonlyMilestoneListRenderer
plan={modifiedPlan}
milestonesWithAutomation={
new Set([sourceId].filter(Boolean))
}
/>
) : (
<EditableMilestoneListRenderer
plan={modifiedPlan}
milestonesWithAutomation={
new Set([sourceId].filter(Boolean))
}
onUpdateAutomation={onUpdateChangeRequestSubmit}
onDeleteAutomation={onDeleteChangeRequestSubmit}
/>
)}
</TabPanel>
<TabPanel variant='diff'>
<EventDiff

View File

@ -8,7 +8,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { calculateMilestoneStatus } from './milestoneStatusUtils.js';
import { usePendingProgressionChanges } from './usePendingProgressionChanges.js';
import { getPendingProgressionData } from './pendingProgressionChanges.js';
import { MilestoneAutomation } from './MilestoneAutomation.tsx';
const StyledConnection = styled('div', {
@ -130,7 +130,7 @@ export const ReleasePlanMilestoneItem = ({
);
const { pendingProgressionChange, effectiveTransitionCondition } =
usePendingProgressionChanges(milestone, getPendingProgressionChange);
getPendingProgressionData(milestone, getPendingProgressionChange);
const shouldShowAutomation =
isNotLastMilestone && milestoneProgressionsEnabled;

View File

@ -9,7 +9,7 @@ interface PendingProgressionChangeResult {
effectiveTransitionCondition: IReleasePlanMilestone['transitionCondition'];
}
export const usePendingProgressionChanges = (
export const getPendingProgressionData = (
milestone: IReleasePlanMilestone,
getPendingProgressionChange: IReleasePlanMilestoneItemProps['getPendingProgressionChange'],
): PendingProgressionChangeResult => {