From 757a215d0d2a372ae4bf0dc9cc51fc87d3fef4ab Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Sat, 13 Jun 2026 17:50:23 -0300 Subject: [PATCH] Back run/node status and node type with TS consts Add NodeRunStatus and RunStatus consts mirroring the backend enums, and route the test panel and invocations list through them (plus the existing NodeType) instead of bare 'completed'/'failed'/'fetch_rss' string checks. Type the run/ node-run interfaces with the value unions, extract the status tile classes into a helper, and render the node type via i18n like the invocations list already does. --- .../components/automations/TestRunPanel.vue | 53 ++++++++++--------- .../js/pages/automations/Invocations.vue | 25 +++++---- .../js/types/automation/node-run-status.ts | 8 +++ resources/js/types/automation/run-status.ts | 10 ++++ 4 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 resources/js/types/automation/node-run-status.ts create mode 100644 resources/js/types/automation/run-status.ts diff --git a/resources/js/components/automations/TestRunPanel.vue b/resources/js/components/automations/TestRunPanel.vue index 0f797768..5dbaca7b 100644 --- a/resources/js/components/automations/TestRunPanel.vue +++ b/resources/js/components/automations/TestRunPanel.vue @@ -12,12 +12,15 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; import { useAutomationEcho } from '@/composables/echo/useAutomationEcho'; import { test as testAutomation } from '@/routes/app/automations'; +import { NodeRunStatus, type NodeRunStatusValue } from '@/types/automation/node-run-status'; +import { NodeType, type NodeTypeValue } from '@/types/automation/node-type'; +import { RunStatus, type RunStatusValue } from '@/types/automation/run-status'; interface NodeRun { id: string; node_id: string; - node_type: string; - status: string; + node_type: NodeTypeValue; + status: NodeRunStatusValue; input: Record | null; output: Record | null; error: { message?: string } | null; @@ -27,7 +30,7 @@ interface NodeRun { interface Run { id: string; - status: string; + status: RunStatusValue; context: Record | null; error: { message?: string } | null; started_at: string | null; @@ -98,29 +101,35 @@ const runTest = async () => { await start(); }; -const statusLabel = (status: string): string => { - const map: Record = { - running: trans('automations.test.in_progress'), - completed: trans('automations.test.completed'), - failed: trans('automations.test.failed'), - waiting: trans('automations.test.waiting'), +const statusLabel = (status: NodeRunStatusValue): string => { + const map: Partial> = { + [NodeRunStatus.Running]: trans('automations.test.in_progress'), + [NodeRunStatus.Completed]: trans('automations.test.completed'), + [NodeRunStatus.Failed]: trans('automations.test.failed'), }; return map[status] ?? status; }; -const nodeStatusIcon = (status: string) => { - if (status === 'completed') return IconCircleCheck; - if (status === 'failed') return IconAlertCircle; - if (status === 'running') return IconLoader2; +const nodeStatusIcon = (status: NodeRunStatusValue) => { + if (status === NodeRunStatus.Completed) return IconCircleCheck; + if (status === NodeRunStatus.Failed) return IconAlertCircle; + if (status === NodeRunStatus.Running) return IconLoader2; return IconCircleDot; }; +const nodeStatusTile = (status: NodeRunStatusValue): string => { + if (status === NodeRunStatus.Completed) return 'bg-emerald-200 text-emerald-900'; + if (status === NodeRunStatus.Failed) return 'bg-rose-200 text-rose-900'; + if (status === NodeRunStatus.Running) return 'bg-amber-200 text-amber-900'; + return 'bg-zinc-200 text-zinc-900'; +}; + // Fetch nodes (RSS / HTTP) short-circuit via the `no_items` handle with an // output of `{ fetch: { count: 0 } }` when nothing new arrived. Surface that // as an explicit note instead of an uninformative empty JSON blob. const isZeroFetchResult = (nodeRun: NodeRun): boolean => { - if (nodeRun.status !== 'completed') return false; - if (nodeRun.node_type !== 'fetch_rss' && nodeRun.node_type !== 'http_request') return false; + if (nodeRun.status !== NodeRunStatus.Completed) return false; + if (nodeRun.node_type !== NodeType.FetchRss && nodeRun.node_type !== NodeType.HttpRequest) return false; const fetch = nodeRun.output?.fetch as { count?: number } | undefined; return fetch?.count === 0; }; @@ -170,7 +179,7 @@ const isZeroFetchResult = (nodeRun: NodeRun): boolean => {
@@ -190,18 +199,12 @@ const isZeroFetchResult = (nodeRun: NodeRun): boolean => { >
- +
-

{{ nodeRun.node_type.replace('_', ' ') }}

+

{{ $t(`automations.node_type.${nodeRun.node_type}`) }}

{{ statusLabel(nodeRun.status) }}

diff --git a/resources/js/pages/automations/Invocations.vue b/resources/js/pages/automations/Invocations.vue index 6d490373..151fd6b8 100644 --- a/resources/js/pages/automations/Invocations.vue +++ b/resources/js/pages/automations/Invocations.vue @@ -35,10 +35,13 @@ import { } from '@/components/ui/tooltip'; import date from '@/date'; import type { Automation } from '@/types/automation/automation'; +import type { NodeRunStatusValue } from '@/types/automation/node-run-status'; +import type { NodeTypeValue } from '@/types/automation/node-type'; +import { RunStatus, type RunStatusValue } from '@/types/automation/run-status'; type Invocation = { id: string; - status: string; + status: RunStatusValue; is_manual: boolean; node_run_count: number; duration_ms: number | null; @@ -51,8 +54,8 @@ type Invocation = { type NodeRun = { id: string; node_id: string; - node_type: string; - status: string; + node_type: NodeTypeValue; + status: NodeRunStatusValue; error: { message?: string } | null; started_at: string | null; finished_at: string | null; @@ -74,25 +77,25 @@ const statusLabel = computed(() => ); const statusVariant = ( - status: string, + status: RunStatusValue | NodeRunStatusValue, ): 'default' | 'secondary' | 'destructive' | 'outline' => { - if (status === 'completed') return 'default'; - if (status === 'failed' || status === 'cancelled') return 'destructive'; - if (status === 'running' || status === 'waiting') return 'secondary'; + if (status === RunStatus.Completed) return 'default'; + if (status === RunStatus.Failed || status === RunStatus.Cancelled) return 'destructive'; + if (status === RunStatus.Running || status === RunStatus.Waiting) return 'secondary'; return 'outline'; }; const summary = (invocation: Invocation): string => { - if (invocation.status === 'failed') + if (invocation.status === RunStatus.Failed) return ( invocation.error_message ?? trans('automations.invocations.summary.failed') ); - if (invocation.status === 'completed') + if (invocation.status === RunStatus.Completed) return trans('automations.invocations.summary.completed'); - if (invocation.status === 'running' || invocation.status === 'waiting') + if (invocation.status === RunStatus.Running || invocation.status === RunStatus.Waiting) return trans('automations.invocations.summary.running'); - if (invocation.status === 'cancelled') + if (invocation.status === RunStatus.Cancelled) return trans('automations.invocations.summary.cancelled'); return trans('automations.invocations.summary.pending'); }; diff --git a/resources/js/types/automation/node-run-status.ts b/resources/js/types/automation/node-run-status.ts new file mode 100644 index 00000000..7384c716 --- /dev/null +++ b/resources/js/types/automation/node-run-status.ts @@ -0,0 +1,8 @@ +export const NodeRunStatus = { + Running: 'running', + Completed: 'completed', + Failed: 'failed', + Skipped: 'skipped', +} as const; + +export type NodeRunStatusValue = (typeof NodeRunStatus)[keyof typeof NodeRunStatus]; diff --git a/resources/js/types/automation/run-status.ts b/resources/js/types/automation/run-status.ts new file mode 100644 index 00000000..3099577e --- /dev/null +++ b/resources/js/types/automation/run-status.ts @@ -0,0 +1,10 @@ +export const RunStatus = { + Pending: 'pending', + Running: 'running', + Waiting: 'waiting', + Completed: 'completed', + Failed: 'failed', + Cancelled: 'cancelled', +} as const; + +export type RunStatusValue = (typeof RunStatus)[keyof typeof RunStatus];