mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
Replace the free-form trigger-config form with a structured schedule editor built on an orthogonal cron model: separate frequency, time, and day-of-week/day-of-month dimensions map to and from cron expressions via a dedicated grammar and mapping layer, with validation and a human-readable describe() summary. The grammar suite drives the editor against a combinatorially generated corpus of 51,755 distinct cron expressions - every token form of every field, crossed - each judged against a reference robfig/cron v3 parser. Add a server-side /autopilot/cron-preview endpoint (plus schema and React Query hook) so the editor shows upcoming run times, and echo wildcard-carrying cron lists correctly instead of collapsing them. Supporting pieces: timezone-aware formatting helper, segmented-toggle and debounced-value utilities, a reworked time-input, and refreshed en/ja/ko/zh-Hans locale strings.
111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { queryOptions } from "@tanstack/react-query";
|
|
import { api } from "../api";
|
|
|
|
export const autopilotKeys = {
|
|
all: (wsId: string) => ["autopilots", wsId] as const,
|
|
list: (wsId: string) => [...autopilotKeys.all(wsId), "list"] as const,
|
|
detail: (wsId: string, id: string) =>
|
|
[...autopilotKeys.all(wsId), "detail", id] as const,
|
|
runs: (wsId: string, id: string) =>
|
|
[...autopilotKeys.all(wsId), "runs", id] as const,
|
|
run: (wsId: string, autopilotId: string, runId: string) =>
|
|
[...autopilotKeys.all(wsId), "runs", autopilotId, runId] as const,
|
|
deliveries: (wsId: string, id: string) =>
|
|
[...autopilotKeys.all(wsId), "deliveries", id] as const,
|
|
delivery: (wsId: string, autopilotId: string, deliveryId: string) =>
|
|
[...autopilotKeys.all(wsId), "deliveries", autopilotId, deliveryId] as const,
|
|
cronPreview: (wsId: string, expr: string, tz: string) =>
|
|
[...autopilotKeys.all(wsId), "cron-preview", expr, tz] as const,
|
|
};
|
|
|
|
export function autopilotListOptions(wsId: string) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.list(wsId),
|
|
queryFn: () => api.listAutopilots(),
|
|
select: (data) => data.autopilots,
|
|
});
|
|
}
|
|
|
|
export function autopilotDetailOptions(wsId: string, id: string) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.detail(wsId, id),
|
|
queryFn: () => api.getAutopilot(id),
|
|
});
|
|
}
|
|
|
|
export function autopilotRunsOptions(wsId: string, id: string) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.runs(wsId, id),
|
|
queryFn: () => api.listAutopilotRuns(id),
|
|
select: (data) => data.runs,
|
|
});
|
|
}
|
|
|
|
// autopilotRunOptions fetches a single run with its full trigger_payload.
|
|
// The list endpoint (autopilotRunsOptions) omits trigger_payload to keep
|
|
// list responses small; callers (e.g. the run-detail dialog) use this
|
|
// query on demand when the user opens a run.
|
|
export function autopilotRunOptions(
|
|
wsId: string,
|
|
autopilotId: string,
|
|
runId: string,
|
|
options?: { enabled?: boolean },
|
|
) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.run(wsId, autopilotId, runId),
|
|
queryFn: () => api.getAutopilotRun(autopilotId, runId),
|
|
enabled: options?.enabled ?? true,
|
|
});
|
|
}
|
|
|
|
// autopilotDeliveriesOptions powers the Deliveries section in the autopilot
|
|
// detail page. The list is slim — raw_body / selected_headers / response_body
|
|
// are omitted server-side. Detail rows are fetched on-demand when the user
|
|
// expands a row (see autopilotDeliveryOptions).
|
|
export function autopilotDeliveriesOptions(
|
|
wsId: string,
|
|
autopilotId: string,
|
|
options?: { enabled?: boolean },
|
|
) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.deliveries(wsId, autopilotId),
|
|
queryFn: () => api.listAutopilotDeliveries(autopilotId),
|
|
select: (data) => data.deliveries,
|
|
enabled: options?.enabled ?? true,
|
|
});
|
|
}
|
|
|
|
// autopilotDeliveryOptions fetches the full delivery row including raw_body
|
|
// and headers subset. Used by the detail dialog opened from a list row.
|
|
export function autopilotDeliveryOptions(
|
|
wsId: string,
|
|
autopilotId: string,
|
|
deliveryId: string,
|
|
options?: { enabled?: boolean },
|
|
) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.delivery(wsId, autopilotId, deliveryId),
|
|
queryFn: () => api.getAutopilotDelivery(autopilotId, deliveryId),
|
|
enabled: options?.enabled ?? true,
|
|
});
|
|
}
|
|
|
|
// cronPreviewOptions backs the schedule editor's next-run preview. The server
|
|
// owns cron/timezone evaluation, so the editor never approximates it locally.
|
|
export function cronPreviewOptions(
|
|
wsId: string,
|
|
expr: string,
|
|
tz: string,
|
|
options?: { enabled?: boolean },
|
|
) {
|
|
return queryOptions({
|
|
queryKey: autopilotKeys.cronPreview(wsId, expr, tz),
|
|
queryFn: () => api.cronPreview({ expr, tz }),
|
|
enabled: options?.enabled ?? true,
|
|
staleTime: 30_000,
|
|
// A 400 (invalid expression/timezone) is a stable answer for this input,
|
|
// not a transient failure — retrying would only delay the inline error.
|
|
retry: false,
|
|
});
|
|
}
|