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 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
|
||||
// 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">
|
||||
<Select
|
||||
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}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
AlertRuleResponse,
|
||||
AlertCondition,
|
||||
} 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 const WIZARD_STEPS: WizardStep[] = ['scope', 'condition', 'trigger', 'notify', 'review'];
|
||||
@@ -137,6 +137,38 @@ export function toRequest(f: FormState): 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[] {
|
||||
const errs: string[] = [];
|
||||
if (step === 'scope') {
|
||||
|
||||
Reference in New Issue
Block a user