@voyant-travel/workflows-react 0.121.0 → 0.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/common.d.ts +1 -1
- package/dist/components/common.js +2 -2
- package/dist/components/workflow-run-detail-page.js +5 -5
- package/dist/components/workflow-runs-page.js +3 -3
- package/dist/components/workflow-schedules-page.js +12 -12
- package/dist/i18n/provider.d.ts +2 -1
- package/dist/i18n/provider.js +2 -2
- package/package.json +5 -5
- package/src/components/common.tsx +6 -2
- package/src/components/workflow-run-detail-page.tsx +9 -6
- package/src/components/workflow-runs-page.tsx +6 -3
- package/src/components/workflow-schedules-page.tsx +20 -9
- package/src/i18n/provider.tsx +3 -0
|
@@ -24,6 +24,6 @@ export declare function PayloadBlock({ title, value, messages, hideTitle, }: {
|
|
|
24
24
|
messages: WorkflowRunsUiMessages;
|
|
25
25
|
hideTitle?: boolean;
|
|
26
26
|
}): import("react").JSX.Element;
|
|
27
|
-
export declare function formatRelative(iso: string, messages: WorkflowRunsUiMessages): string;
|
|
27
|
+
export declare function formatRelative(iso: string, messages: WorkflowRunsUiMessages, locale: string): string;
|
|
28
28
|
export declare function formatDuration(ms: number): string;
|
|
29
29
|
export declare function runHasLiveStatus(run: WorkflowRun | WorkflowRunStep): boolean;
|
|
@@ -78,11 +78,11 @@ export function PayloadBlock({ title, value, messages, hideTitle = false, }) {
|
|
|
78
78
|
};
|
|
79
79
|
return (_jsxs("div", { className: "space-y-1", children: [hideTitle ? null : (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: title }), _jsxs("button", { type: "button", onClick: onCopy, className: "flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground", children: [_jsx(Copy, { className: "h-3 w-3" }), copied ? messages.detail.copied : messages.detail.copy] })] })), _jsx("pre", { className: "max-h-[24rem] overflow-auto rounded bg-muted/40 p-3 font-mono text-[11px] leading-relaxed", children: json })] }));
|
|
80
80
|
}
|
|
81
|
-
export function formatRelative(iso, messages) {
|
|
81
|
+
export function formatRelative(iso, messages, locale) {
|
|
82
82
|
const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000);
|
|
83
83
|
if (Math.abs(seconds) < 5)
|
|
84
84
|
return messages.format.relativeNow;
|
|
85
|
-
const formatter = new Intl.RelativeTimeFormat(
|
|
85
|
+
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
86
86
|
if (Math.abs(seconds) < 60)
|
|
87
87
|
return formatter.format(-seconds, "second");
|
|
88
88
|
const minutes = Math.round(seconds / 60);
|
|
@@ -4,7 +4,7 @@ import { Badge } from "@voyant-travel/ui/components/badge";
|
|
|
4
4
|
import { Card, CardContent, CardHeader, CardTitle } from "@voyant-travel/ui/components/card";
|
|
5
5
|
import { ChevronDown, ChevronRight, Link2 } from "lucide-react";
|
|
6
6
|
import { useEffect, useMemo, useState } from "react";
|
|
7
|
-
import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js";
|
|
7
|
+
import { useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiMessagesOrDefault, } from "../i18n/index.js";
|
|
8
8
|
import { CopyableId, formatDuration, formatRelative, PayloadBlock, StatusBadge, StatusIcon, StepStatusIcon, TagChip, } from "./common.js";
|
|
9
9
|
import { WorkflowRunActionsCard } from "./workflow-run-actions.js";
|
|
10
10
|
export function WorkflowRunDetailPage({ api, runId, onOpenRun, pollIntervalMs = 3000, className, }) {
|
|
@@ -53,8 +53,8 @@ export function WorkflowRunDetailPage({ api, runId, onOpenRun, pollIntervalMs =
|
|
|
53
53
|
return (_jsxs("div", { className: `space-y-4 ${className ?? ""}`, children: [_jsx(RunHeaderCard, { run: run, onOpenRun: onOpenRun }), _jsx(WorkflowRunActionsCard, { api: api, run: run, onOpenRun: onOpenRun }), reruns.length > 0 ? _jsx(RerunsListCard, { reruns: reruns, onOpenRun: onOpenRun }) : null, _jsx(StepsCard, { steps: steps }), run.input ? _jsx(PayloadCard, { title: messages.detail.input, value: run.input }) : null, run.result ? _jsx(PayloadCard, { title: messages.detail.result, value: run.result }) : null, run.error && !duplicateRunError ? (_jsx(ErrorCard, { title: messages.detail.runError, error: run.error })) : null] }));
|
|
54
54
|
}
|
|
55
55
|
function RunHeaderCard({ run, onOpenRun }) {
|
|
56
|
-
const messages =
|
|
57
|
-
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "space-y-3 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(StatusIcon, { status: run.status }), _jsx(CardTitle, { className: "font-semibold text-lg", children: run.workflowName }), _jsx(StatusBadge, { status: run.status, messages: messages }), run.durationMs != null ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: formatDuration(run.durationMs) })) : null, run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx(CopyableId, { id: run.id, copiedLabel: messages.detail.copied, className: "ml-auto" })] }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [_jsxs("span", { children: [messages.detail.started, " ",
|
|
56
|
+
const { formatDateTime, messages } = useWorkflowRunsUiI18nOrDefault();
|
|
57
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "space-y-3 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(StatusIcon, { status: run.status }), _jsx(CardTitle, { className: "font-semibold text-lg", children: run.workflowName }), _jsx(StatusBadge, { status: run.status, messages: messages }), run.durationMs != null ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: formatDuration(run.durationMs) })) : null, run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx(CopyableId, { id: run.id, copiedLabel: messages.detail.copied, className: "ml-auto" })] }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [_jsxs("span", { children: [messages.detail.started, " ", formatDateTime(run.startedAt)] }), run.completedAt ? (_jsxs("span", { children: [" · ", messages.detail.finished, " ", formatDateTime(run.completedAt)] })) : null] })] }), _jsxs(CardContent, { className: "space-y-3 pt-0", children: [_jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-2 text-sm sm:grid-cols-2", children: [_jsx(DefRow, { label: messages.detail.trigger, value: run.trigger, mono: true }), run.correlationId ? (_jsx(DefRow, { label: messages.detail.correlation, value: run.correlationId, mono: true })) : null, run.parentRunId ? (_jsx(LinkedRunRow, { label: messages.detail.parent, runId: run.parentRunId, onOpenRun: onOpenRun })) : null, run.triggeredByUserId ? (_jsx(DefRow, { label: messages.detail.triggeredBy, value: run.triggeredByUserId, mono: true })) : null] }), run.tags.length > 0 ? (_jsxs("div", { children: [_jsx("div", { className: "mb-1.5 text-muted-foreground text-xs uppercase tracking-wide", children: messages.detail.tags }), _jsx("div", { className: "flex flex-wrap gap-1", children: run.tags.map((tag) => (_jsx(TagChip, { tag: tag }, tag))) })] })) : null] })] }));
|
|
58
58
|
}
|
|
59
59
|
function LinkedRunRow({ label, runId, onOpenRun, }) {
|
|
60
60
|
return (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground text-xs uppercase tracking-wide", children: label }), _jsx("dd", { className: "min-w-0 truncate", children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(runId), className: "inline-flex items-center gap-1.5 truncate font-mono text-xs hover:underline", title: runId, children: [_jsx(Link2, { className: "h-3 w-3 opacity-60" }), runId] }) })] }));
|
|
@@ -64,8 +64,8 @@ function DefRow({ label, value, mono }) {
|
|
|
64
64
|
, children: value })] }));
|
|
65
65
|
}
|
|
66
66
|
function RerunsListCard({ reruns, onOpenRun, }) {
|
|
67
|
-
const messages =
|
|
68
|
-
return (_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-sm", children: [_jsx(Link2, { className: "h-3.5 w-3.5 text-muted-foreground" }), messages.detail.reruns, _jsx("span", { className: "font-normal text-muted-foreground text-xs", children: `(${reruns.length})` })] }) }), _jsx(CardContent, { children: _jsx("ul", { className: "space-y-1", children: reruns.map((run) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(run.id), className: "flex w-full items-center gap-2 rounded p-2 text-left text-sm hover:bg-muted/40", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "font-mono text-xs", children: run.id }), _jsx("span", { className: "text-muted-foreground text-xs", children: run.trigger }), run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages) })] }) }, run.id))) }) })] }));
|
|
67
|
+
const { locale, messages } = useWorkflowRunsUiI18nOrDefault();
|
|
68
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-sm", children: [_jsx(Link2, { className: "h-3.5 w-3.5 text-muted-foreground" }), messages.detail.reruns, _jsx("span", { className: "font-normal text-muted-foreground text-xs", children: `(${reruns.length})` })] }) }), _jsx(CardContent, { children: _jsx("ul", { className: "space-y-1", children: reruns.map((run) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(run.id), className: "flex w-full items-center gap-2 rounded p-2 text-left text-sm hover:bg-muted/40", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "font-mono text-xs", children: run.id }), _jsx("span", { className: "text-muted-foreground text-xs", children: run.trigger }), run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages, locale) })] }) }, run.id))) }) })] }));
|
|
69
69
|
}
|
|
70
70
|
function StepsCard({ steps }) {
|
|
71
71
|
const messages = useWorkflowRunsUiMessagesOrDefault();
|
|
@@ -4,7 +4,7 @@ import { Button } from "@voyant-travel/ui/components/button";
|
|
|
4
4
|
import { Card, CardContent } from "@voyant-travel/ui/components/card";
|
|
5
5
|
import { Clock, Workflow } from "lucide-react";
|
|
6
6
|
import { useEffect, useMemo, useState } from "react";
|
|
7
|
-
import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js";
|
|
7
|
+
import { useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiMessagesOrDefault, } from "../i18n/index.js";
|
|
8
8
|
import { formatDuration, formatRelative, StatusBadge, StatusIcon, TagChip } from "./common.js";
|
|
9
9
|
import { WorkflowRunDetailPage } from "./workflow-run-detail-page.js";
|
|
10
10
|
import { buildFilterOptions, WorkflowRunsFilters } from "./workflow-runs-filters.js";
|
|
@@ -84,9 +84,9 @@ export function WorkflowRunsPageSkeleton() {
|
|
|
84
84
|
return (_jsxs("div", { className: "flex min-h-screen flex-col bg-background", children: [_jsx("header", { className: "border-b px-4 py-3", children: _jsx("div", { className: "h-5 w-40 rounded bg-muted", role: "status", "aria-label": messages.page.loading }) }), _jsxs("main", { className: "container mx-auto flex flex-1 flex-col gap-6 px-4 py-6 md:flex-row", children: [_jsxs("aside", { className: "space-y-3 md:w-96 md:shrink-0", children: [_jsx("div", { className: "h-56 rounded-md bg-muted" }), _jsx("div", { className: "h-20 rounded-md bg-muted" }), _jsx("div", { className: "h-20 rounded-md bg-muted" })] }), _jsx("section", { className: "min-h-[24rem] flex-1 rounded-md bg-muted" })] })] }));
|
|
85
85
|
}
|
|
86
86
|
function RunListItem({ run, selected, activeTagFilters, activeStatusFilters, onSelect, onToggleStatus, onToggleTag, }) {
|
|
87
|
-
const messages =
|
|
87
|
+
const { locale, messages } = useWorkflowRunsUiI18nOrDefault();
|
|
88
88
|
return (_jsxs("div", { className: `rounded-md border bg-card p-3 text-sm transition-colors hover:bg-muted/50 ${selected ? "border-primary bg-primary/5" : "" // i18n-literal-ok: CSS classes
|
|
89
|
-
}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: onSelect, className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "truncate font-medium", children: run.workflowName }), _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages) })] }), _jsx("button", { type: "button", onClick: () => onToggleStatus(run.status), "aria-pressed": activeStatusFilters.includes(run.status), children: _jsx(StatusBadge, { status: run.status, messages: messages }) })] }), run.durationMs != null ? (_jsx("div", { className: "mt-1 text-muted-foreground text-xs", children: formatDuration(run.durationMs) })) : null, run.tags.length > 0 ? (_jsxs("div", { className: "mt-2 flex flex-wrap gap-1", children: [run.tags.slice(0, 3).map((tag) => (_jsx("button", { type: "button", onClick: () => onToggleTag(tag), "aria-pressed": activeTagFilters.includes(tag), className: activeTagFilters.includes(tag) ? "rounded-full ring-2 ring-primary/40" : undefined, children: _jsx(TagChip, { tag: tag }) }, tag))), run.tags.length > 3 ? (_jsx("span", { className: "text-muted-foreground text-xs", children: `+${run.tags.length - 3}` })) : null] })) : null] }));
|
|
89
|
+
}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: onSelect, className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "truncate font-medium", children: run.workflowName }), _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages, locale) })] }), _jsx("button", { type: "button", onClick: () => onToggleStatus(run.status), "aria-pressed": activeStatusFilters.includes(run.status), children: _jsx(StatusBadge, { status: run.status, messages: messages }) })] }), run.durationMs != null ? (_jsx("div", { className: "mt-1 text-muted-foreground text-xs", children: formatDuration(run.durationMs) })) : null, run.tags.length > 0 ? (_jsxs("div", { className: "mt-2 flex flex-wrap gap-1", children: [run.tags.slice(0, 3).map((tag) => (_jsx("button", { type: "button", onClick: () => onToggleTag(tag), "aria-pressed": activeTagFilters.includes(tag), className: activeTagFilters.includes(tag) ? "rounded-full ring-2 ring-primary/40" : undefined, children: _jsx(TagChip, { tag: tag }) }, tag))), run.tags.length > 3 ? (_jsx("span", { className: "text-muted-foreground text-xs", children: `+${run.tags.length - 3}` })) : null] })) : null] }));
|
|
90
90
|
}
|
|
91
91
|
function SelectPrompt() {
|
|
92
92
|
const messages = useWorkflowRunsUiMessagesOrDefault();
|
|
@@ -5,10 +5,10 @@ import { Button } from "@voyant-travel/ui/components/button";
|
|
|
5
5
|
import { Card, CardContent } from "@voyant-travel/ui/components/card";
|
|
6
6
|
import { AlertTriangle, CalendarClock, RefreshCw } from "lucide-react";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
8
|
-
import {
|
|
8
|
+
import { useWorkflowRunsUiI18nOrDefault } from "../i18n/index.js";
|
|
9
9
|
import { formatRelative, StatusIcon } from "./common.js";
|
|
10
10
|
export function WorkflowSchedulesPage({ schedulesApi, runsApi, onTriggerNow, environment = "production", pollIntervalMs = 30_000, className, }) {
|
|
11
|
-
const rootMessages =
|
|
11
|
+
const { locale, messages: rootMessages } = useWorkflowRunsUiI18nOrDefault();
|
|
12
12
|
const messages = rootMessages.schedules;
|
|
13
13
|
const [response, setResponse] = useState(null);
|
|
14
14
|
const [lastRuns, setLastRuns] = useState({});
|
|
@@ -76,11 +76,11 @@ export function WorkflowSchedulesPage({ schedulesApi, runsApi, onTriggerNow, env
|
|
|
76
76
|
? "border-emerald-500/40 bg-emerald-500/5"
|
|
77
77
|
: "border-destructive/40 bg-destructive/5", children: _jsx(CardContent, { className: `pt-4 text-sm ${triggerNotice.kind === "success"
|
|
78
78
|
? "text-emerald-600 dark:text-emerald-300"
|
|
79
|
-
: "text-destructive"}`, children: triggerNotice.text }) })) : null, error ? (_jsx(Card, { className: "border-destructive/40", children: _jsx(CardContent, { className: "pt-4 text-destructive text-sm", children: error }) })) : null, !error && rows.length === 0 && !loading ? (_jsx(Card, { children: _jsx(CardContent, { className: "pt-4 text-muted-foreground text-sm", children: messages.empty }) })) : null, rows.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: messages.workflowColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.scheduleColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.nextRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.lastRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.statusColumn }), onTriggerNow ? (_jsx("th", { className: "px-3 py-2 font-medium", children: messages.actionsColumn })) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(ScheduleRow, { row: row, lastRun: lastRuns[row.workflowId] ?? null, triggering: !!triggering[row.scheduleId], onTriggerNow: onTriggerNow ? () => void triggerRow(row) : undefined, envFlagDisabled: showEnvFlag && !envFlagOn, rootMessages: rootMessages }, row.scheduleId))) })] }) })) : null] })] }));
|
|
79
|
+
: "text-destructive"}`, children: triggerNotice.text }) })) : null, error ? (_jsx(Card, { className: "border-destructive/40", children: _jsx(CardContent, { className: "pt-4 text-destructive text-sm", children: error }) })) : null, !error && rows.length === 0 && !loading ? (_jsx(Card, { children: _jsx(CardContent, { className: "pt-4 text-muted-foreground text-sm", children: messages.empty }) })) : null, rows.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: messages.workflowColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.scheduleColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.nextRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.lastRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.statusColumn }), onTriggerNow ? (_jsx("th", { className: "px-3 py-2 font-medium", children: messages.actionsColumn })) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(ScheduleRow, { row: row, lastRun: lastRuns[row.workflowId] ?? null, triggering: !!triggering[row.scheduleId], onTriggerNow: onTriggerNow ? () => void triggerRow(row) : undefined, envFlagDisabled: showEnvFlag && !envFlagOn, rootMessages: rootMessages, locale: locale }, row.scheduleId))) })] }) })) : null] })] }));
|
|
80
80
|
}
|
|
81
|
-
function ScheduleRow({ row, lastRun, triggering, onTriggerNow, envFlagDisabled, rootMessages, }) {
|
|
81
|
+
function ScheduleRow({ row, lastRun, triggering, onTriggerNow, envFlagDisabled, rootMessages, locale, }) {
|
|
82
82
|
const messages = rootMessages.schedules;
|
|
83
|
-
return (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs", children: row.workflowId }), _jsx("td", { className: "px-3 py-2 font-mono text-xs", children: formatScheduleDecl(row.schedule, messages) }), _jsx("td", { className: "px-3 py-2 text-xs text-muted-foreground", children: formatNextRun(row, messages, rootMessages) }), _jsx("td", { className: "px-3 py-2 text-xs", children: formatLastRun(lastRun, row, messages, rootMessages) }), _jsx("td", { className: "px-3 py-2", children: _jsx(StatusPill, { row: row, envFlagDisabled: envFlagDisabled, messages: messages }) }), onTriggerNow ? (_jsx("td", { className: "px-3 py-2", children: _jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onTriggerNow, disabled: triggering, children: triggering ? messages.triggering : messages.triggerNow }) })) : null] }));
|
|
83
|
+
return (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs", children: row.workflowId }), _jsx("td", { className: "px-3 py-2 font-mono text-xs", children: formatScheduleDecl(row.schedule, messages) }), _jsx("td", { className: "px-3 py-2 text-xs text-muted-foreground", children: formatNextRun(row, messages, rootMessages, locale) }), _jsx("td", { className: "px-3 py-2 text-xs", children: formatLastRun(lastRun, row, messages, rootMessages, locale) }), _jsx("td", { className: "px-3 py-2", children: _jsx(StatusPill, { row: row, envFlagDisabled: envFlagDisabled, messages: messages }) }), onTriggerNow ? (_jsx("td", { className: "px-3 py-2", children: _jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onTriggerNow, disabled: triggering, children: triggering ? messages.triggering : messages.triggerNow }) })) : null] }));
|
|
84
84
|
}
|
|
85
85
|
function StatusPill({ row, envFlagDisabled, messages, }) {
|
|
86
86
|
if (envFlagDisabled) {
|
|
@@ -103,29 +103,29 @@ function formatScheduleDecl(decl, messages) {
|
|
|
103
103
|
return messages.at(decl.at);
|
|
104
104
|
return messages.eventDriven;
|
|
105
105
|
}
|
|
106
|
-
function formatNextRun(row, messages, rootMessages) {
|
|
106
|
+
function formatNextRun(row, messages, rootMessages, locale) {
|
|
107
107
|
if (!row.enabled || row.nextRunAt === null)
|
|
108
108
|
return messages.notScheduled;
|
|
109
109
|
const delta = row.nextRunAt - Date.now();
|
|
110
|
-
const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages);
|
|
110
|
+
const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages, locale);
|
|
111
111
|
return delta >= 0 ? messages.inFuture(relative) : messages.inPast(relative);
|
|
112
112
|
}
|
|
113
|
-
function formatLastRun(lastRun, row, messages, rootMessages) {
|
|
113
|
+
function formatLastRun(lastRun, row, messages, rootMessages, locale) {
|
|
114
114
|
if (lastRun) {
|
|
115
|
-
const relative = formatRelative(lastRun.startedAt, rootMessages);
|
|
115
|
+
const relative = formatRelative(lastRun.startedAt, rootMessages, locale);
|
|
116
116
|
const label = lastRunLabel(lastRun.status, relative, messages);
|
|
117
117
|
return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(StatusIcon, { status: lastRun.status }), label] }));
|
|
118
118
|
}
|
|
119
119
|
if (row.lastError && row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
120
|
-
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages);
|
|
120
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale);
|
|
121
121
|
return (_jsxs("span", { className: "inline-flex items-center gap-1.5", title: row.lastError, children: [_jsx(StatusIcon, { status: "failed" }), messages.lastRunFailed(relative)] }));
|
|
122
122
|
}
|
|
123
123
|
if (row.lastSuccessfulRunAt !== undefined && row.lastSuccessfulRunAt !== null) {
|
|
124
|
-
const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages);
|
|
124
|
+
const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages, locale);
|
|
125
125
|
return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(StatusIcon, { status: "succeeded" }), messages.lastRunSucceeded(relative)] }));
|
|
126
126
|
}
|
|
127
127
|
if (row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
128
|
-
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages);
|
|
128
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale);
|
|
129
129
|
return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(CalendarClock, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), messages.lastFireRecorded(relative)] }));
|
|
130
130
|
}
|
|
131
131
|
return _jsx("span", { className: "text-muted-foreground", children: messages.lastRunNone });
|
package/dist/i18n/provider.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ export declare function getWorkflowRunsUiI18n({ locale, overrides, }: {
|
|
|
14
14
|
locale?: string | null | undefined;
|
|
15
15
|
overrides?: WorkflowRunsUiMessageOverrides | null;
|
|
16
16
|
}): PackageI18nValue<WorkflowRunsUiMessages>;
|
|
17
|
-
export declare function WorkflowRunsUiMessagesProvider({ children, locale, overrides, }: {
|
|
17
|
+
export declare function WorkflowRunsUiMessagesProvider({ children, locale, timeZone, overrides, }: {
|
|
18
18
|
children: ReactNode;
|
|
19
19
|
locale: string | null | undefined;
|
|
20
|
+
timeZone?: string | null;
|
|
20
21
|
overrides?: WorkflowRunsUiMessageOverrides | null;
|
|
21
22
|
}): import("react").JSX.Element;
|
|
22
23
|
export declare const useWorkflowRunsUiI18n: () => PackageI18nValue<WorkflowRunsUiMessages>;
|
package/dist/i18n/provider.js
CHANGED
|
@@ -31,8 +31,8 @@ export function getWorkflowRunsUiI18n({ locale, overrides, }) {
|
|
|
31
31
|
...createLocaleFormatters(resolvedLocale),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
export function WorkflowRunsUiMessagesProvider({ children, locale, overrides, }) {
|
|
35
|
-
return (_jsx(workflowRunsUiContext.ResolvedMessagesProvider, { definitions: workflowRunsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
|
|
34
|
+
export function WorkflowRunsUiMessagesProvider({ children, locale, timeZone, overrides, }) {
|
|
35
|
+
return (_jsx(workflowRunsUiContext.ResolvedMessagesProvider, { definitions: workflowRunsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, timeZone: timeZone, overrides: overrides, children: children }));
|
|
36
36
|
}
|
|
37
37
|
export const useWorkflowRunsUiI18n = workflowRunsUiContext.useI18n;
|
|
38
38
|
export const useWorkflowRunsUiMessages = workflowRunsUiContext.useMessages;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/workflows-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.122.1",
|
|
4
4
|
"description": "React hooks for Voyant Workflows — trigger, subscribe, and stream runs — plus the styled workflow-run admin UI (formerly @voyant-travel/workflows-ui).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@voyant-travel/i18n": "^0.111.
|
|
70
|
-
"@voyant-travel/
|
|
71
|
-
"@voyant-travel/
|
|
69
|
+
"@voyant-travel/i18n": "^0.111.3",
|
|
70
|
+
"@voyant-travel/react": "^0.104.2",
|
|
71
|
+
"@voyant-travel/workflows": "^0.122.1"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@tanstack/react-query": "^5.0.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"react-dom": "^19.2.7",
|
|
95
95
|
"typescript": "^6.0.3",
|
|
96
96
|
"vitest": "^4.1.9",
|
|
97
|
-
"@voyant-travel/i18n": "^0.111.
|
|
97
|
+
"@voyant-travel/i18n": "^0.111.3",
|
|
98
98
|
"@voyant-travel/react": "^0.104.2",
|
|
99
99
|
"@voyant-travel/ui": "^0.109.2",
|
|
100
100
|
"@voyant-travel/voyant-typescript-config": "^0.1.0"
|
|
@@ -156,10 +156,14 @@ export function PayloadBlock({
|
|
|
156
156
|
)
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export function formatRelative(
|
|
159
|
+
export function formatRelative(
|
|
160
|
+
iso: string,
|
|
161
|
+
messages: WorkflowRunsUiMessages,
|
|
162
|
+
locale: string,
|
|
163
|
+
): string {
|
|
160
164
|
const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000)
|
|
161
165
|
if (Math.abs(seconds) < 5) return messages.format.relativeNow
|
|
162
|
-
const formatter = new Intl.RelativeTimeFormat(
|
|
166
|
+
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
|
|
163
167
|
if (Math.abs(seconds) < 60) return formatter.format(-seconds, "second")
|
|
164
168
|
const minutes = Math.round(seconds / 60)
|
|
165
169
|
if (Math.abs(minutes) < 60) return formatter.format(-minutes, "minute")
|
|
@@ -5,7 +5,10 @@ import { Card, CardContent, CardHeader, CardTitle } from "@voyant-travel/ui/comp
|
|
|
5
5
|
import { ChevronDown, ChevronRight, Link2 } from "lucide-react"
|
|
6
6
|
import { useEffect, useMemo, useState } from "react"
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
useWorkflowRunsUiI18nOrDefault,
|
|
10
|
+
useWorkflowRunsUiMessagesOrDefault,
|
|
11
|
+
} from "../i18n/index.js"
|
|
9
12
|
import type {
|
|
10
13
|
WorkflowRun,
|
|
11
14
|
WorkflowRunErrorPayload,
|
|
@@ -102,7 +105,7 @@ export function WorkflowRunDetailPage({
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
function RunHeaderCard({ run, onOpenRun }: { run: WorkflowRun; onOpenRun?: (id: string) => void }) {
|
|
105
|
-
const messages =
|
|
108
|
+
const { formatDateTime, messages } = useWorkflowRunsUiI18nOrDefault()
|
|
106
109
|
return (
|
|
107
110
|
<Card>
|
|
108
111
|
<CardHeader className="space-y-3 pb-4">
|
|
@@ -124,12 +127,12 @@ function RunHeaderCard({ run, onOpenRun }: { run: WorkflowRun; onOpenRun?: (id:
|
|
|
124
127
|
</div>
|
|
125
128
|
<div className="text-muted-foreground text-sm">
|
|
126
129
|
<span>
|
|
127
|
-
{messages.detail.started} {
|
|
130
|
+
{messages.detail.started} {formatDateTime(run.startedAt)}
|
|
128
131
|
</span>
|
|
129
132
|
{run.completedAt ? (
|
|
130
133
|
<span>
|
|
131
134
|
{" · "}
|
|
132
|
-
{messages.detail.finished} {
|
|
135
|
+
{messages.detail.finished} {formatDateTime(run.completedAt)}
|
|
133
136
|
</span>
|
|
134
137
|
) : null}
|
|
135
138
|
</div>
|
|
@@ -217,7 +220,7 @@ function RerunsListCard({
|
|
|
217
220
|
reruns: WorkflowRun[]
|
|
218
221
|
onOpenRun?: (id: string) => void
|
|
219
222
|
}) {
|
|
220
|
-
const messages =
|
|
223
|
+
const { locale, messages } = useWorkflowRunsUiI18nOrDefault()
|
|
221
224
|
return (
|
|
222
225
|
<Card>
|
|
223
226
|
<CardHeader className="pb-3">
|
|
@@ -245,7 +248,7 @@ function RerunsListCard({
|
|
|
245
248
|
</Badge>
|
|
246
249
|
) : null}
|
|
247
250
|
<span className="ml-auto whitespace-nowrap text-muted-foreground text-xs">
|
|
248
|
-
{formatRelative(run.startedAt, messages)}
|
|
251
|
+
{formatRelative(run.startedAt, messages, locale)}
|
|
249
252
|
</span>
|
|
250
253
|
</button>
|
|
251
254
|
</li>
|
|
@@ -5,7 +5,10 @@ import { Card, CardContent } from "@voyant-travel/ui/components/card"
|
|
|
5
5
|
import { Clock, Workflow } from "lucide-react"
|
|
6
6
|
import { useEffect, useMemo, useState } from "react"
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
useWorkflowRunsUiI18nOrDefault,
|
|
10
|
+
useWorkflowRunsUiMessagesOrDefault,
|
|
11
|
+
} from "../i18n/index.js"
|
|
9
12
|
import type {
|
|
10
13
|
ListWorkflowRunsQuery,
|
|
11
14
|
WorkflowRun,
|
|
@@ -241,7 +244,7 @@ function RunListItem({
|
|
|
241
244
|
onToggleStatus: (status: WorkflowRunStatus) => void
|
|
242
245
|
onToggleTag: (tag: string) => void
|
|
243
246
|
}) {
|
|
244
|
-
const messages =
|
|
247
|
+
const { locale, messages } = useWorkflowRunsUiI18nOrDefault()
|
|
245
248
|
return (
|
|
246
249
|
<div
|
|
247
250
|
className={`rounded-md border bg-card p-3 text-sm transition-colors hover:bg-muted/50 ${
|
|
@@ -257,7 +260,7 @@ function RunListItem({
|
|
|
257
260
|
<StatusIcon status={run.status} />
|
|
258
261
|
<span className="truncate font-medium">{run.workflowName}</span>
|
|
259
262
|
<span className="ml-auto whitespace-nowrap text-muted-foreground text-xs">
|
|
260
|
-
{formatRelative(run.startedAt, messages)}
|
|
263
|
+
{formatRelative(run.startedAt, messages, locale)}
|
|
261
264
|
</span>
|
|
262
265
|
</button>
|
|
263
266
|
<button
|
|
@@ -6,7 +6,7 @@ import { Card, CardContent } from "@voyant-travel/ui/components/card"
|
|
|
6
6
|
import { AlertTriangle, CalendarClock, RefreshCw } from "lucide-react"
|
|
7
7
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { useWorkflowRunsUiI18nOrDefault, type WorkflowRunsUiMessages } from "../i18n/index.js"
|
|
10
10
|
import type {
|
|
11
11
|
ListWorkflowSchedulesResponse,
|
|
12
12
|
WorkflowScheduleDecl,
|
|
@@ -44,7 +44,7 @@ export function WorkflowSchedulesPage({
|
|
|
44
44
|
pollIntervalMs = 30_000,
|
|
45
45
|
className,
|
|
46
46
|
}: WorkflowSchedulesPageProps) {
|
|
47
|
-
const rootMessages =
|
|
47
|
+
const { locale, messages: rootMessages } = useWorkflowRunsUiI18nOrDefault()
|
|
48
48
|
const messages = rootMessages.schedules
|
|
49
49
|
const [response, setResponse] = useState<ListWorkflowSchedulesResponse | null>(null)
|
|
50
50
|
const [lastRuns, setLastRuns] = useState<Record<string, WorkflowRun | null>>({})
|
|
@@ -220,6 +220,7 @@ export function WorkflowSchedulesPage({
|
|
|
220
220
|
onTriggerNow={onTriggerNow ? () => void triggerRow(row) : undefined}
|
|
221
221
|
envFlagDisabled={showEnvFlag && !envFlagOn}
|
|
222
222
|
rootMessages={rootMessages}
|
|
223
|
+
locale={locale}
|
|
223
224
|
/>
|
|
224
225
|
))}
|
|
225
226
|
</tbody>
|
|
@@ -238,6 +239,7 @@ function ScheduleRow({
|
|
|
238
239
|
onTriggerNow,
|
|
239
240
|
envFlagDisabled,
|
|
240
241
|
rootMessages,
|
|
242
|
+
locale,
|
|
241
243
|
}: {
|
|
242
244
|
row: WorkflowScheduleSummary
|
|
243
245
|
lastRun: WorkflowRun | null
|
|
@@ -245,6 +247,7 @@ function ScheduleRow({
|
|
|
245
247
|
onTriggerNow?: () => void
|
|
246
248
|
envFlagDisabled: boolean
|
|
247
249
|
rootMessages: WorkflowRunsUiMessages
|
|
250
|
+
locale: string
|
|
248
251
|
}) {
|
|
249
252
|
const messages = rootMessages.schedules
|
|
250
253
|
|
|
@@ -253,9 +256,11 @@ function ScheduleRow({
|
|
|
253
256
|
<td className="px-3 py-2 font-mono text-xs">{row.workflowId}</td>
|
|
254
257
|
<td className="px-3 py-2 font-mono text-xs">{formatScheduleDecl(row.schedule, messages)}</td>
|
|
255
258
|
<td className="px-3 py-2 text-xs text-muted-foreground">
|
|
256
|
-
{formatNextRun(row, messages, rootMessages)}
|
|
259
|
+
{formatNextRun(row, messages, rootMessages, locale)}
|
|
260
|
+
</td>
|
|
261
|
+
<td className="px-3 py-2 text-xs">
|
|
262
|
+
{formatLastRun(lastRun, row, messages, rootMessages, locale)}
|
|
257
263
|
</td>
|
|
258
|
-
<td className="px-3 py-2 text-xs">{formatLastRun(lastRun, row, messages, rootMessages)}</td>
|
|
259
264
|
<td className="px-3 py-2">
|
|
260
265
|
<StatusPill row={row} envFlagDisabled={envFlagDisabled} messages={messages} />
|
|
261
266
|
</td>
|
|
@@ -327,10 +332,11 @@ function formatNextRun(
|
|
|
327
332
|
row: WorkflowScheduleSummary,
|
|
328
333
|
messages: SchedulesMessages,
|
|
329
334
|
rootMessages: WorkflowRunsUiMessages,
|
|
335
|
+
locale: string,
|
|
330
336
|
): string {
|
|
331
337
|
if (!row.enabled || row.nextRunAt === null) return messages.notScheduled
|
|
332
338
|
const delta = row.nextRunAt - Date.now()
|
|
333
|
-
const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages)
|
|
339
|
+
const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages, locale)
|
|
334
340
|
return delta >= 0 ? messages.inFuture(relative) : messages.inPast(relative)
|
|
335
341
|
}
|
|
336
342
|
|
|
@@ -339,9 +345,10 @@ function formatLastRun(
|
|
|
339
345
|
row: WorkflowScheduleSummary,
|
|
340
346
|
messages: SchedulesMessages,
|
|
341
347
|
rootMessages: WorkflowRunsUiMessages,
|
|
348
|
+
locale: string,
|
|
342
349
|
) {
|
|
343
350
|
if (lastRun) {
|
|
344
|
-
const relative = formatRelative(lastRun.startedAt, rootMessages)
|
|
351
|
+
const relative = formatRelative(lastRun.startedAt, rootMessages, locale)
|
|
345
352
|
const label = lastRunLabel(lastRun.status, relative, messages)
|
|
346
353
|
return (
|
|
347
354
|
<span className="inline-flex items-center gap-1.5">
|
|
@@ -351,7 +358,7 @@ function formatLastRun(
|
|
|
351
358
|
)
|
|
352
359
|
}
|
|
353
360
|
if (row.lastError && row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
354
|
-
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
|
|
361
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale)
|
|
355
362
|
return (
|
|
356
363
|
<span className="inline-flex items-center gap-1.5" title={row.lastError}>
|
|
357
364
|
<StatusIcon status="failed" />
|
|
@@ -360,7 +367,11 @@ function formatLastRun(
|
|
|
360
367
|
)
|
|
361
368
|
}
|
|
362
369
|
if (row.lastSuccessfulRunAt !== undefined && row.lastSuccessfulRunAt !== null) {
|
|
363
|
-
const relative = formatRelative(
|
|
370
|
+
const relative = formatRelative(
|
|
371
|
+
new Date(row.lastSuccessfulRunAt).toISOString(),
|
|
372
|
+
rootMessages,
|
|
373
|
+
locale,
|
|
374
|
+
)
|
|
364
375
|
return (
|
|
365
376
|
<span className="inline-flex items-center gap-1.5">
|
|
366
377
|
<StatusIcon status="succeeded" />
|
|
@@ -369,7 +380,7 @@ function formatLastRun(
|
|
|
369
380
|
)
|
|
370
381
|
}
|
|
371
382
|
if (row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
372
|
-
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
|
|
383
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale)
|
|
373
384
|
return (
|
|
374
385
|
<span className="inline-flex items-center gap-1.5">
|
|
375
386
|
<CalendarClock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
package/src/i18n/provider.tsx
CHANGED
|
@@ -66,10 +66,12 @@ export function getWorkflowRunsUiI18n({
|
|
|
66
66
|
export function WorkflowRunsUiMessagesProvider({
|
|
67
67
|
children,
|
|
68
68
|
locale,
|
|
69
|
+
timeZone,
|
|
69
70
|
overrides,
|
|
70
71
|
}: {
|
|
71
72
|
children: ReactNode
|
|
72
73
|
locale: string | null | undefined
|
|
74
|
+
timeZone?: string | null
|
|
73
75
|
overrides?: WorkflowRunsUiMessageOverrides | null
|
|
74
76
|
}) {
|
|
75
77
|
return (
|
|
@@ -77,6 +79,7 @@ export function WorkflowRunsUiMessagesProvider({
|
|
|
77
79
|
definitions={workflowRunsUiMessageDefinitions}
|
|
78
80
|
fallbackLocale={fallbackLocale}
|
|
79
81
|
locale={locale}
|
|
82
|
+
timeZone={timeZone}
|
|
80
83
|
overrides={overrides}
|
|
81
84
|
>
|
|
82
85
|
{children}
|