@flanksource/clicky-ui 0.2.4 → 0.2.5
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/Combobox.cjs +291 -0
- package/dist/components/Combobox.cjs.map +1 -0
- package/dist/components/Combobox.d.ts +59 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox.js +291 -0
- package/dist/components/Combobox.js.map +1 -0
- package/dist/components/FilterBar.cjs +153 -124
- package/dist/components/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar.d.ts +25 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +153 -124
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/FormatOptionsDropdown.cjs +62 -0
- package/dist/components/FormatOptionsDropdown.cjs.map +1 -0
- package/dist/components/FormatOptionsDropdown.d.ts +37 -0
- package/dist/components/FormatOptionsDropdown.d.ts.map +1 -0
- package/dist/components/FormatOptionsDropdown.js +62 -0
- package/dist/components/FormatOptionsDropdown.js.map +1 -0
- package/dist/components/SplitButton.cjs +87 -0
- package/dist/components/SplitButton.cjs.map +1 -0
- package/dist/components/SplitButton.d.ts +32 -0
- package/dist/components/SplitButton.d.ts.map +1 -0
- package/dist/components/SplitButton.js +87 -0
- package/dist/components/SplitButton.js.map +1 -0
- package/dist/components/icon-menu-picker.cjs +1 -1
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.js +1 -1
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components.cjs +7 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +7 -0
- package/dist/components.js.map +1 -1
- package/dist/data/Badge.d.ts +1 -1
- package/dist/data/ProgressBar.cjs +4 -2
- package/dist/data/ProgressBar.cjs.map +1 -1
- package/dist/data/ProgressBar.d.ts +1 -1
- package/dist/data/ProgressBar.d.ts.map +1 -1
- package/dist/data/ProgressBar.js +4 -2
- package/dist/data/ProgressBar.js.map +1 -1
- package/dist/data/TaskManager.cjs +162 -0
- package/dist/data/TaskManager.cjs.map +1 -0
- package/dist/data/TaskManager.d.ts +21 -0
- package/dist/data/TaskManager.d.ts.map +1 -0
- package/dist/data/TaskManager.js +162 -0
- package/dist/data/TaskManager.js.map +1 -0
- package/dist/data/TaskProgress.cjs +125 -0
- package/dist/data/TaskProgress.cjs.map +1 -0
- package/dist/data/TaskProgress.d.ts +20 -0
- package/dist/data/TaskProgress.d.ts.map +1 -0
- package/dist/data/TaskProgress.js +125 -0
- package/dist/data/TaskProgress.js.map +1 -0
- package/dist/data/TaskSnapshot.cjs +23 -0
- package/dist/data/TaskSnapshot.cjs.map +1 -0
- package/dist/data/TaskSnapshot.d.ts +49 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -0
- package/dist/data/TaskSnapshot.js +23 -0
- package/dist/data/TaskSnapshot.js.map +1 -0
- package/dist/data/TimeseriesPanel.axes.cjs +39 -0
- package/dist/data/TimeseriesPanel.axes.cjs.map +1 -0
- package/dist/data/TimeseriesPanel.axes.d.ts +28 -0
- package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -0
- package/dist/data/TimeseriesPanel.axes.js +39 -0
- package/dist/data/TimeseriesPanel.axes.js.map +1 -0
- package/dist/data/TimeseriesPanel.cjs +324 -0
- package/dist/data/TimeseriesPanel.cjs.map +1 -0
- package/dist/data/TimeseriesPanel.d.ts +141 -0
- package/dist/data/TimeseriesPanel.d.ts.map +1 -0
- package/dist/data/TimeseriesPanel.js +324 -0
- package/dist/data/TimeseriesPanel.js.map +1 -0
- package/dist/data/Tree.cjs.map +1 -1
- package/dist/data/Tree.d.ts.map +1 -1
- package/dist/data/Tree.js.map +1 -1
- package/dist/data/TreeNode.cjs +44 -7
- package/dist/data/TreeNode.cjs.map +1 -1
- package/dist/data/TreeNode.d.ts +19 -1
- package/dist/data/TreeNode.d.ts.map +1 -1
- package/dist/data/TreeNode.js +44 -7
- package/dist/data/TreeNode.js.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +66 -44
- package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts +21 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js +65 -43
- package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
- package/dist/data/diagnostics/DiagnosticsTree.cjs +2 -1
- package/dist/data/diagnostics/DiagnosticsTree.cjs.map +1 -1
- package/dist/data/diagnostics/DiagnosticsTree.js +2 -1
- package/dist/data/diagnostics/DiagnosticsTree.js.map +1 -1
- package/dist/data/diagnostics/FrameSourceWindow.cjs +37 -0
- package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -0
- package/dist/data/diagnostics/FrameSourceWindow.d.ts +15 -0
- package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -0
- package/dist/data/diagnostics/FrameSourceWindow.js +37 -0
- package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -0
- package/dist/data/diagnostics/JvmStackTrace.cjs +25 -12
- package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
- package/dist/data/diagnostics/JvmStackTrace.d.ts +8 -1
- package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
- package/dist/data/diagnostics/JvmStackTrace.js +26 -13
- package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
- package/dist/data/diagnostics/ThreadCard.cjs +9 -2
- package/dist/data/diagnostics/ThreadCard.cjs.map +1 -1
- package/dist/data/diagnostics/ThreadCard.d.ts +3 -1
- package/dist/data/diagnostics/ThreadCard.d.ts.map +1 -1
- package/dist/data/diagnostics/ThreadCard.js +9 -2
- package/dist/data/diagnostics/ThreadCard.js.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.cjs +10 -1
- package/dist/data/diagnostics/jvm-stacktrace.cjs.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.d.ts.map +1 -1
- package/dist/data/diagnostics/jvm-stacktrace.js +10 -1
- package/dist/data/diagnostics/jvm-stacktrace.js.map +1 -1
- package/dist/data/diagnostics/utils.cjs +0 -12
- package/dist/data/diagnostics/utils.cjs.map +1 -1
- package/dist/data/diagnostics/utils.d.ts +1 -1
- package/dist/data/diagnostics/utils.d.ts.map +1 -1
- package/dist/data/diagnostics/utils.js +0 -12
- package/dist/data/diagnostics/utils.js.map +1 -1
- package/dist/data/task-status.cjs +108 -0
- package/dist/data/task-status.cjs.map +1 -0
- package/dist/data/task-status.d.ts +21 -0
- package/dist/data/task-status.d.ts.map +1 -0
- package/dist/data/task-status.js +108 -0
- package/dist/data/task-status.js.map +1 -0
- package/dist/data.cjs +21 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +7 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-task-run.cjs +137 -0
- package/dist/hooks/use-task-run.cjs.map +1 -0
- package/dist/hooks/use-task-run.d.ts +35 -0
- package/dist/hooks/use-task-run.d.ts.map +1 -0
- package/dist/hooks/use-task-run.js +137 -0
- package/dist/hooks/use-task-run.js.map +1 -0
- package/dist/hooks.cjs +3 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +3 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +37 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/color.cjs +33 -0
- package/dist/lib/color.cjs.map +1 -0
- package/dist/lib/color.d.ts +10 -0
- package/dist/lib/color.d.ts.map +1 -0
- package/dist/lib/color.js +33 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/format.cjs +77 -0
- package/dist/lib/format.cjs.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +77 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/overlay/DropdownMenu.cjs +18 -2
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts +3 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +18 -2
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +4 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils.cjs +6 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -0
- package/dist/utils.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const utils = require("../lib/utils.cjs");
|
|
6
|
+
const ProgressBar = require("./ProgressBar.cjs");
|
|
7
|
+
const taskStatus = require("./task-status.cjs");
|
|
8
|
+
const MAX_COMPLETED = 5;
|
|
9
|
+
const MAX_PENDING = 3;
|
|
10
|
+
function TaskProgress({ snapshots, title, compact, className }) {
|
|
11
|
+
const groups = snapshots.filter((s) => s.type === "group");
|
|
12
|
+
const tasks = snapshots.filter((s) => s.type === "task");
|
|
13
|
+
if (groups.length === 0) {
|
|
14
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("text-sm text-muted-foreground", className), children: [
|
|
15
|
+
title ? `${title}: ` : "",
|
|
16
|
+
"No tasks yet."
|
|
17
|
+
] });
|
|
18
|
+
}
|
|
19
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn(compact ? "space-y-2" : "space-y-3", className), children: [
|
|
20
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: title }),
|
|
21
|
+
groups.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22
|
+
TaskGroupCard,
|
|
23
|
+
{
|
|
24
|
+
group: g,
|
|
25
|
+
tasks: tasks.filter((t) => t.groupId === g.groupId || t.group === g.id),
|
|
26
|
+
compact
|
|
27
|
+
},
|
|
28
|
+
g.groupId || g.id
|
|
29
|
+
))
|
|
30
|
+
] });
|
|
31
|
+
}
|
|
32
|
+
function isFailedOrWarn(t) {
|
|
33
|
+
return t.status === "failed" || t.status === "FAIL" || t.status === "ERR" || t.status === "warning";
|
|
34
|
+
}
|
|
35
|
+
function TaskGroupCard({
|
|
36
|
+
group: g,
|
|
37
|
+
tasks,
|
|
38
|
+
compact
|
|
39
|
+
}) {
|
|
40
|
+
const [showAll, setShowAll] = react.useState(false);
|
|
41
|
+
const total = g.total ?? tasks.length;
|
|
42
|
+
const counts = taskStatus.bucketTasks(tasks);
|
|
43
|
+
const done = counts.ok + counts.warn + counts.fail;
|
|
44
|
+
const progress = total > 0 ? `${done}/${total}` : "";
|
|
45
|
+
const running = tasks.filter((t) => t.status === "running");
|
|
46
|
+
const pending = tasks.filter((t) => t.status === "pending");
|
|
47
|
+
const completed = tasks.filter((t) => t.status !== "running" && t.status !== "pending");
|
|
48
|
+
const alwaysShow = completed.filter(isFailedOrWarn);
|
|
49
|
+
const collapsible = completed.filter((t) => !isFailedOrWarn(t));
|
|
50
|
+
const hiddenCompleted = collapsible.length - MAX_COMPLETED;
|
|
51
|
+
const collapseCompleted = !showAll && hiddenCompleted > 0;
|
|
52
|
+
const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;
|
|
53
|
+
const hiddenPending = pending.length - MAX_PENDING;
|
|
54
|
+
const collapsePending = !showAll && hiddenPending > 0;
|
|
55
|
+
const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;
|
|
56
|
+
const isTerminal = g.status !== "running" && g.status !== "pending";
|
|
57
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("rounded-lg border bg-card", compact ? "p-3" : "p-4"), children: [
|
|
58
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
59
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [
|
|
60
|
+
/* @__PURE__ */ jsxRuntime.jsx("iconify-icon", { icon: taskStatus.taskStatusIcon(g.status), class: taskStatus.taskStatusColor(g.status) }),
|
|
61
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: g.name }),
|
|
62
|
+
progress && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: progress }),
|
|
63
|
+
g.kind && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground", children: g.kind })
|
|
64
|
+
] }),
|
|
65
|
+
isTerminal && /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
|
+
"span",
|
|
67
|
+
{
|
|
68
|
+
className: utils.cn(
|
|
69
|
+
"rounded-full px-2 py-0.5 text-xs",
|
|
70
|
+
taskStatus.taskStatusColor(g.status),
|
|
71
|
+
taskStatus.taskStatusBg(g.status)
|
|
72
|
+
),
|
|
73
|
+
children: g.status
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
] }),
|
|
77
|
+
total > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(ProgressBar.ProgressBar, { segments: taskStatus.taskSegments(counts), total, height: "h-1.5" }) }),
|
|
78
|
+
hiddenCompleted > 0 || hiddenPending > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
79
|
+
"button",
|
|
80
|
+
{
|
|
81
|
+
type: "button",
|
|
82
|
+
className: "border-b py-1.5 text-xs text-muted-foreground hover:text-foreground",
|
|
83
|
+
onClick: () => setShowAll((v) => !v),
|
|
84
|
+
children: showAll ? "▲ collapse" : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`
|
|
85
|
+
}
|
|
86
|
+
) : null,
|
|
87
|
+
[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => /* @__PURE__ */ jsxRuntime.jsx(TaskRow, { task: t }, t.id))
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
function TaskRow({ task: t }) {
|
|
91
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
92
|
+
const logs = t.logs ?? [];
|
|
93
|
+
const hasLogs = logs.length > 0;
|
|
94
|
+
const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
|
|
95
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
className: utils.cn(
|
|
99
|
+
"flex items-start gap-3 border-b py-2 last:border-0",
|
|
100
|
+
hasLogs && "-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50"
|
|
101
|
+
),
|
|
102
|
+
onClick: hasLogs ? () => setExpanded((v) => !v) : void 0,
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsxRuntime.jsx("iconify-icon", { icon: taskStatus.taskStatusIcon(t.status), class: utils.cn(taskStatus.taskStatusColor(t.status), "mt-0.5 text-lg") }),
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium", children: [
|
|
108
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: t.name }),
|
|
109
|
+
t.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs font-normal text-red-500", children: t.error }),
|
|
110
|
+
latestWarn && /* @__PURE__ */ jsxRuntime.jsx("span", { className: utils.cn("truncate text-xs font-normal", taskStatus.logLevelColor("warn")), children: latestWarn.message }),
|
|
111
|
+
hasLogs && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: logs.length })
|
|
112
|
+
] }),
|
|
113
|
+
t.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
|
|
114
|
+
] }),
|
|
115
|
+
expanded && hasLogs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("text-xs", taskStatus.logLevelColor(l.level)), children: [
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
|
|
117
|
+
l.message
|
|
118
|
+
] }, i)) })
|
|
119
|
+
] })
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
exports.TaskProgress = TaskProgress;
|
|
125
|
+
//# sourceMappingURL=TaskProgress.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskProgress.cjs","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["cn","jsxs","jsx","useState","bucketTasks","taskStatusIcon","taskStatusColor","taskStatusBg","ProgressBar","taskSegments","logLevelColor"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TaskSnapshot } from './TaskSnapshot';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace JSX {
|
|
4
|
+
interface IntrinsicElements {
|
|
5
|
+
"iconify-icon": {
|
|
6
|
+
icon: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export interface TaskProgressProps {
|
|
13
|
+
snapshots: TaskSnapshot[];
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Tighter spacing for embedding in a panel. */
|
|
16
|
+
compact?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=TaskProgress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskProgress.d.ts","sourceRoot":"","sources":["../../src/data/TaskProgress.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAiB7D,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,cAAc,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC;SAClD;KACF;CACF;AAKD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAyBvF"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { ProgressBar } from "./ProgressBar.js";
|
|
5
|
+
import { bucketTasks, taskStatusColor, taskStatusIcon, taskStatusBg, taskSegments, logLevelColor } from "./task-status.js";
|
|
6
|
+
const MAX_COMPLETED = 5;
|
|
7
|
+
const MAX_PENDING = 3;
|
|
8
|
+
function TaskProgress({ snapshots, title, compact, className }) {
|
|
9
|
+
const groups = snapshots.filter((s) => s.type === "group");
|
|
10
|
+
const tasks = snapshots.filter((s) => s.type === "task");
|
|
11
|
+
if (groups.length === 0) {
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("text-sm text-muted-foreground", className), children: [
|
|
13
|
+
title ? `${title}: ` : "",
|
|
14
|
+
"No tasks yet."
|
|
15
|
+
] });
|
|
16
|
+
}
|
|
17
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(compact ? "space-y-2" : "space-y-3", className), children: [
|
|
18
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: title }),
|
|
19
|
+
groups.map((g) => /* @__PURE__ */ jsx(
|
|
20
|
+
TaskGroupCard,
|
|
21
|
+
{
|
|
22
|
+
group: g,
|
|
23
|
+
tasks: tasks.filter((t) => t.groupId === g.groupId || t.group === g.id),
|
|
24
|
+
compact
|
|
25
|
+
},
|
|
26
|
+
g.groupId || g.id
|
|
27
|
+
))
|
|
28
|
+
] });
|
|
29
|
+
}
|
|
30
|
+
function isFailedOrWarn(t) {
|
|
31
|
+
return t.status === "failed" || t.status === "FAIL" || t.status === "ERR" || t.status === "warning";
|
|
32
|
+
}
|
|
33
|
+
function TaskGroupCard({
|
|
34
|
+
group: g,
|
|
35
|
+
tasks,
|
|
36
|
+
compact
|
|
37
|
+
}) {
|
|
38
|
+
const [showAll, setShowAll] = useState(false);
|
|
39
|
+
const total = g.total ?? tasks.length;
|
|
40
|
+
const counts = bucketTasks(tasks);
|
|
41
|
+
const done = counts.ok + counts.warn + counts.fail;
|
|
42
|
+
const progress = total > 0 ? `${done}/${total}` : "";
|
|
43
|
+
const running = tasks.filter((t) => t.status === "running");
|
|
44
|
+
const pending = tasks.filter((t) => t.status === "pending");
|
|
45
|
+
const completed = tasks.filter((t) => t.status !== "running" && t.status !== "pending");
|
|
46
|
+
const alwaysShow = completed.filter(isFailedOrWarn);
|
|
47
|
+
const collapsible = completed.filter((t) => !isFailedOrWarn(t));
|
|
48
|
+
const hiddenCompleted = collapsible.length - MAX_COMPLETED;
|
|
49
|
+
const collapseCompleted = !showAll && hiddenCompleted > 0;
|
|
50
|
+
const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;
|
|
51
|
+
const hiddenPending = pending.length - MAX_PENDING;
|
|
52
|
+
const collapsePending = !showAll && hiddenPending > 0;
|
|
53
|
+
const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;
|
|
54
|
+
const isTerminal = g.status !== "running" && g.status !== "pending";
|
|
55
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border bg-card", compact ? "p-3" : "p-4"), children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [
|
|
58
|
+
/* @__PURE__ */ jsx("iconify-icon", { icon: taskStatusIcon(g.status), class: taskStatusColor(g.status) }),
|
|
59
|
+
/* @__PURE__ */ jsx("span", { children: g.name }),
|
|
60
|
+
progress && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: progress }),
|
|
61
|
+
g.kind && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground", children: g.kind })
|
|
62
|
+
] }),
|
|
63
|
+
isTerminal && /* @__PURE__ */ jsx(
|
|
64
|
+
"span",
|
|
65
|
+
{
|
|
66
|
+
className: cn(
|
|
67
|
+
"rounded-full px-2 py-0.5 text-xs",
|
|
68
|
+
taskStatusColor(g.status),
|
|
69
|
+
taskStatusBg(g.status)
|
|
70
|
+
),
|
|
71
|
+
children: g.status
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
] }),
|
|
75
|
+
total > 0 && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(ProgressBar, { segments: taskSegments(counts), total, height: "h-1.5" }) }),
|
|
76
|
+
hiddenCompleted > 0 || hiddenPending > 0 ? /* @__PURE__ */ jsx(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
className: "border-b py-1.5 text-xs text-muted-foreground hover:text-foreground",
|
|
81
|
+
onClick: () => setShowAll((v) => !v),
|
|
82
|
+
children: showAll ? "▲ collapse" : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`
|
|
83
|
+
}
|
|
84
|
+
) : null,
|
|
85
|
+
[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => /* @__PURE__ */ jsx(TaskRow, { task: t }, t.id))
|
|
86
|
+
] });
|
|
87
|
+
}
|
|
88
|
+
function TaskRow({ task: t }) {
|
|
89
|
+
const [expanded, setExpanded] = useState(false);
|
|
90
|
+
const logs = t.logs ?? [];
|
|
91
|
+
const hasLogs = logs.length > 0;
|
|
92
|
+
const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
|
|
93
|
+
return /* @__PURE__ */ jsxs(
|
|
94
|
+
"div",
|
|
95
|
+
{
|
|
96
|
+
className: cn(
|
|
97
|
+
"flex items-start gap-3 border-b py-2 last:border-0",
|
|
98
|
+
hasLogs && "-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50"
|
|
99
|
+
),
|
|
100
|
+
onClick: hasLogs ? () => setExpanded((v) => !v) : void 0,
|
|
101
|
+
children: [
|
|
102
|
+
/* @__PURE__ */ jsx("iconify-icon", { icon: taskStatusIcon(t.status), class: cn(taskStatusColor(t.status), "mt-0.5 text-lg") }),
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
105
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium", children: [
|
|
106
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: t.name }),
|
|
107
|
+
t.error && /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-normal text-red-500", children: t.error }),
|
|
108
|
+
latestWarn && /* @__PURE__ */ jsx("span", { className: cn("truncate text-xs font-normal", logLevelColor("warn")), children: latestWarn.message }),
|
|
109
|
+
hasLogs && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: logs.length })
|
|
110
|
+
] }),
|
|
111
|
+
t.duration && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
|
|
112
|
+
] }),
|
|
113
|
+
expanded && hasLogs && /* @__PURE__ */ jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxs("div", { className: cn("text-xs", logLevelColor(l.level)), children: [
|
|
114
|
+
/* @__PURE__ */ jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
|
|
115
|
+
l.message
|
|
116
|
+
] }, i)) })
|
|
117
|
+
] })
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
TaskProgress
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=TaskProgress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
4
|
+
"success",
|
|
5
|
+
"failed",
|
|
6
|
+
"warning",
|
|
7
|
+
"canceled",
|
|
8
|
+
"PASS",
|
|
9
|
+
"FAIL",
|
|
10
|
+
"ERR",
|
|
11
|
+
"SKIP"
|
|
12
|
+
]);
|
|
13
|
+
function isTerminalStatus(status) {
|
|
14
|
+
return !!status && TERMINAL_STATUSES.has(status);
|
|
15
|
+
}
|
|
16
|
+
function allGroupsTerminal(snapshots) {
|
|
17
|
+
const groups = snapshots.filter((s) => s.type === "group");
|
|
18
|
+
if (groups.length === 0) return false;
|
|
19
|
+
return groups.every((g) => isTerminalStatus(g.status));
|
|
20
|
+
}
|
|
21
|
+
exports.allGroupsTerminal = allGroupsTerminal;
|
|
22
|
+
exports.isTerminalStatus = isTerminalStatus;
|
|
23
|
+
//# sourceMappingURL=TaskSnapshot.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;AA2DA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;;;"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
level: string;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export interface TaskSnapshot {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
/** "task" or "group". */
|
|
9
|
+
type: "task" | "group";
|
|
10
|
+
/** Parent group NAME (groups: empty). */
|
|
11
|
+
group?: string;
|
|
12
|
+
status: string;
|
|
13
|
+
duration?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
/** Latest log line. */
|
|
16
|
+
message?: string;
|
|
17
|
+
logs?: LogEntry[];
|
|
18
|
+
/** Group aggregates. */
|
|
19
|
+
total?: number;
|
|
20
|
+
completed?: number;
|
|
21
|
+
failed?: number;
|
|
22
|
+
running?: number;
|
|
23
|
+
/** Stable parent-run id (tasks) or the run's own id (groups). */
|
|
24
|
+
groupId?: string;
|
|
25
|
+
kind?: string;
|
|
26
|
+
labels?: Record<string, string>;
|
|
27
|
+
owner?: string;
|
|
28
|
+
/** RFC3339. */
|
|
29
|
+
startedAt?: string;
|
|
30
|
+
/** RFC3339. */
|
|
31
|
+
finishedAt?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TaskRunMeta {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
kind?: string;
|
|
37
|
+
labels?: Record<string, string>;
|
|
38
|
+
owner?: string;
|
|
39
|
+
status: string;
|
|
40
|
+
startedAt?: string;
|
|
41
|
+
finishedAt?: string;
|
|
42
|
+
total: number;
|
|
43
|
+
completed: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
running: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function isTerminalStatus(status: string | undefined): boolean;
|
|
48
|
+
export declare function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean;
|
|
49
|
+
//# sourceMappingURL=TaskSnapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;IAClB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAIpE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
2
|
+
"success",
|
|
3
|
+
"failed",
|
|
4
|
+
"warning",
|
|
5
|
+
"canceled",
|
|
6
|
+
"PASS",
|
|
7
|
+
"FAIL",
|
|
8
|
+
"ERR",
|
|
9
|
+
"SKIP"
|
|
10
|
+
]);
|
|
11
|
+
function isTerminalStatus(status) {
|
|
12
|
+
return !!status && TERMINAL_STATUSES.has(status);
|
|
13
|
+
}
|
|
14
|
+
function allGroupsTerminal(snapshots) {
|
|
15
|
+
const groups = snapshots.filter((s) => s.type === "group");
|
|
16
|
+
if (groups.length === 0) return false;
|
|
17
|
+
return groups.every((g) => isTerminalStatus(g.status));
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
allGroupsTerminal,
|
|
21
|
+
isTerminalStatus
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=TaskSnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"AA2DA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const NO_UNIT = "\0__noUnit";
|
|
4
|
+
function assignAxes(series) {
|
|
5
|
+
const order = [];
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
for (const s of series) {
|
|
8
|
+
const u = s.unit ?? NO_UNIT;
|
|
9
|
+
if (!seen.has(u)) {
|
|
10
|
+
seen.add(u);
|
|
11
|
+
order.push(u);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const unitToAxis = /* @__PURE__ */ new Map();
|
|
15
|
+
const axes = [];
|
|
16
|
+
order.forEach((u, i) => {
|
|
17
|
+
const orientation = i === 0 ? "left" : "right";
|
|
18
|
+
unitToAxis.set(u, orientation);
|
|
19
|
+
if (i <= 1) {
|
|
20
|
+
axes.push({ id: orientation, orientation, ...u !== NO_UNIT ? { unit: u } : {} });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const axisOfKey = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const s of series) {
|
|
25
|
+
axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? "left");
|
|
26
|
+
}
|
|
27
|
+
return { axes, axisOfKey };
|
|
28
|
+
}
|
|
29
|
+
function latestOf(rows, key) {
|
|
30
|
+
var _a;
|
|
31
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
32
|
+
const v = (_a = rows[i]) == null ? void 0 : _a[key];
|
|
33
|
+
if (typeof v === "number") return v;
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
exports.assignAxes = assignAxes;
|
|
38
|
+
exports.latestOf = latestOf;
|
|
39
|
+
//# sourceMappingURL=TimeseriesPanel.axes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.cjs","sources":["../../src/data/TimeseriesPanel.axes.ts"],"sourcesContent":["import type { MergedRow, ResolvedSeries } from \"./TimeseriesPanel\";\n\nexport type AxisOrientation = \"left\" | \"right\";\n\n/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */\nexport interface AxisSpec {\n id: AxisOrientation;\n orientation: AxisOrientation;\n /** Unit the axis ticks format with; undefined for a unitless axis. */\n unit?: string;\n}\n\nexport interface AxisAssignment {\n /** 1 or 2 axes in render order (left first). */\n axes: AxisSpec[];\n /** Maps each series.key to the axis it draws against. */\n axisOfKey: Map<string, AxisOrientation>;\n}\n\n/** Sentinel grouping unitless series; never surfaced as an AxisSpec.unit. */\nconst NO_UNIT = \"\u0000__noUnit\";\n\n/**\n * Assigns series to one or two Y-axes by distinct unit, in first-seen order:\n * the first distinct unit takes the left axis, the second takes the right.\n * A third-or-later distinct unit shares the right axis (its series still plot,\n * but the right ticks read in the second unit's scale — a known approximation;\n * true N-axis support is out of scope). A single distinct unit yields one left\n * axis only. Undefined units form their own bucket so unitless series still get\n * an axis (recharts drops any series whose yAxisId is missing). Pure.\n */\nexport function assignAxes(series: ResolvedSeries[]): AxisAssignment {\n const order: string[] = [];\n const seen = new Set<string>();\n for (const s of series) {\n const u = s.unit ?? NO_UNIT;\n if (!seen.has(u)) {\n seen.add(u);\n order.push(u);\n }\n }\n\n const unitToAxis = new Map<string, AxisOrientation>();\n const axes: AxisSpec[] = [];\n order.forEach((u, i) => {\n const orientation: AxisOrientation = i === 0 ? \"left\" : \"right\";\n unitToAxis.set(u, orientation);\n if (i <= 1) {\n axes.push({ id: orientation, orientation, ...(u !== NO_UNIT ? { unit: u } : {}) });\n }\n });\n\n const axisOfKey = new Map<string, AxisOrientation>();\n for (const s of series) {\n axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? \"left\");\n }\n\n return { axes, axisOfKey };\n}\n\n/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */\nexport function latestOf(rows: MergedRow[], key: string): number | undefined {\n for (let i = rows.length - 1; i >= 0; i--) {\n const v = rows[i]?.[key];\n if (typeof v === \"number\") return v;\n }\n return undefined;\n}\n"],"names":[],"mappings":";;AAoBA,MAAM,UAAU;AAWT,SAAS,WAAW,QAA0C;AACnE,QAAM,QAAkB,CAAA;AACxB,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,EAAE,QAAQ;AACpB,QAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,WAAK,IAAI,CAAC;AACV,YAAM,KAAK,CAAC;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iCAAiB,IAAA;AACvB,QAAM,OAAmB,CAAA;AACzB,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAM,cAA+B,MAAM,IAAI,SAAS;AACxD,eAAW,IAAI,GAAG,WAAW;AAC7B,QAAI,KAAK,GAAG;AACV,WAAK,KAAK,EAAE,IAAI,aAAa,aAAa,GAAI,MAAM,UAAU,EAAE,MAAM,EAAA,IAAM,CAAA,GAAK;AAAA,IACnF;AAAA,EACF,CAAC;AAED,QAAM,gCAAgB,IAAA;AACtB,aAAW,KAAK,QAAQ;AACtB,cAAU,IAAI,EAAE,KAAK,WAAW,IAAI,EAAE,QAAQ,OAAO,KAAK,MAAM;AAAA,EAClE;AAEA,SAAO,EAAE,MAAM,UAAA;AACjB;AAGO,SAAS,SAAS,MAAmB,KAAiC;;AAC3E,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,KAAI,UAAK,CAAC,MAAN,mBAAU;AACpB,QAAI,OAAO,MAAM,SAAU,QAAO;AAAA,EACpC;AACA,SAAO;AACT;;;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MergedRow, ResolvedSeries } from './TimeseriesPanel';
|
|
2
|
+
export type AxisOrientation = "left" | "right";
|
|
3
|
+
/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */
|
|
4
|
+
export interface AxisSpec {
|
|
5
|
+
id: AxisOrientation;
|
|
6
|
+
orientation: AxisOrientation;
|
|
7
|
+
/** Unit the axis ticks format with; undefined for a unitless axis. */
|
|
8
|
+
unit?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AxisAssignment {
|
|
11
|
+
/** 1 or 2 axes in render order (left first). */
|
|
12
|
+
axes: AxisSpec[];
|
|
13
|
+
/** Maps each series.key to the axis it draws against. */
|
|
14
|
+
axisOfKey: Map<string, AxisOrientation>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Assigns series to one or two Y-axes by distinct unit, in first-seen order:
|
|
18
|
+
* the first distinct unit takes the left axis, the second takes the right.
|
|
19
|
+
* A third-or-later distinct unit shares the right axis (its series still plot,
|
|
20
|
+
* but the right ticks read in the second unit's scale — a known approximation;
|
|
21
|
+
* true N-axis support is out of scope). A single distinct unit yields one left
|
|
22
|
+
* axis only. Undefined units form their own bucket so unitless series still get
|
|
23
|
+
* an axis (recharts drops any series whose yAxisId is missing). Pure.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assignAxes(series: ResolvedSeries[]): AxisAssignment;
|
|
26
|
+
/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */
|
|
27
|
+
export declare function latestOf(rows: MergedRow[], key: string): number | undefined;
|
|
28
|
+
//# sourceMappingURL=TimeseriesPanel.axes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.d.ts","sourceRoot":"","sources":["../../src/data/TimeseriesPanel.axes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C,wFAAwF;AACxF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,eAAe,CAAC;IACpB,WAAW,EAAE,eAAe,CAAC;IAC7B,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACzC;AAKD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,CA2BnE;AAED,8FAA8F;AAC9F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM3E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const NO_UNIT = "\0__noUnit";
|
|
2
|
+
function assignAxes(series) {
|
|
3
|
+
const order = [];
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const s of series) {
|
|
6
|
+
const u = s.unit ?? NO_UNIT;
|
|
7
|
+
if (!seen.has(u)) {
|
|
8
|
+
seen.add(u);
|
|
9
|
+
order.push(u);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const unitToAxis = /* @__PURE__ */ new Map();
|
|
13
|
+
const axes = [];
|
|
14
|
+
order.forEach((u, i) => {
|
|
15
|
+
const orientation = i === 0 ? "left" : "right";
|
|
16
|
+
unitToAxis.set(u, orientation);
|
|
17
|
+
if (i <= 1) {
|
|
18
|
+
axes.push({ id: orientation, orientation, ...u !== NO_UNIT ? { unit: u } : {} });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const axisOfKey = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const s of series) {
|
|
23
|
+
axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? "left");
|
|
24
|
+
}
|
|
25
|
+
return { axes, axisOfKey };
|
|
26
|
+
}
|
|
27
|
+
function latestOf(rows, key) {
|
|
28
|
+
var _a;
|
|
29
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
30
|
+
const v = (_a = rows[i]) == null ? void 0 : _a[key];
|
|
31
|
+
if (typeof v === "number") return v;
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
assignAxes,
|
|
37
|
+
latestOf
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=TimeseriesPanel.axes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeseriesPanel.axes.js","sources":["../../src/data/TimeseriesPanel.axes.ts"],"sourcesContent":["import type { MergedRow, ResolvedSeries } from \"./TimeseriesPanel\";\n\nexport type AxisOrientation = \"left\" | \"right\";\n\n/** A rendered Y-axis: its recharts id (== orientation) and the unit it formats with. */\nexport interface AxisSpec {\n id: AxisOrientation;\n orientation: AxisOrientation;\n /** Unit the axis ticks format with; undefined for a unitless axis. */\n unit?: string;\n}\n\nexport interface AxisAssignment {\n /** 1 or 2 axes in render order (left first). */\n axes: AxisSpec[];\n /** Maps each series.key to the axis it draws against. */\n axisOfKey: Map<string, AxisOrientation>;\n}\n\n/** Sentinel grouping unitless series; never surfaced as an AxisSpec.unit. */\nconst NO_UNIT = \"\u0000__noUnit\";\n\n/**\n * Assigns series to one or two Y-axes by distinct unit, in first-seen order:\n * the first distinct unit takes the left axis, the second takes the right.\n * A third-or-later distinct unit shares the right axis (its series still plot,\n * but the right ticks read in the second unit's scale — a known approximation;\n * true N-axis support is out of scope). A single distinct unit yields one left\n * axis only. Undefined units form their own bucket so unitless series still get\n * an axis (recharts drops any series whose yAxisId is missing). Pure.\n */\nexport function assignAxes(series: ResolvedSeries[]): AxisAssignment {\n const order: string[] = [];\n const seen = new Set<string>();\n for (const s of series) {\n const u = s.unit ?? NO_UNIT;\n if (!seen.has(u)) {\n seen.add(u);\n order.push(u);\n }\n }\n\n const unitToAxis = new Map<string, AxisOrientation>();\n const axes: AxisSpec[] = [];\n order.forEach((u, i) => {\n const orientation: AxisOrientation = i === 0 ? \"left\" : \"right\";\n unitToAxis.set(u, orientation);\n if (i <= 1) {\n axes.push({ id: orientation, orientation, ...(u !== NO_UNIT ? { unit: u } : {}) });\n }\n });\n\n const axisOfKey = new Map<string, AxisOrientation>();\n for (const s of series) {\n axisOfKey.set(s.key, unitToAxis.get(s.unit ?? NO_UNIT) ?? \"left\");\n }\n\n return { axes, axisOfKey };\n}\n\n/** Last numeric value for a series key, scanning from newest row; undefined if none. Pure. */\nexport function latestOf(rows: MergedRow[], key: string): number | undefined {\n for (let i = rows.length - 1; i >= 0; i--) {\n const v = rows[i]?.[key];\n if (typeof v === \"number\") return v;\n }\n return undefined;\n}\n"],"names":[],"mappings":"AAoBA,MAAM,UAAU;AAWT,SAAS,WAAW,QAA0C;AACnE,QAAM,QAAkB,CAAA;AACxB,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,EAAE,QAAQ;AACpB,QAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,WAAK,IAAI,CAAC;AACV,YAAM,KAAK,CAAC;AAAA,IACd;AAAA,EACF;AAEA,QAAM,iCAAiB,IAAA;AACvB,QAAM,OAAmB,CAAA;AACzB,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAM,cAA+B,MAAM,IAAI,SAAS;AACxD,eAAW,IAAI,GAAG,WAAW;AAC7B,QAAI,KAAK,GAAG;AACV,WAAK,KAAK,EAAE,IAAI,aAAa,aAAa,GAAI,MAAM,UAAU,EAAE,MAAM,EAAA,IAAM,CAAA,GAAK;AAAA,IACnF;AAAA,EACF,CAAC;AAED,QAAM,gCAAgB,IAAA;AACtB,aAAW,KAAK,QAAQ;AACtB,cAAU,IAAI,EAAE,KAAK,WAAW,IAAI,EAAE,QAAQ,OAAO,KAAK,MAAM;AAAA,EAClE;AAEA,SAAO,EAAE,MAAM,UAAA;AACjB;AAGO,SAAS,SAAS,MAAmB,KAAiC;AAzC7E;AA0CE,WAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,UAAM,KAAI,UAAK,CAAC,MAAN,mBAAU;AACpB,QAAI,OAAO,MAAM,SAAU,QAAO;AAAA,EACpC;AACA,SAAO;AACT;"}
|