@parallelworks/ui 0.62.0 → 0.64.0

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.
@@ -0,0 +1,184 @@
1
+ import { cx } from "../cx.js";
2
+ import { FileTextIcon } from "../icons.js";
3
+ import { useRunFile, useStrings, useWorkflowEngine } from "../components/Provider.js";
4
+ import Markdown from "../components/Markdown.js";
5
+ import { RunStatusIcon } from "../components/RunStatusIcon.js";
6
+ import { jobLabel } from "./util.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { useEffect, useId, useRef, useState } from "react";
9
+ //#region src/graph/StepSummaries.tsx
10
+ var SEPARATOR = " › ";
11
+ function completion(job) {
12
+ const millis = job.completedAt ? Date.parse(job.completedAt) : NaN;
13
+ return Number.isNaN(millis) ? Number.POSITIVE_INFINITY : millis;
14
+ }
15
+ /** The steps, subworkflows' included, that wrote a summary, in the order their jobs completed. */
16
+ function stepSummarySources(jobs) {
17
+ const found = [];
18
+ const collect = (level, prefix, parents) => {
19
+ for (const [job, value] of Object.entries(level)) {
20
+ const jobNames = [...parents, jobLabel(job, value)];
21
+ value.steps?.forEach((step, index) => {
22
+ if (step.summary) found.push({
23
+ prefix,
24
+ job,
25
+ jobNames,
26
+ index,
27
+ step,
28
+ completedAt: completion(value)
29
+ });
30
+ if (step.subworkflow?.jobs) collect(step.subworkflow.jobs, `${prefix}subworkflows/${job}/step_${index}/`, jobNames);
31
+ });
32
+ }
33
+ };
34
+ collect(jobs, "", []);
35
+ return found.sort((a, b) => a.completedAt - b.completedAt || 0).map(({ completedAt: _, ...source }) => source);
36
+ }
37
+ /**
38
+ * A run's step summaries, each on its own card headed by the job and step that wrote it, so a run
39
+ * reads like a report under its graph. The job list beside the graph already shows the run's tree,
40
+ * so the cards are flat.
41
+ */
42
+ function StepSummaries({ runSlug, jobs, onOpenLog }) {
43
+ const { dag } = useStrings();
44
+ const sources = stepSummarySources(jobs);
45
+ if (sources.length === 0) return null;
46
+ return /* @__PURE__ */ jsx("section", {
47
+ "aria-label": dag.stepSummaries,
48
+ className: "flex flex-col gap-3",
49
+ children: sources.map((source) => /* @__PURE__ */ jsx(SummaryCard, {
50
+ runSlug,
51
+ source,
52
+ onOpenLog: () => onOpenLog(source)
53
+ }, `${source.prefix}${source.job}:${source.index}`))
54
+ });
55
+ }
56
+ function SummaryCard({ runSlug, source, onOpenLog }) {
57
+ const { dag } = useStrings();
58
+ const engine = useWorkflowEngine();
59
+ const headingId = useId();
60
+ const ref = useRef(null);
61
+ const near = useNearViewport(ref);
62
+ const path = engine.stepLogPath(source.prefix, source.job, source.index).replace(/logs\.out$/, "summary.md");
63
+ const failed = source.step.status === "failed";
64
+ const job = source.jobNames.join(SEPARATOR);
65
+ const step = engine.stepLabel(source.step, source.index);
66
+ return /* @__PURE__ */ jsxs("article", {
67
+ ref,
68
+ "aria-labelledby": headingId,
69
+ className: cx("panel min-w-0 rounded-lg border", failed && "border-(--theme-error)"),
70
+ children: [/* @__PURE__ */ jsxs("div", {
71
+ className: "flex items-center gap-2 border-b px-4 py-2.5",
72
+ children: [
73
+ /* @__PURE__ */ jsx(RunStatusIcon, { status: source.step.status }),
74
+ /* @__PURE__ */ jsxs("h3", {
75
+ id: headingId,
76
+ "aria-label": `${job}${SEPARATOR}${step}`,
77
+ className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm",
78
+ children: [
79
+ /* @__PURE__ */ jsx("span", {
80
+ className: "min-w-0 shrink-[2] truncate theme-muted-text",
81
+ title: job,
82
+ children: job
83
+ }),
84
+ /* @__PURE__ */ jsx("span", {
85
+ className: "shrink-0 theme-muted-text",
86
+ children: SEPARATOR
87
+ }),
88
+ /* @__PURE__ */ jsx("span", {
89
+ className: "min-w-0 truncate font-medium",
90
+ title: step,
91
+ children: step
92
+ })
93
+ ]
94
+ }),
95
+ /* @__PURE__ */ jsxs("button", {
96
+ type: "button",
97
+ onClick: onOpenLog,
98
+ "aria-describedby": headingId,
99
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded px-1.5 py-0.5 text-sm theme-muted-text transition-colors hover:bg-(--theme-hover) hover:text-(--theme-link)",
100
+ children: [/* @__PURE__ */ jsx(FileTextIcon, {
101
+ "aria-hidden": true,
102
+ className: "h-3.5 w-3.5"
103
+ }), /* @__PURE__ */ jsx("span", {
104
+ className: "max-sm:sr-only",
105
+ children: dag.logsTab
106
+ })]
107
+ })
108
+ ]
109
+ }), /* @__PURE__ */ jsx("div", {
110
+ className: "px-4 py-3.5 sm:px-5",
111
+ children: /* @__PURE__ */ jsx(SummaryBody, {
112
+ runSlug,
113
+ path: near ? path : null
114
+ })
115
+ })]
116
+ });
117
+ }
118
+ function SummaryBody({ runSlug, path }) {
119
+ const { dag } = useStrings();
120
+ const { data, isLoading, error } = useRunFile(runSlug, path);
121
+ if (path === null || isLoading && data === void 0) return /* @__PURE__ */ jsx("div", {
122
+ role: "status",
123
+ "aria-label": dag.loadingSummary,
124
+ className: "flex flex-col gap-2 py-1",
125
+ children: [
126
+ "w-2/3",
127
+ "w-1/2",
128
+ "w-5/6"
129
+ ].map((width) => /* @__PURE__ */ jsx("div", { className: cx("h-3 rounded bg-(--theme-border) motion-safe:animate-pulse", width) }, width))
130
+ });
131
+ if (error || data === void 0) return /* @__PURE__ */ jsx("p", {
132
+ className: "text-sm theme-muted-text",
133
+ children: dag.summaryLoadFailed
134
+ });
135
+ return /* @__PURE__ */ jsx(Clamped, { children: /* @__PURE__ */ jsx(Markdown, {
136
+ httpsImagesOnly: true,
137
+ compact: true,
138
+ children: data
139
+ }) });
140
+ }
141
+ function useNearViewport(ref) {
142
+ const [near, setNear] = useState(typeof IntersectionObserver === "undefined");
143
+ useEffect(() => {
144
+ const el = ref.current;
145
+ if (near || !el) return;
146
+ const observer = new IntersectionObserver((entries) => {
147
+ if (entries.some((entry) => entry.isIntersecting)) setNear(true);
148
+ }, { rootMargin: "200% 0px" });
149
+ observer.observe(el);
150
+ return () => observer.disconnect();
151
+ }, [ref, near]);
152
+ return near;
153
+ }
154
+ function Clamped({ children }) {
155
+ const { common } = useStrings();
156
+ const ref = useRef(null);
157
+ const [expanded, setExpanded] = useState(false);
158
+ const [clipped, setClipped] = useState(false);
159
+ useEffect(() => {
160
+ const el = ref.current;
161
+ if (!el || expanded) return;
162
+ const measure = () => setClipped(el.scrollHeight > el.clientHeight + 1);
163
+ const observer = new ResizeObserver(measure);
164
+ observer.observe(el);
165
+ measure();
166
+ return () => observer.disconnect();
167
+ }, [expanded]);
168
+ return /* @__PURE__ */ jsxs("div", {
169
+ className: "flex flex-col gap-2",
170
+ children: [/* @__PURE__ */ jsx("div", {
171
+ ref,
172
+ className: cx("min-w-0 [overflow-wrap:anywhere] [&_table]:[overflow-wrap:normal] [&_[data-streamdown=table-wrapper]>div]:max-h-none!", !expanded && "max-h-[32rem] overflow-hidden", !expanded && clipped && "mask-[linear-gradient(to_bottom,black_75%,transparent)]"),
173
+ children
174
+ }), (clipped || expanded) && /* @__PURE__ */ jsx("button", {
175
+ type: "button",
176
+ "aria-expanded": expanded,
177
+ onClick: () => setExpanded(!expanded),
178
+ className: "link w-fit text-sm font-medium",
179
+ children: expanded ? common.showLess : common.showMore
180
+ })]
181
+ });
182
+ }
183
+ //#endregion
184
+ export { StepSummaries, stepSummarySources };
@@ -134,7 +134,7 @@ function ListSearchControl({ search, placeholder, label: labelProp }) {
134
134
  function ListCount({ total, matches, filterActive, noun, nounPlural }) {
135
135
  const { list: t } = useStrings();
136
136
  return /* @__PURE__ */ jsx("div", {
137
- className: "flex items-center h-9",
137
+ className: "flex items-center h-8",
138
138
  children: /* @__PURE__ */ jsx("span", {
139
139
  role: "status",
140
140
  className: "text-[13px] text-(--theme-muted-text-color)",
@@ -137,7 +137,7 @@
137
137
 
138
138
  /* The bordered panel graphs and settings cards sit in; graph circles hide connectors with its color. */
139
139
  @utility panel {
140
- @apply border-solid rounded-2xl border border-(--theme-border) theme-panel;
140
+ @apply border-solid rounded-xl border border-(--theme-border) theme-panel;
141
141
  }
142
142
 
143
143
  @utility theme-muted-panel {