From 97a20b09299d4a4e76894484cd3f11ac5f1852e7 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 21 Nov 2025 16:00:45 +0100 Subject: [PATCH] refactor: simplify safeguard form management (#11013) --- .../EnvironmentAccordionBody.tsx | 3 +- .../ReleasePlan/ReleasePlan.tsx | 41 +++++++++---------- .../ReleasePlanMilestoneItem.tsx | 4 +- .../SafeguardForm/SafeguardForm.tsx | 16 +++++++- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx index acfe27e1a7..b4e1dcf065 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx @@ -66,7 +66,7 @@ export const EnvironmentAccordionBody = ({ const [strategies, setStrategies] = useState( featureEnvironment?.strategies || [], ); - const { releasePlans } = useFeatureReleasePlans( + const { releasePlans, refetch } = useFeatureReleasePlans( projectId, featureId, featureEnvironment?.name, @@ -229,6 +229,7 @@ export const EnvironmentAccordionBody = ({ ))} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx index 2695640346..c721d84ae1 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan.tsx @@ -4,7 +4,6 @@ import PlayCircle from '@mui/icons-material/PlayCircle'; import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; -import { useFeatureReleasePlans } from 'hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useToast from 'hooks/useToast'; import type { @@ -32,7 +31,10 @@ import { ReleasePlanMilestoneItem } from './ReleasePlanMilestoneItem/ReleasePlan import Add from '@mui/icons-material/Add'; import { StyledActionButton } from './ReleasePlanMilestoneItem/StyledActionButton.tsx'; -import { SafeguardForm } from './SafeguardForm/SafeguardForm.tsx'; +import { + SafeguardForm, + useSafeguardForm, +} from './SafeguardForm/SafeguardForm.tsx'; import { useSafeguardsApi } from 'hooks/api/actions/useSafeguardsApi/useSafeguardsApi'; import type { CreateSafeguardSchema } from 'openapi/models/createSafeguardSchema'; import { DeleteSafeguardDialog } from './DeleteSafeguardDialog.tsx'; @@ -120,12 +122,14 @@ interface IReleasePlanProps { plan: IReleasePlan; environmentIsDisabled?: boolean; readonly?: boolean; + onAutomationChange?: () => void; } export const ReleasePlan = ({ plan, environmentIsDisabled, readonly, + onAutomationChange, }: IReleasePlanProps) => { const { id, @@ -139,8 +143,6 @@ export const ReleasePlan = ({ } = plan; const projectId = useRequiredPathParam('projectId'); - const { refetch, loading: featureReleasePlansLoading } = - useFeatureReleasePlans(projectId, featureName, environment); const { removeReleasePlanFromFeature, startReleasePlanMilestone } = useReleasePlansApi(); const { @@ -221,9 +223,11 @@ export const ReleasePlan = ({ >(null); const [milestoneToDeleteProgression, setMilestoneToDeleteProgression] = useState(null); - const [safeguardFormOpen, setSafeguardFormOpen] = useState(false); + const [safeguardDeleteDialogOpen, setSafeguardDeleteDialogOpen] = useState(false); + const { safeguardFormOpen, setSafeguardFormOpen } = + useSafeguardForm(safeguards); const onChangeRequestConfirm = async () => { if (!changeRequestAction) return; @@ -311,7 +315,7 @@ export const ReleasePlan = ({ type: 'success', }); - refetch(); + onAutomationChange?.(); setRemoveOpen(false); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); @@ -337,7 +341,7 @@ export const ReleasePlan = ({ text: `Milestone "${milestone.name}" has started`, type: 'success', }); - refetch(); + onAutomationChange?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } @@ -387,7 +391,7 @@ export const ReleasePlan = ({ featureName, sourceMilestoneId: milestoneToDeleteProgression.id, }); - await refetch(); + onAutomationChange?.(); setMilestoneToDeleteProgression(null); setToastData({ type: 'success', @@ -411,7 +415,7 @@ export const ReleasePlan = ({ type: 'success', text: 'Automation resumed successfully', }); - refetch(); + onAutomationChange?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } @@ -434,11 +438,9 @@ export const ReleasePlan = ({ type: 'success', text: 'Safeguard added successfully', }); - refetch(); + onAutomationChange?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); - } finally { - setSafeguardFormOpen(false); } }; @@ -461,7 +463,7 @@ export const ReleasePlan = ({ type: 'success', text: 'Safeguard deleted successfully', }); - refetch(); + onAutomationChange?.(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } finally { @@ -529,20 +531,15 @@ export const ReleasePlan = ({ ) : null} - {safeguardsEnabled ? ( + {onAutomationChange && safeguardsEnabled ? ( - {safeguards.length > 0 ? ( + {safeguardFormOpen ? ( setSafeguardFormOpen(false)} onDelete={handleSafeguardDelete} /> - ) : safeguardFormOpen || featureReleasePlansLoading ? ( - setSafeguardFormOpen(false)} - /> ) : ( setSafeguardFormOpen(true)} @@ -582,7 +579,7 @@ export const ReleasePlan = ({ projectId={projectId} environment={environment} featureName={featureName} - onUpdate={refetch} + onUpdate={onAutomationChange} /> ))} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx index d1b1f8c38a..9cbe107780 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/ReleasePlanMilestoneItem.tsx @@ -52,7 +52,7 @@ export interface IReleasePlanMilestoneItemProps { projectId: string; environment: string; featureName: string; - onUpdate: () => void | Promise; + onUpdate?: () => void; } const getTimeUnit = (intervalMinutes: number): 'minutes' | 'hours' | 'days' => { @@ -134,7 +134,7 @@ export const ReleasePlanMilestoneItem = ({ text: 'Automation configured successfully', }); handleCloseProgressionForm(); - await onUpdate(); + onUpdate?.(); return {}; } catch (error: unknown) { setToastApiError(formatUnknownError(error)); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/SafeguardForm/SafeguardForm.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/SafeguardForm/SafeguardForm.tsx index 876a932b24..db86b751f1 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/SafeguardForm/SafeguardForm.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/SafeguardForm/SafeguardForm.tsx @@ -26,6 +26,20 @@ import type { ISafeguard } from 'interfaces/releasePlans.ts'; const StyledIcon = createStyledIcon(ShieldIcon); +export const useSafeguardForm = (safeguards: ISafeguard[] | undefined) => { + const [safeguardFormOpen, setSafeguardFormOpen] = useState(false); + + useEffect(() => { + if (safeguards && safeguards.length > 0) { + setSafeguardFormOpen(true); + } else { + setSafeguardFormOpen(false); + } + }, [JSON.stringify(safeguards)]); + + return { safeguardFormOpen, setSafeguardFormOpen }; +}; + interface ISafeguardFormProps { onSubmit: (data: CreateSafeguardSchema) => void; onCancel: () => void; @@ -182,7 +196,7 @@ export const SafeguardForm = ({ threshold: Number(threshold), }); - if (mode === 'edit') { + if (mode === 'edit' || mode === 'create') { setMode('display'); } };