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

refactor: simplify safeguard form management (#11013)

This commit is contained in:
Mateusz Kwasniewski 2025-11-21 16:00:45 +01:00 committed by GitHub
parent 63e969821c
commit 97a20b0929
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 26 deletions

View File

@ -66,7 +66,7 @@ export const EnvironmentAccordionBody = ({
const [strategies, setStrategies] = useState( const [strategies, setStrategies] = useState(
featureEnvironment?.strategies || [], featureEnvironment?.strategies || [],
); );
const { releasePlans } = useFeatureReleasePlans( const { releasePlans, refetch } = useFeatureReleasePlans(
projectId, projectId,
featureId, featureId,
featureEnvironment?.name, featureEnvironment?.name,
@ -229,6 +229,7 @@ export const EnvironmentAccordionBody = ({
<ReleasePlan <ReleasePlan
plan={plan} plan={plan}
environmentIsDisabled={isDisabled} environmentIsDisabled={isDisabled}
onAutomationChange={refetch}
/> />
</StrategyListItem> </StrategyListItem>
))} ))}

View File

@ -4,7 +4,6 @@ import PlayCircle from '@mui/icons-material/PlayCircle';
import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions'; import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi';
import { useFeatureReleasePlans } from 'hooks/api/getters/useFeatureReleasePlans/useFeatureReleasePlans';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import type { import type {
@ -32,7 +31,10 @@ import { ReleasePlanMilestoneItem } from './ReleasePlanMilestoneItem/ReleasePlan
import Add from '@mui/icons-material/Add'; import Add from '@mui/icons-material/Add';
import { StyledActionButton } from './ReleasePlanMilestoneItem/StyledActionButton.tsx'; 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 { useSafeguardsApi } from 'hooks/api/actions/useSafeguardsApi/useSafeguardsApi';
import type { CreateSafeguardSchema } from 'openapi/models/createSafeguardSchema'; import type { CreateSafeguardSchema } from 'openapi/models/createSafeguardSchema';
import { DeleteSafeguardDialog } from './DeleteSafeguardDialog.tsx'; import { DeleteSafeguardDialog } from './DeleteSafeguardDialog.tsx';
@ -120,12 +122,14 @@ interface IReleasePlanProps {
plan: IReleasePlan; plan: IReleasePlan;
environmentIsDisabled?: boolean; environmentIsDisabled?: boolean;
readonly?: boolean; readonly?: boolean;
onAutomationChange?: () => void;
} }
export const ReleasePlan = ({ export const ReleasePlan = ({
plan, plan,
environmentIsDisabled, environmentIsDisabled,
readonly, readonly,
onAutomationChange,
}: IReleasePlanProps) => { }: IReleasePlanProps) => {
const { const {
id, id,
@ -139,8 +143,6 @@ export const ReleasePlan = ({
} = plan; } = plan;
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const { refetch, loading: featureReleasePlansLoading } =
useFeatureReleasePlans(projectId, featureName, environment);
const { removeReleasePlanFromFeature, startReleasePlanMilestone } = const { removeReleasePlanFromFeature, startReleasePlanMilestone } =
useReleasePlansApi(); useReleasePlansApi();
const { const {
@ -221,9 +223,11 @@ export const ReleasePlan = ({
>(null); >(null);
const [milestoneToDeleteProgression, setMilestoneToDeleteProgression] = const [milestoneToDeleteProgression, setMilestoneToDeleteProgression] =
useState<IReleasePlanMilestone | null>(null); useState<IReleasePlanMilestone | null>(null);
const [safeguardFormOpen, setSafeguardFormOpen] = useState(false);
const [safeguardDeleteDialogOpen, setSafeguardDeleteDialogOpen] = const [safeguardDeleteDialogOpen, setSafeguardDeleteDialogOpen] =
useState(false); useState(false);
const { safeguardFormOpen, setSafeguardFormOpen } =
useSafeguardForm(safeguards);
const onChangeRequestConfirm = async () => { const onChangeRequestConfirm = async () => {
if (!changeRequestAction) return; if (!changeRequestAction) return;
@ -311,7 +315,7 @@ export const ReleasePlan = ({
type: 'success', type: 'success',
}); });
refetch(); onAutomationChange?.();
setRemoveOpen(false); setRemoveOpen(false);
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
@ -337,7 +341,7 @@ export const ReleasePlan = ({
text: `Milestone "${milestone.name}" has started`, text: `Milestone "${milestone.name}" has started`,
type: 'success', type: 'success',
}); });
refetch(); onAutomationChange?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} }
@ -387,7 +391,7 @@ export const ReleasePlan = ({
featureName, featureName,
sourceMilestoneId: milestoneToDeleteProgression.id, sourceMilestoneId: milestoneToDeleteProgression.id,
}); });
await refetch(); onAutomationChange?.();
setMilestoneToDeleteProgression(null); setMilestoneToDeleteProgression(null);
setToastData({ setToastData({
type: 'success', type: 'success',
@ -411,7 +415,7 @@ export const ReleasePlan = ({
type: 'success', type: 'success',
text: 'Automation resumed successfully', text: 'Automation resumed successfully',
}); });
refetch(); onAutomationChange?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} }
@ -434,11 +438,9 @@ export const ReleasePlan = ({
type: 'success', type: 'success',
text: 'Safeguard added successfully', text: 'Safeguard added successfully',
}); });
refetch(); onAutomationChange?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setSafeguardFormOpen(false);
} }
}; };
@ -461,7 +463,7 @@ export const ReleasePlan = ({
type: 'success', type: 'success',
text: 'Safeguard deleted successfully', text: 'Safeguard deleted successfully',
}); });
refetch(); onAutomationChange?.();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally { } finally {
@ -529,20 +531,15 @@ export const ReleasePlan = ({
) : null} ) : null}
<StyledBody border={safeguardBorder}> <StyledBody border={safeguardBorder}>
{safeguardsEnabled ? ( {onAutomationChange && safeguardsEnabled ? (
<StyledAddSafeguard border={safeguardBorder}> <StyledAddSafeguard border={safeguardBorder}>
{safeguards.length > 0 ? ( {safeguardFormOpen ? (
<SafeguardForm <SafeguardForm
safeguard={safeguards[0]} safeguard={safeguards?.[0]}
onSubmit={handleSafeguardSubmit} onSubmit={handleSafeguardSubmit}
onCancel={() => setSafeguardFormOpen(false)} onCancel={() => setSafeguardFormOpen(false)}
onDelete={handleSafeguardDelete} onDelete={handleSafeguardDelete}
/> />
) : safeguardFormOpen || featureReleasePlansLoading ? (
<SafeguardForm
onSubmit={handleSafeguardSubmit}
onCancel={() => setSafeguardFormOpen(false)}
/>
) : ( ) : (
<StyledActionButton <StyledActionButton
onClick={() => setSafeguardFormOpen(true)} onClick={() => setSafeguardFormOpen(true)}
@ -582,7 +579,7 @@ export const ReleasePlan = ({
projectId={projectId} projectId={projectId}
environment={environment} environment={environment}
featureName={featureName} featureName={featureName}
onUpdate={refetch} onUpdate={onAutomationChange}
/> />
))} ))}
</StyledMilestones> </StyledMilestones>

View File

@ -52,7 +52,7 @@ export interface IReleasePlanMilestoneItemProps {
projectId: string; projectId: string;
environment: string; environment: string;
featureName: string; featureName: string;
onUpdate: () => void | Promise<void>; onUpdate?: () => void;
} }
const getTimeUnit = (intervalMinutes: number): 'minutes' | 'hours' | 'days' => { const getTimeUnit = (intervalMinutes: number): 'minutes' | 'hours' | 'days' => {
@ -134,7 +134,7 @@ export const ReleasePlanMilestoneItem = ({
text: 'Automation configured successfully', text: 'Automation configured successfully',
}); });
handleCloseProgressionForm(); handleCloseProgressionForm();
await onUpdate(); onUpdate?.();
return {}; return {};
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));

View File

@ -26,6 +26,20 @@ import type { ISafeguard } from 'interfaces/releasePlans.ts';
const StyledIcon = createStyledIcon(ShieldIcon); 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 { interface ISafeguardFormProps {
onSubmit: (data: CreateSafeguardSchema) => void; onSubmit: (data: CreateSafeguardSchema) => void;
onCancel: () => void; onCancel: () => void;
@ -182,7 +196,7 @@ export const SafeguardForm = ({
threshold: Number(threshold), threshold: Number(threshold),
}); });
if (mode === 'edit') { if (mode === 'edit' || mode === 'create') {
setMode('display'); setMode('display');
} }
}; };