ui(alerts): applyFireModeChange — clear mode-specific fields on toggle
Prevents stale COUNT_IN_WINDOW threshold/windowSeconds from surviving PER_EXCHANGE save (would trip the Task 3.3 server-side validator). Also forces reNotifyMinutes=0 and forDurationSeconds=0 when switching to PER_EXCHANGE. Turns green: form-state.test.ts#applyFireModeChange (3 tests).
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { FormField, Input, Select } from '@cameleer/design-system';
|
import { FormField, Input, Select } from '@cameleer/design-system';
|
||||||
import type { FormState } from '../form-state';
|
import type { FormState } from '../form-state';
|
||||||
import { EXCHANGE_FIRE_MODE_OPTIONS } from '../../enums';
|
import { applyFireModeChange } from '../form-state';
|
||||||
|
import { EXCHANGE_FIRE_MODE_OPTIONS, type ExchangeFireMode } from '../../enums';
|
||||||
|
|
||||||
// ExchangeFilter.status is typed as `String` on the backend (no @Schema
|
// ExchangeFilter.status is typed as `String` on the backend (no @Schema
|
||||||
// allowableValues yet) so options stay hand-typed. Follow-up: annotate the
|
// allowableValues yet) so options stay hand-typed. Follow-up: annotate the
|
||||||
@@ -23,7 +24,7 @@ export function ExchangeMatchForm({ form, setForm }: { form: FormState; setForm:
|
|||||||
<FormField label="Fire mode">
|
<FormField label="Fire mode">
|
||||||
<Select
|
<Select
|
||||||
value={(c.fireMode as string) ?? 'PER_EXCHANGE'}
|
value={(c.fireMode as string) ?? 'PER_EXCHANGE'}
|
||||||
onChange={(e) => patch({ fireMode: e.target.value })}
|
onChange={(e) => setForm(applyFireModeChange(form, e.target.value as ExchangeFireMode))}
|
||||||
options={EXCHANGE_FIRE_MODE_OPTIONS}
|
options={EXCHANGE_FIRE_MODE_OPTIONS}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type {
|
|||||||
AlertRuleResponse,
|
AlertRuleResponse,
|
||||||
AlertCondition,
|
AlertCondition,
|
||||||
} from '../../../api/queries/alertRules';
|
} from '../../../api/queries/alertRules';
|
||||||
import type { ConditionKind, Severity, TargetKind } from '../enums';
|
import type { ConditionKind, ExchangeFireMode, Severity, TargetKind } from '../enums';
|
||||||
|
|
||||||
export type WizardStep = 'scope' | 'condition' | 'trigger' | 'notify' | 'review';
|
export type WizardStep = 'scope' | 'condition' | 'trigger' | 'notify' | 'review';
|
||||||
export const WIZARD_STEPS: WizardStep[] = ['scope', 'condition', 'trigger', 'notify', 'review'];
|
export const WIZARD_STEPS: WizardStep[] = ['scope', 'condition', 'trigger', 'notify', 'review'];
|
||||||
@@ -137,6 +137,38 @@ export function toRequest(f: FormState): AlertRuleRequest {
|
|||||||
} as AlertRuleRequest;
|
} as AlertRuleRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure helper for the ExchangeMatchForm's Fire-mode <Select>. Guarantees state
|
||||||
|
* hygiene across toggles:
|
||||||
|
*
|
||||||
|
* - Switching to PER_EXCHANGE clears COUNT_IN_WINDOW-only condition fields
|
||||||
|
* (threshold, windowSeconds) AND forces top-level reNotifyMinutes = 0
|
||||||
|
* and forDurationSeconds = 0 — PER_EXCHANGE is exactly-once-per-exchange,
|
||||||
|
* so re-notify cadence and hold-duration are meaningless and must not leak
|
||||||
|
* into toRequest().
|
||||||
|
* - Switching back to COUNT_IN_WINDOW resets threshold/windowSeconds to 0
|
||||||
|
* (never restoring stale values from the previous COUNT_IN_WINDOW session).
|
||||||
|
*
|
||||||
|
* No-op for non-EXCHANGE_MATCH conditions. Returns a new form object.
|
||||||
|
*/
|
||||||
|
export function applyFireModeChange(form: FormState, newMode: ExchangeFireMode): FormState {
|
||||||
|
const c = form.condition as Record<string, unknown>;
|
||||||
|
if (c.kind !== 'EXCHANGE_MATCH') return form;
|
||||||
|
const base: FormState = {
|
||||||
|
...form,
|
||||||
|
condition: {
|
||||||
|
...c,
|
||||||
|
fireMode: newMode,
|
||||||
|
threshold: 0,
|
||||||
|
windowSeconds: 0,
|
||||||
|
} as FormState['condition'],
|
||||||
|
};
|
||||||
|
if (newMode === 'PER_EXCHANGE') {
|
||||||
|
return { ...base, reNotifyMinutes: 0, forDurationSeconds: 0 };
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
export function validateStep(step: WizardStep, f: FormState): string[] {
|
export function validateStep(step: WizardStep, f: FormState): string[] {
|
||||||
const errs: string[] = [];
|
const errs: string[] = [];
|
||||||
if (step === 'scope') {
|
if (step === 'scope') {
|
||||||
|
|||||||
Reference in New Issue
Block a user