mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
feat(web): automations contracts, API client, atoms and hooks
Foundation for the v1 automations UI. Mirrors backend Pydantic schemas into Zod and wires the data layer end-to-end so feature surfaces can be built on top. contracts/types/automation.types.ts: - AutomationStatus, TriggerType, RunStatus enums. - AutomationDefinition envelope (PlanStep, TriggerSpec, Execution, Metadata, Inputs). - AutomationCreate/Update/Detail/Summary/List + listParams. - TriggerCreate/Update/Detail. - RunSummary/Detail/List + runListParams. lib/apis/automations-api.service.ts: - list/get/create/update/delete automations. - add/update/remove triggers (sub-resource). - list/get runs (read-only sub-resource). - safeParse on every write, 204-safe deletes. atoms/automations/: - automationsListAtom (active search space, first page). - 6 mutation atoms with toast + cache invalidation. hooks/: - use-automations.ts wraps the list atom. - use-automation.ts: parameterized detail by id. - use-automation-runs.ts: useAutomationRuns + useAutomationRun. lib/query-client/cache-keys.ts: automations namespace (list, detail, runs, run) keyed by (id, limit, offset) where relevant. Smoke: zod round-trip OK on backend-shape payloads (Automation, AutomationCreate, Trigger, Run); typecheck clean for new files; biome clean.
This commit is contained in:
parent
d48bb2033b
commit
b18a5fdca9
8 changed files with 548 additions and 0 deletions
193
surfsense_web/contracts/types/automation.types.ts
Normal file
193
surfsense_web/contracts/types/automation.types.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import { z } from "zod";
|
||||
|
||||
// =============================================================================
|
||||
// Enums — mirror app/automations/persistence/enums/*
|
||||
// =============================================================================
|
||||
|
||||
export const automationStatus = z.enum(["active", "paused", "archived"]);
|
||||
export type AutomationStatus = z.infer<typeof automationStatus>;
|
||||
|
||||
export const triggerType = z.enum(["schedule", "manual"]);
|
||||
export type TriggerType = z.infer<typeof triggerType>;
|
||||
|
||||
export const runStatus = z.enum([
|
||||
"pending",
|
||||
"running",
|
||||
"succeeded",
|
||||
"failed",
|
||||
"cancelled",
|
||||
"timed_out",
|
||||
]);
|
||||
export type RunStatus = z.infer<typeof runStatus>;
|
||||
|
||||
// =============================================================================
|
||||
// Definition envelope — mirror app/automations/schemas/definition/*
|
||||
// =============================================================================
|
||||
|
||||
export const planStep = z.object({
|
||||
step_id: z.string().min(1),
|
||||
action: z.string().min(1),
|
||||
when: z.string().nullable().optional(),
|
||||
params: z.record(z.string(), z.any()).default({}),
|
||||
output_as: z.string().nullable().optional(),
|
||||
max_retries: z.number().int().min(0).nullable().optional(),
|
||||
timeout_seconds: z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
export type PlanStep = z.infer<typeof planStep>;
|
||||
|
||||
export const definitionTriggerSpec = z.object({
|
||||
type: z.string().min(1),
|
||||
params: z.record(z.string(), z.any()).default({}),
|
||||
});
|
||||
export type DefinitionTriggerSpec = z.infer<typeof definitionTriggerSpec>;
|
||||
|
||||
export const execution = z.object({
|
||||
timeout_seconds: z.number().int().positive().default(600),
|
||||
max_retries: z.number().int().min(0).default(2),
|
||||
retry_backoff: z.enum(["exponential", "linear", "none"]).default("exponential"),
|
||||
concurrency: z.enum(["drop_if_running", "queue", "always"]).default("drop_if_running"),
|
||||
on_failure: z.array(planStep).default([]),
|
||||
});
|
||||
export type Execution = z.infer<typeof execution>;
|
||||
|
||||
// Backend ``Metadata`` is ``extra="allow"`` — keep ``tags`` typed, accept arbitrary keys.
|
||||
export const metadata = z.object({ tags: z.array(z.string()).default([]) }).catchall(z.any());
|
||||
export type Metadata = z.infer<typeof metadata>;
|
||||
|
||||
// Backend ``Inputs`` serializes its ``schema_`` field as ``schema`` (alias).
|
||||
export const inputs = z.object({
|
||||
schema: z.record(z.string(), z.any()),
|
||||
});
|
||||
export type Inputs = z.infer<typeof inputs>;
|
||||
|
||||
export const automationDefinition = z.object({
|
||||
schema_version: z.string().default("1.0"),
|
||||
name: z.string().min(1).max(200),
|
||||
goal: z.string().nullable().optional(),
|
||||
inputs: inputs.nullable().optional(),
|
||||
triggers: z.array(definitionTriggerSpec).default([]),
|
||||
plan: z.array(planStep).min(1),
|
||||
execution: execution.default(execution.parse({})),
|
||||
metadata: metadata.default(metadata.parse({})),
|
||||
});
|
||||
export type AutomationDefinition = z.infer<typeof automationDefinition>;
|
||||
|
||||
// =============================================================================
|
||||
// Triggers (sub-resource) — mirror app/automations/schemas/api/trigger.py
|
||||
// =============================================================================
|
||||
|
||||
export const triggerCreateRequest = z.object({
|
||||
type: triggerType,
|
||||
params: z.record(z.string(), z.any()).default({}),
|
||||
static_inputs: z.record(z.string(), z.any()).default({}),
|
||||
enabled: z.boolean().default(true),
|
||||
});
|
||||
export type TriggerCreateRequest = z.infer<typeof triggerCreateRequest>;
|
||||
|
||||
export const triggerUpdateRequest = z.object({
|
||||
enabled: z.boolean().nullable().optional(),
|
||||
params: z.record(z.string(), z.any()).nullable().optional(),
|
||||
static_inputs: z.record(z.string(), z.any()).nullable().optional(),
|
||||
});
|
||||
export type TriggerUpdateRequest = z.infer<typeof triggerUpdateRequest>;
|
||||
|
||||
export const trigger = z.object({
|
||||
id: z.number(),
|
||||
type: triggerType,
|
||||
params: z.record(z.string(), z.any()),
|
||||
static_inputs: z.record(z.string(), z.any()),
|
||||
enabled: z.boolean(),
|
||||
last_fired_at: z.string().nullable().optional(),
|
||||
next_fire_at: z.string().nullable().optional(),
|
||||
created_at: z.string(),
|
||||
});
|
||||
export type Trigger = z.infer<typeof trigger>;
|
||||
|
||||
// =============================================================================
|
||||
// Automations — mirror app/automations/schemas/api/automation.py
|
||||
// =============================================================================
|
||||
|
||||
export const automationCreateRequest = z.object({
|
||||
search_space_id: z.number(),
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().nullable().optional(),
|
||||
definition: automationDefinition,
|
||||
triggers: z.array(triggerCreateRequest).default([]),
|
||||
});
|
||||
export type AutomationCreateRequest = z.infer<typeof automationCreateRequest>;
|
||||
|
||||
export const automationUpdateRequest = z.object({
|
||||
name: z.string().min(1).max(200).nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
status: automationStatus.nullable().optional(),
|
||||
definition: automationDefinition.nullable().optional(),
|
||||
});
|
||||
export type AutomationUpdateRequest = z.infer<typeof automationUpdateRequest>;
|
||||
|
||||
export const automationSummary = z.object({
|
||||
id: z.number(),
|
||||
search_space_id: z.number(),
|
||||
name: z.string(),
|
||||
description: z.string().nullable().optional(),
|
||||
status: automationStatus,
|
||||
version: z.number(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
});
|
||||
export type AutomationSummary = z.infer<typeof automationSummary>;
|
||||
|
||||
export const automation = automationSummary.extend({
|
||||
definition: automationDefinition,
|
||||
triggers: z.array(trigger).default([]),
|
||||
});
|
||||
export type Automation = z.infer<typeof automation>;
|
||||
|
||||
export const automationListResponse = z.object({
|
||||
items: z.array(automationSummary),
|
||||
total: z.number(),
|
||||
});
|
||||
export type AutomationListResponse = z.infer<typeof automationListResponse>;
|
||||
|
||||
export const automationListParams = z.object({
|
||||
search_space_id: z.number(),
|
||||
limit: z.number().int().min(1).max(200).default(50),
|
||||
offset: z.number().int().min(0).default(0),
|
||||
});
|
||||
export type AutomationListParams = z.infer<typeof automationListParams>;
|
||||
|
||||
// =============================================================================
|
||||
// Runs (sub-resource) — mirror app/automations/schemas/api/run.py
|
||||
// =============================================================================
|
||||
|
||||
export const runSummary = z.object({
|
||||
id: z.number(),
|
||||
automation_id: z.number(),
|
||||
trigger_id: z.number().nullable().optional(),
|
||||
status: runStatus,
|
||||
started_at: z.string().nullable().optional(),
|
||||
finished_at: z.string().nullable().optional(),
|
||||
created_at: z.string(),
|
||||
});
|
||||
export type RunSummary = z.infer<typeof runSummary>;
|
||||
|
||||
export const run = runSummary.extend({
|
||||
definition_snapshot: z.record(z.string(), z.any()),
|
||||
inputs: z.record(z.string(), z.any()),
|
||||
step_results: z.array(z.record(z.string(), z.any())),
|
||||
output: z.record(z.string(), z.any()).nullable().optional(),
|
||||
artifacts: z.array(z.record(z.string(), z.any())),
|
||||
error: z.record(z.string(), z.any()).nullable().optional(),
|
||||
});
|
||||
export type Run = z.infer<typeof run>;
|
||||
|
||||
export const runListResponse = z.object({
|
||||
items: z.array(runSummary),
|
||||
total: z.number(),
|
||||
});
|
||||
export type RunListResponse = z.infer<typeof runListResponse>;
|
||||
|
||||
export const runListParams = z.object({
|
||||
limit: z.number().int().min(1).max(200).default(50),
|
||||
offset: z.number().int().min(0).default(0),
|
||||
});
|
||||
export type RunListParams = z.infer<typeof runListParams>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue