@parallelworks/ui 0.19.0 → 0.19.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.
Files changed (217) hide show
  1. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  2. package/dist/ai/adapter/mock/index.js +2 -80
  3. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  4. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  5. package/dist/ai/adapter/openai/errors.js +37 -0
  6. package/dist/ai/adapter/openai/index.js +5 -333
  7. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  8. package/dist/ai/adapter/openai/responses.js +108 -0
  9. package/dist/ai/adapter/openai/sse.js +112 -0
  10. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  11. package/dist/ai/agent/classifier.js +12 -0
  12. package/dist/ai/agent/diff.js +223 -0
  13. package/dist/ai/agent/diffHighlight.js +101 -0
  14. package/dist/ai/agent/permissions.js +29 -0
  15. package/dist/ai/agent/toolfmt.js +383 -0
  16. package/dist/ai/agent/transcript.js +127 -0
  17. package/dist/ai/components/AllocationSelector.js +56 -0
  18. package/dist/ai/components/AttachmentManager.js +144 -0
  19. package/dist/ai/components/AttachmentUpload.js +179 -0
  20. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  21. package/dist/ai/components/BranchNavigator.js +57 -0
  22. package/dist/ai/components/ChatEmptyState.js +171 -0
  23. package/dist/ai/components/ChatInput.js +265 -0
  24. package/dist/ai/components/ChatLayout.js +15 -0
  25. package/dist/ai/components/ChatMessage.js +338 -0
  26. package/dist/ai/components/ChatMessageList.js +301 -0
  27. package/dist/ai/components/ChatSidebar.js +345 -0
  28. package/dist/ai/components/ChatThread.js +198 -0
  29. package/dist/ai/components/ComposerChrome.js +50 -0
  30. package/dist/ai/components/ComposerFrame.js +33 -0
  31. package/dist/ai/components/DragOverlay.js +20 -0
  32. package/dist/ai/components/ModelSelector.js +343 -0
  33. package/dist/ai/components/ShareDialog.js +260 -0
  34. package/dist/ai/components/SlashMenu.js +91 -0
  35. package/dist/ai/components/agent/ActivityLine.js +151 -0
  36. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  37. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  38. package/dist/ai/components/attachmentKeys.js +10 -0
  39. package/dist/ai/core/ChatProvider.js +848 -0
  40. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  41. package/dist/ai/core/chatReducer.js +223 -0
  42. package/dist/ai/core/config.js +45 -0
  43. package/dist/ai/core/events.js +4 -0
  44. package/dist/ai/core/exportConversation.js +42 -0
  45. package/dist/ai/core/greeting.js +38 -0
  46. package/dist/ai/core/parts.js +84 -0
  47. package/dist/ai/core/useDragDrop.js +36 -0
  48. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  49. package/dist/ai/core/useSSEStream.js +98 -0
  50. package/dist/ai/index.js +43 -6644
  51. package/dist/ai/strings.js +175 -0
  52. package/dist/ai/ui/Avatar.js +35 -0
  53. package/dist/ai/ui/Dropdown.js +110 -0
  54. package/dist/ai/ui/Markdown.js +79 -0
  55. package/dist/ai/ui/Notice.js +77 -0
  56. package/dist/ai/ui/codeBreaks.js +36 -0
  57. package/dist/ai/ui/prose.js +13 -0
  58. package/dist/ai/ui/sidebar.js +148 -0
  59. package/dist/ai/utils.js +23 -0
  60. package/dist/components/AddMenu.js +46 -0
  61. package/dist/components/Avatar.js +46 -0
  62. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  63. package/dist/components/Breadcrumbs.js +83 -0
  64. package/dist/components/Button.js +83 -0
  65. package/dist/components/Callout.js +23 -0
  66. package/dist/components/Card.js +31 -0
  67. package/dist/components/CollapsiblePanel.js +77 -0
  68. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  69. package/dist/components/CopyToClipboard.js +81 -0
  70. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  71. package/dist/components/DescriptionList.js +42 -0
  72. package/dist/components/Dropdown.js +349 -0
  73. package/dist/components/EmptyState.js +27 -0
  74. package/dist/components/Filter.js +32 -0
  75. package/dist/components/FilterPill.js +13 -0
  76. package/dist/components/IconButton.js +34 -0
  77. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  78. package/dist/components/Input.js +88 -0
  79. package/dist/components/LetterBadge.js +13 -0
  80. package/dist/components/Listbox.js +123 -0
  81. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  82. package/dist/components/Markdown.js +15 -0
  83. package/dist/components/MultiSelect.js +171 -0
  84. package/dist/components/Overview.js +39 -0
  85. package/dist/components/PageHeader.js +194 -0
  86. package/dist/components/Pagination.js +35 -0
  87. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +1 -1
  88. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  89. package/dist/components/SectionHeader.js +11 -0
  90. package/dist/components/SettingModeOption.js +30 -0
  91. package/dist/components/SettingRow.js +35 -0
  92. package/dist/components/SettingsCard.js +31 -0
  93. package/dist/components/SettingsGroup.js +17 -0
  94. package/dist/components/Sort.js +42 -0
  95. package/dist/components/StatusBadge.js +63 -0
  96. package/dist/components/SwitchToggle.js +59 -0
  97. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  98. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  99. package/dist/components/Toggle.js +20 -0
  100. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  101. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  102. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  103. package/dist/components/ghostButton.js +7 -0
  104. package/dist/components/inputClasses.js +4 -0
  105. package/dist/components/keys.js +27 -0
  106. package/dist/components/relativeTime.js +32 -0
  107. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  108. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  109. package/dist/editor/Editor.js +13 -0
  110. package/dist/editor/EditorField.js +53 -0
  111. package/dist/editor/FormCodePanel.js +173 -0
  112. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +4 -2
  113. package/dist/editor/index.js +6 -1
  114. package/dist/editor/themes.js +91 -0
  115. package/dist/editor/workers.js +29 -0
  116. package/dist/editor/yaml.js +15 -0
  117. package/dist/file-explorer/FileExplorer.js +1402 -0
  118. package/dist/file-explorer/FileExplorerContext.js +370 -0
  119. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  120. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  121. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  122. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  123. package/dist/file-explorer/FilePreview/index.js +266 -0
  124. package/dist/file-explorer/FilePreview/parse.js +63 -0
  125. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  126. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  127. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  128. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  129. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  130. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  131. package/dist/file-explorer/FileRow.js +165 -0
  132. package/dist/file-explorer/FileRowsWindow.js +92 -0
  133. package/dist/file-explorer/FileTable.js +172 -0
  134. package/dist/file-explorer/FileTableHeaders.js +49 -0
  135. package/dist/file-explorer/LoadMoreRow.js +46 -0
  136. package/dist/file-explorer/TreeView.js +376 -0
  137. package/dist/file-explorer/helpers.js +537 -0
  138. package/dist/file-explorer/index.js +7 -5671
  139. package/dist/file-explorer/lib/batch.js +39 -0
  140. package/dist/file-explorer/lib/errors.js +9 -0
  141. package/dist/file-explorer/lib/paging.js +124 -0
  142. package/dist/file-explorer/lib/utils.js +322 -0
  143. package/dist/file-explorer/toasts.js +400 -0
  144. package/dist/file-explorer/treeRows.js +73 -0
  145. package/dist/file-explorer/useNearEnd.js +27 -0
  146. package/dist/form/FieldWrapper.js +54 -0
  147. package/dist/form/Fields/BooleanField.js +51 -0
  148. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  149. package/dist/form/Fields/ColorField.js +33 -0
  150. package/dist/form/Fields/DropdownField.js +49 -0
  151. package/dist/form/Fields/DurationField.js +130 -0
  152. package/dist/form/Fields/LabelField.js +19 -0
  153. package/dist/form/Fields/MultiDropdownField.js +40 -0
  154. package/dist/form/Fields/NumberField.js +51 -0
  155. package/dist/form/Fields/ObjectField.js +54 -0
  156. package/dist/form/Fields/RadioField.js +49 -0
  157. package/dist/form/Fields/RangeField.js +58 -0
  158. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  159. package/dist/form/Fields/StepField.js +6 -0
  160. package/dist/form/Fields/StringField.js +37 -0
  161. package/dist/form/Fields/TextAreaField.js +31 -0
  162. package/dist/form/Fields/durationValidity.js +11 -0
  163. package/dist/form/Form.js +643 -0
  164. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  165. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  166. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  167. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  168. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  169. package/dist/form/Wizard/WizardContainer.js +138 -0
  170. package/dist/form/Wizard/WizardNavigation.js +92 -0
  171. package/dist/form/Wizard/WizardStepContent.js +35 -0
  172. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  173. package/dist/form/Wizard/useWizardState.js +118 -0
  174. package/dist/form/Wizard/utils.js +34 -0
  175. package/dist/form/fieldContext.js +26 -0
  176. package/dist/form/fieldRegistry.js +50 -0
  177. package/dist/form/index.js +17 -2321
  178. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  179. package/dist/form/useParsedOpts.js +62 -0
  180. package/dist/form/usePrevious.js +11 -0
  181. package/dist/form/utils/checkShowIfObject.js +14 -0
  182. package/dist/form/utils/duration.js +69 -0
  183. package/dist/form/utils/getParentValue.js +23 -0
  184. package/dist/form/utils/getPlaceholder.js +9 -0
  185. package/dist/form/utils/getValueUsingPath.js +23 -0
  186. package/dist/form/utils/secondaryField.js +22 -0
  187. package/dist/graph/AnnotationBanner.js +141 -0
  188. package/dist/graph/Collapse.js +41 -0
  189. package/dist/graph/DependencyGraph.js +1398 -0
  190. package/dist/graph/JobSummary.js +104 -0
  191. package/dist/graph/MatrixGroup.js +107 -0
  192. package/dist/graph/Reveal.js +57 -0
  193. package/dist/graph/TreeView.js +711 -0
  194. package/dist/graph/index.js +1 -2519
  195. package/dist/graph/types.js +6 -0
  196. package/dist/index.js +46 -599
  197. package/dist/list/ActionToolbar.js +184 -0
  198. package/dist/list/HoverCard.js +73 -0
  199. package/dist/list/ListPage.js +145 -0
  200. package/dist/list/ListPager.js +59 -0
  201. package/dist/list/ListTable.js +163 -0
  202. package/dist/list/ListViewControls.js +372 -0
  203. package/dist/list/RowContextMenu.js +350 -0
  204. package/dist/list/UserHoverCard.js +117 -0
  205. package/dist/list/index.js +11 -2
  206. package/dist/list/listSearch.js +137 -0
  207. package/dist/list/listView.js +210 -0
  208. package/dist/list/textEntry.js +10 -0
  209. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  210. package/dist/logviewer/index.js +2 -1
  211. package/dist/logviewer/segments.js +80 -0
  212. package/package.json +1 -1
  213. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  214. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  215. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  216. package/dist/chunks/list-CJm875m7.js +0 -1418
  217. /package/dist/{chunks/MarkdownImpl-CyeTVvSt.js → components/MarkdownImpl.js} +0 -0
@@ -1,2520 +1,2 @@
1
- import { c as useRunFile, d as useWorkflowEngine, l as useSlots, r as useNavigation, u as useStrings } from "../chunks/Provider-CDYurraE.js";
2
- import { ArrowLeftIcon, ArrowRightIcon, ChevronDownIcon, ChevronRightIcon, DocumentIcon, ErrorIcon, InfoIcon, LoaderIcon, OpenInNewGraphIcon, WarningIcon } from "../icons.js";
3
- import { n as TOOLTIP_ID, o as keyedByContent, r as TooltipInfo } from "../chunks/Tooltip-K5HJr4_p.js";
4
- import { n as modalPanelClasses, t as BareModal } from "../chunks/BareModal-BtRiQezg.js";
5
- import { t as Indicator } from "../chunks/Indicator-hBSIxOPK.js";
6
- import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
7
- import { t as LogViewer } from "../chunks/logviewer-BTKuQWzw.js";
8
- import { n as toAbsHumanDuration } from "../chunks/duration-BOv8Phl8.js";
9
- import cx from "classnames";
10
- import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
11
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
- import { flushSync } from "react-dom";
13
- import { DateTime } from "luxon";
14
- import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
15
- import { AnimatePresence, motion } from "framer-motion";
16
- //#region src/graph/AnnotationBanner.tsx
17
- var typeOrder = {
18
- error: 0,
19
- warning: 1,
20
- notice: 2
21
- };
22
- var typeConfig = {
23
- error: {
24
- icon: ErrorIcon,
25
- label: "Error",
26
- accentClass: "text-red-500",
27
- bgClass: "bg-red-500/5 dark:bg-red-500/10",
28
- countBg: "bg-red-500/15 text-red-700 dark:text-red-400"
29
- },
30
- warning: {
31
- icon: WarningIcon,
32
- label: "Warning",
33
- accentClass: "text-yellow-500",
34
- bgClass: "bg-yellow-500/5 dark:bg-yellow-500/10",
35
- countBg: "bg-yellow-500/15 text-yellow-700 dark:text-yellow-400"
36
- },
37
- notice: {
38
- icon: InfoIcon,
39
- label: "Notice",
40
- accentClass: "text-blue-500",
41
- bgClass: "bg-blue-500/5 dark:bg-blue-500/10",
42
- countBg: "bg-blue-500/15 text-blue-700 dark:text-blue-400"
43
- }
44
- };
45
- function collectAnnotations(executedJobs, parentPath) {
46
- const annotations = [];
47
- for (const [jobName, job] of Object.entries(executedJobs)) {
48
- const fullJobName = parentPath ? `${parentPath} > ${jobName}` : jobName;
49
- const steps = job?.steps;
50
- if (!Array.isArray(steps)) continue;
51
- for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
52
- const step = steps[stepIndex];
53
- const stepAnnotations = step?.annotations;
54
- if (Array.isArray(stepAnnotations)) {
55
- for (const annotation of stepAnnotations) if (annotation?.message) annotations.push({
56
- type: annotation.type || "notice",
57
- message: annotation.message,
58
- title: annotation.title,
59
- file: annotation.file,
60
- line: annotation.line,
61
- jobName: fullJobName,
62
- stepName: step?.name,
63
- stepIndex
64
- });
65
- }
66
- const subJobs = step?.subworkflow?.jobs;
67
- if (subJobs && typeof subJobs === "object") annotations.push(...collectAnnotations(subJobs, fullJobName));
68
- }
69
- }
70
- annotations.sort((a, b) => (typeOrder[a.type] ?? 2) - (typeOrder[b.type] ?? 2));
71
- return annotations;
72
- }
73
- function AnnotationBanner({ executedJobs }) {
74
- const [expanded, setExpanded] = useState(false);
75
- if (!executedJobs) return null;
76
- const annotations = collectAnnotations(executedJobs);
77
- if (annotations.length === 0) return null;
78
- const counts = {
79
- error: 0,
80
- warning: 0,
81
- notice: 0
82
- };
83
- for (const a of annotations) counts[a.type]++;
84
- const ChevronIcon = expanded ? ChevronDownIcon : ChevronRightIcon;
85
- return /* @__PURE__ */ jsxs("div", {
86
- className: cx("rounded-lg border border-[var(--theme-border)] overflow-hidden", "bg-[var(--theme-panel-bg)]"),
87
- children: [/* @__PURE__ */ jsxs("button", {
88
- type: "button",
89
- onClick: () => setExpanded((prev) => !prev),
90
- className: cx("w-full flex items-center gap-3 px-4 py-2.5 text-left", "hover:bg-[var(--theme-hover)] transition-colors", expanded && "border-b border-[var(--theme-border)]"),
91
- children: [
92
- /* @__PURE__ */ jsx(ChevronIcon, { className: "w-3.5 h-3.5 text-[var(--theme-muted-text-color)] shrink-0" }),
93
- /* @__PURE__ */ jsx("span", {
94
- className: "text-sm font-medium text-[var(--theme-panel-color)]",
95
- children: "Annotations"
96
- }),
97
- /* @__PURE__ */ jsx("div", {
98
- className: "flex items-center gap-1.5",
99
- children: [
100
- "error",
101
- "warning",
102
- "notice"
103
- ].map((type) => {
104
- if (!counts[type]) return null;
105
- const config = typeConfig[type];
106
- const Icon = config.icon;
107
- return /* @__PURE__ */ jsxs("span", {
108
- className: cx("inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium", config.countBg),
109
- children: [/* @__PURE__ */ jsx(Icon, { className: "w-3 h-3" }), counts[type]]
110
- }, type);
111
- })
112
- })
113
- ]
114
- }), expanded && /* @__PURE__ */ jsx("div", {
115
- className: "divide-y divide-[var(--theme-border)] max-h-[400px] overflow-y-auto",
116
- children: keyedByContent(annotations, (a) => a.id ?? `${a.type}:${a.title}:${a.message}`).map(({ key, item: annotation }) => {
117
- const config = typeConfig[annotation.type] || typeConfig.notice;
118
- const Icon = config.icon;
119
- return /* @__PURE__ */ jsxs("div", {
120
- className: cx("flex items-start gap-2.5 px-4 py-2 text-sm", config.bgClass),
121
- children: [/* @__PURE__ */ jsx(Icon, { className: cx("w-4 h-4 mt-0.5 shrink-0", config.accentClass) }), /* @__PURE__ */ jsxs("div", {
122
- className: "flex-1 min-w-0",
123
- children: [
124
- /* @__PURE__ */ jsxs("span", {
125
- className: cx("font-medium", config.accentClass),
126
- children: [annotation.title || config.label, ":"]
127
- }),
128
- " ",
129
- /* @__PURE__ */ jsx("span", {
130
- className: "text-[var(--theme-panel-color)]",
131
- children: annotation.message
132
- }),
133
- /* @__PURE__ */ jsxs("span", {
134
- className: "text-[var(--theme-muted-text-color)] ml-2 text-xs",
135
- children: [
136
- "in ",
137
- annotation.jobName,
138
- " →",
139
- " ",
140
- annotation.stepName || `step ${annotation.stepIndex}`
141
- ]
142
- })
143
- ]
144
- })]
145
- }, key);
146
- })
147
- })]
148
- });
149
- }
150
- //#endregion
151
- //#region src/graph/Collapse.tsx
152
- function Collapse({ expanded, children, releaseOverflow = true }) {
153
- const [overflowVisible, setOverflowVisible] = useState(false);
154
- return /* @__PURE__ */ jsx(AnimatePresence, {
155
- initial: false,
156
- children: expanded && /* @__PURE__ */ jsx(motion.div, {
157
- initial: {
158
- height: 0,
159
- opacity: 0
160
- },
161
- animate: {
162
- height: "auto",
163
- opacity: 1
164
- },
165
- exit: {
166
- height: 0,
167
- opacity: 0
168
- },
169
- transition: {
170
- duration: .2,
171
- ease: [
172
- .25,
173
- .1,
174
- .25,
175
- 1
176
- ]
177
- },
178
- style: { overflow: overflowVisible ? "visible" : "hidden" },
179
- onAnimationStart: () => setOverflowVisible(false),
180
- onAnimationComplete: () => {
181
- if (expanded && releaseOverflow) setOverflowVisible(true);
182
- },
183
- children
184
- }, "collapse")
185
- });
186
- }
187
- //#endregion
188
- //#region src/graph/Reveal.tsx
189
- var EASE = "cubic-bezier(0.25, 0.1, 0.25, 1)";
190
- var ROWS_TRANSITION = `grid-template-rows 200ms ${EASE}`;
191
- var FULL_TRANSITION = `${ROWS_TRANSITION}, width 200ms ${EASE}`;
192
- function Reveal({ open, children, growWidth = false, onSettled }) {
193
- const contentRef = useRef(null);
194
- const [width, setWidth] = useState(0);
195
- const [prevOpen, setPrevOpen] = useState(open);
196
- const [clipped, setClipped] = useState(!open);
197
- const [toggling, setToggling] = useState(false);
198
- const [renderChildren, setRenderChildren] = useState(open);
199
- if (open && !renderChildren) setRenderChildren(true);
200
- if (prevOpen !== open) {
201
- setPrevOpen(open);
202
- setToggling(true);
203
- setClipped(true);
204
- }
205
- useLayoutEffect(() => {
206
- if (!growWidth || !renderChildren) return;
207
- const el = contentRef.current;
208
- if (!el) return;
209
- const measure = () => setWidth(el.offsetWidth);
210
- measure();
211
- const ro = new ResizeObserver(measure);
212
- ro.observe(el);
213
- return () => ro.disconnect();
214
- }, [growWidth, renderChildren]);
215
- return /* @__PURE__ */ jsx("div", {
216
- className: "grid",
217
- style: {
218
- gridTemplateRows: open ? "1fr" : "0fr",
219
- overflow: clipped ? "hidden" : "visible",
220
- ...growWidth ? {
221
- width: open ? width : 0,
222
- transition: toggling ? FULL_TRANSITION : ROWS_TRANSITION
223
- } : { transition: ROWS_TRANSITION }
224
- },
225
- onTransitionEnd: (e) => {
226
- if (e.target === e.currentTarget && e.propertyName === "grid-template-rows") {
227
- setToggling(false);
228
- setClipped(!open);
229
- if (!open) setRenderChildren(false);
230
- onSettled?.();
231
- }
232
- },
233
- children: /* @__PURE__ */ jsx("div", {
234
- ref: contentRef,
235
- className: "min-h-0",
236
- ...growWidth ? { style: { width: "max-content" } } : {},
237
- children: renderChildren ? children : null
238
- })
239
- });
240
- }
241
- //#endregion
242
- //#region src/graph/JobSummary.tsx
243
- function UsesIcon({ uses, subworkflow }) {
244
- const navigation = useNavigation();
245
- const slots = useSlots();
246
- const { jobActions } = useStrings();
247
- if (!uses || !slots.workflowIcon) return null;
248
- const open = () => navigation.openStepSource(uses, subworkflow.sourceUrl);
249
- return /* @__PURE__ */ jsx("button", {
250
- type: "button",
251
- "aria-label": jobActions.openOriginalWorkflow,
252
- "data-tooltip-id": TOOLTIP_ID,
253
- "data-tooltip-content": jobActions.openOriginalWorkflow,
254
- onClick: open,
255
- className: "cursor-pointer",
256
- children: slots.workflowIcon({
257
- image: subworkflow.image,
258
- type: subworkflow.type,
259
- className: "ml-1.5 h-[1.2em] w-[1.2em]"
260
- })
261
- });
262
- }
263
- function Joblist(inputs) {
264
- const { jobActions } = useStrings();
265
- const engine = useWorkflowEngine();
266
- return inputs.jobNames.map((jobName) => {
267
- if (inputs.jobs[jobName]?._matrixGroup) return null;
268
- const jobStatus = inputs.jobs[jobName]?.status;
269
- const matrixMeta = inputs.jobs[jobName]?._matrix;
270
- const jobLabel = matrixMeta?.originaljob ? `${matrixMeta.originaljob[0].toUpperCase() + matrixMeta.originaljob.slice(1)} (${(matrixMeta.index ?? 0) + 1}/${matrixMeta.totalingroup ?? "?"})` : (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
271
- return /* @__PURE__ */ jsxs("div", {
272
- className: "py-1 select-none",
273
- children: [/* @__PURE__ */ jsxs("button", {
274
- type: "button",
275
- "aria-expanded": inputs.isStepsOpen(jobName),
276
- onClick: () => inputs.onJobClick(jobName),
277
- className: cx("flex items-center gap-x-1.5 w-full text-left", jobStatus === "skipped" || jobStatus === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
278
- children: [!inputs.preview && /* @__PURE__ */ jsx(Indicator, { status: inputs.jobs[jobName]?.status }), /* @__PURE__ */ jsx("div", {
279
- className: "font-medium",
280
- children: jobLabel
281
- })]
282
- }), /* @__PURE__ */ jsx(Reveal, {
283
- open: inputs.isStepsOpen(jobName),
284
- growWidth: inputs.growWidth ?? false,
285
- children: /* @__PURE__ */ jsx("div", {
286
- className: "flex flex-col gap-y-0.5 mt-0.5",
287
- children: (inputs.jobs[jobName]?.steps || []).map((step, i) => {
288
- const stepLabel = engine.stepLabel(step, i);
289
- const stepKey = `${jobName}:${i}`;
290
- const subJobs = step.subworkflow?.jobs;
291
- const canExpand = !!inputs.renderSubworkflow && !!subJobs && Object.keys(subJobs).length > 0;
292
- return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
293
- className: cx("flex items-center", step.status === "skipped" || step.status === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
294
- children: [/* @__PURE__ */ jsxs("button", {
295
- type: "button",
296
- onClick: () => inputs.onStepClick(jobName, i),
297
- className: cx(inputs.preview ? "ml-1.5" : "ml-4", "gap-x-1.5 flex items-center text-left"),
298
- "data-testid": stepLabel,
299
- children: [inputs.preview ? "↳" : /* @__PURE__ */ jsx(Indicator, { status: step.status }), /* @__PURE__ */ jsx("div", { children: stepLabel })]
300
- }), step.subworkflow && /* @__PURE__ */ jsxs(Fragment$1, { children: [
301
- inputs.onStepLogClick && /* @__PURE__ */ jsx("button", {
302
- type: "button",
303
- "aria-label": jobActions.openSubworkflowLogs,
304
- "data-tooltip-id": "global-tooltip",
305
- "data-tooltip-content": jobActions.openSubworkflowLogs,
306
- onClick: (e) => {
307
- e.stopPropagation();
308
- inputs.onStepLogClick?.(jobName, i);
309
- },
310
- className: "link ml-1",
311
- children: /* @__PURE__ */ jsx(DocumentIcon, { className: "w-[1.2em] h-[1.2em]" })
312
- }),
313
- /* @__PURE__ */ jsx(UsesIcon, {
314
- uses: step.uses,
315
- subworkflow: step.subworkflow
316
- }),
317
- inputs.onSubworkflowIcon && /* @__PURE__ */ jsx("button", {
318
- type: "button",
319
- "aria-label": jobActions.openInNewGraph,
320
- "data-tooltip-id": "global-tooltip",
321
- "data-tooltip-content": jobActions.openInNewGraph,
322
- onClick: (e) => {
323
- e.stopPropagation();
324
- inputs.onSubworkflowIcon?.(jobName, i);
325
- },
326
- className: "link ml-1",
327
- children: /* @__PURE__ */ jsx(OpenInNewGraphIcon, { className: "w-[1.2em] h-[1.2em]" })
328
- })
329
- ] })]
330
- }), canExpand && inputs.renderSubworkflow?.(jobName, i)] }, stepKey);
331
- })
332
- })
333
- })]
334
- }, "job-" + jobName);
335
- });
336
- }
337
- //#endregion
338
- //#region src/graph/MatrixGroup.tsx
339
- /** Matrix group item in the summary sidebar. */
340
- function MatrixGroupSummaryItem({ matrixGroup, status, isExpanded, onToggle, jobs, isStepsOpen, onJobClick, onStepClick, onStepLogClick, onSubworkflowIcon, isSubworkflowExpanded, renderSubworkflow, preview }) {
341
- return /* @__PURE__ */ jsxs("div", {
342
- className: "py-1.5",
343
- children: [/* @__PURE__ */ jsxs("button", {
344
- type: "button",
345
- "aria-expanded": isExpanded,
346
- onClick: onToggle,
347
- className: "cursor-pointer flex items-center gap-x-1.5 w-full text-left",
348
- children: [/* @__PURE__ */ jsx(Indicator, { status }), /* @__PURE__ */ jsxs("div", {
349
- className: "font-semibold",
350
- children: [
351
- "Matrix: ",
352
- matrixGroup.originaljob,
353
- /* @__PURE__ */ jsxs("span", {
354
- className: "font-normal text-sm ml-1",
355
- children: [
356
- "(",
357
- matrixGroup.members.length,
358
- " jobs)"
359
- ]
360
- })
361
- ]
362
- })]
363
- }), /* @__PURE__ */ jsx(Reveal, {
364
- open: isExpanded,
365
- children: /* @__PURE__ */ jsx("div", {
366
- className: "ml-3 mt-0.5",
367
- children: /* @__PURE__ */ jsx(Joblist, {
368
- jobs,
369
- jobNames: matrixGroup.members,
370
- onJobClick,
371
- isStepsOpen,
372
- onStepClick,
373
- onStepLogClick,
374
- onSubworkflowIcon,
375
- isSubworkflowExpanded,
376
- renderSubworkflow,
377
- preview
378
- })
379
- })
380
- })]
381
- }, "matrix-" + matrixGroup.originaljob);
382
- }
383
- /** Matrix group node in the DAG visualization. */
384
- function MatrixGroupNode({ matrixGroup, jobNames, idPrefix, isExpanded, onToggle, jobs, activeDists, onMouseEnter, onMouseLeave, hoveredRelated, animT, zBase, isStepsOpen, onJobClick, onStepClick, onStepLogClick, onSubworkflowIcon, isSubworkflowExpanded, renderSubworkflow, preview }) {
385
- const matrixName = matrixGroup.originaljob;
386
- const matrixMembers = matrixGroup.members;
387
- const { aggStatus, statusLabel } = useWorkflowEngine().matrixStatus(matrixMembers.map((m) => jobs[m]?.status));
388
- return /* @__PURE__ */ jsxs("div", {
389
- id: "node_" + idPrefix + jobNames[0],
390
- role: "none",
391
- className: "relative m-24",
392
- style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
393
- onMouseEnter,
394
- onMouseLeave,
395
- onFocus: onMouseEnter,
396
- onBlur: onMouseLeave,
397
- children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsxs("div", {
398
- className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
399
- style: {
400
- opacity: hoveredRelated && !hoveredRelated.has(jobNames[0]) ? .5 : 1,
401
- transition: `opacity ${animT}s`
402
- },
403
- children: [/* @__PURE__ */ jsxs("button", {
404
- type: "button",
405
- "aria-expanded": isExpanded,
406
- className: "cursor-pointer w-full text-left",
407
- onClick: onToggle,
408
- children: [/* @__PURE__ */ jsxs("div", {
409
- className: "font-semibold text-md p-0.5",
410
- children: ["Matrix: ", matrixName.length > 20 ? matrixName.slice(0, 20) + "..." : matrixName]
411
- }), !isExpanded && /* @__PURE__ */ jsxs("div", {
412
- className: "mt-1 border-t border-(--theme-border) pt-1 flex items-center gap-x-1",
413
- children: [/* @__PURE__ */ jsx(Indicator, { status: aggStatus }), /* @__PURE__ */ jsx("span", { children: statusLabel })]
414
- })]
415
- }), /* @__PURE__ */ jsx(Reveal, {
416
- open: isExpanded,
417
- growWidth: true,
418
- children: /* @__PURE__ */ jsx("div", {
419
- className: "mt-1 border-t border-(--theme-border) pt-1",
420
- children: /* @__PURE__ */ jsx(Joblist, {
421
- jobs,
422
- jobNames: matrixMembers,
423
- onJobClick,
424
- isStepsOpen,
425
- onStepClick,
426
- onStepLogClick,
427
- onSubworkflowIcon,
428
- isSubworkflowExpanded,
429
- renderSubworkflow,
430
- growWidth: true,
431
- preview
432
- })
433
- })
434
- })]
435
- })]
436
- }, "node_" + jobNames[0]);
437
- }
438
- //#endregion
439
- //#region src/graph/TreeView.tsx
440
- function getStepDuration(step) {
441
- if (!step.startedAt) return "";
442
- const start = DateTime.fromISO(step.startedAt);
443
- const end = step.completedAt ? DateTime.fromISO(step.completedAt) : DateTime.now();
444
- return toAbsHumanDuration(start, end);
445
- }
446
- function getJobDuration(job) {
447
- if (!job.startedAt) return "";
448
- const start = DateTime.fromISO(job.startedAt);
449
- const end = job.completedAt ? DateTime.fromISO(job.completedAt) : DateTime.now();
450
- return toAbsHumanDuration(start, end);
451
- }
452
- function formatJobLabel(jobName, job) {
453
- const matrix = job?._matrix;
454
- if (matrix?.originaljob) return `${matrix.originaljob[0].toUpperCase() + matrix.originaljob.slice(1)} (${(matrix.index ?? 0) + 1}/${matrix.totalingroup ?? "?"})`;
455
- return (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
456
- }
457
- function InlineLogViewer({ slug, jobName, stepIndex }) {
458
- const logPath = useWorkflowEngine().stepLogPath("", jobName, stepIndex);
459
- const { data: logs, isLoading, error } = useRunFile(slug, logPath, { refreshInterval: 2e3 });
460
- if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
461
- className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
462
- children: [/* @__PURE__ */ jsx(LoaderIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: "Loading logs..." })]
463
- });
464
- if (error || !logs) return /* @__PURE__ */ jsx("div", {
465
- className: "px-5 py-4 text-sm text-[var(--theme-muted-text-color)] italic",
466
- children: "No logs available"
467
- });
468
- return /* @__PURE__ */ jsx("div", {
469
- className: "border-t border-[var(--theme-border)] ",
470
- children: /* @__PURE__ */ jsx(LogViewer, {
471
- log: logs,
472
- width: "100%",
473
- height: "unset",
474
- inline: true,
475
- contentClassName: "bg-[var(--theme-app-bg)]",
476
- enableWorkflowCommands: true
477
- })
478
- });
479
- }
480
- function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName, runNumber, slug, expandedJobs, onJobToggle, expandedSteps, onStepToggle }) {
481
- const hasSteps = job.steps && job.steps.length > 0;
482
- const duration = getJobDuration(job);
483
- const panelId = useId();
484
- return /* @__PURE__ */ jsxs("div", {
485
- className: "border-b border-[var(--theme-border)] last:border-b-0",
486
- children: [/* @__PURE__ */ jsxs("button", {
487
- type: "button",
488
- "aria-expanded": isExpanded,
489
- "aria-controls": hasSteps ? panelId : void 0,
490
- className: cx("flex items-center gap-3 px-5 py-3 transition-colors w-full text-left", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
491
- onClick: onToggle,
492
- children: [
493
- /* @__PURE__ */ jsx(motion.div, {
494
- animate: { rotate: isExpanded ? 90 : 0 },
495
- transition: { duration: .15 },
496
- className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
497
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
498
- }),
499
- /* @__PURE__ */ jsx(Indicator, { status: job.status }),
500
- /* @__PURE__ */ jsxs("div", {
501
- className: "flex flex-col gap-1 min-w-0 flex-1",
502
- children: [/* @__PURE__ */ jsx("span", {
503
- className: "font-mono text-sm text-[var(--theme-app)] truncate",
504
- children: formatJobLabel(jobName, job)
505
- }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
506
- className: "flex items-center gap-1.5",
507
- children: [/* @__PURE__ */ jsx("span", {
508
- className: "text-[10px] text-[var(--theme-muted-text-color)]",
509
- children: "depends on:"
510
- }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
511
- className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
512
- children: formatJobLabel(dep)
513
- }, dep))]
514
- })]
515
- }),
516
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
517
- duration && /* @__PURE__ */ jsx("span", {
518
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
519
- children: duration
520
- })
521
- ]
522
- }), /* @__PURE__ */ jsx(AnimatePresence, {
523
- initial: false,
524
- children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
525
- initial: {
526
- height: 0,
527
- opacity: 0
528
- },
529
- animate: {
530
- height: "auto",
531
- opacity: 1
532
- },
533
- exit: {
534
- height: 0,
535
- opacity: 0
536
- },
537
- transition: {
538
- duration: .2,
539
- ease: [
540
- .25,
541
- .1,
542
- .25,
543
- 1
544
- ]
545
- },
546
- className: "overflow-hidden",
547
- id: panelId,
548
- children: /* @__PURE__ */ jsx("div", {
549
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
550
- children: job.steps.map((step, index) => {
551
- const stepKey = `${jobName}:${index}`;
552
- const isStepExpanded = expandedSteps.has(stepKey);
553
- return /* @__PURE__ */ jsx(StepRow, {
554
- step,
555
- index,
556
- isExpanded: isStepExpanded,
557
- onToggle: () => onStepToggle(stepKey),
558
- workflowName,
559
- runNumber,
560
- slug,
561
- jobName,
562
- expandedJobs,
563
- onJobToggle,
564
- expandedSteps,
565
- onStepToggle
566
- }, stepKey);
567
- })
568
- })
569
- })
570
- })]
571
- });
572
- }
573
- function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, slug, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth = 0 }) {
574
- const engine = useWorkflowEngine();
575
- const slots = useSlots();
576
- const duration = getStepDuration(step);
577
- const label = engine.stepLabel(step, index);
578
- const isSubworkflow = !!step.subworkflow;
579
- const panelId = useId();
580
- const subworkflowJobs = step.subworkflow?.jobs ?? null;
581
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
582
- const subworkflowDependenciesMap = useMemo(() => {
583
- if (!subworkflowJobs) return {};
584
- const map = {};
585
- for (const subJobName of subworkflowJobNames) {
586
- const job = subworkflowJobs[subJobName];
587
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
588
- }
589
- return map;
590
- }, [subworkflowJobs, subworkflowJobNames]);
591
- return /* @__PURE__ */ jsxs("div", { children: [
592
- /* @__PURE__ */ jsxs("button", {
593
- type: "button",
594
- "aria-expanded": isExpanded,
595
- "aria-controls": panelId,
596
- className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors w-full text-left", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
597
- onClick: onToggle,
598
- children: [
599
- /* @__PURE__ */ jsx(motion.div, {
600
- animate: { rotate: isExpanded ? 90 : 0 },
601
- transition: { duration: .15 },
602
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
603
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
604
- }),
605
- /* @__PURE__ */ jsx(Indicator, { status: step.status }),
606
- isSubworkflow && slots.workflowIcon?.({
607
- image: step.subworkflow?.image,
608
- type: step.subworkflow?.type,
609
- className: "h-4 w-4 flex-shrink-0"
610
- }),
611
- /* @__PURE__ */ jsxs("span", {
612
- className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
613
- children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
614
- className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
615
- children: "(subworkflow)"
616
- })]
617
- }),
618
- duration && /* @__PURE__ */ jsx("span", {
619
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
620
- children: duration
621
- })
622
- ]
623
- }),
624
- /* @__PURE__ */ jsx(AnimatePresence, {
625
- initial: false,
626
- children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
627
- initial: {
628
- height: 0,
629
- opacity: 0
630
- },
631
- animate: {
632
- height: "auto",
633
- opacity: 1
634
- },
635
- exit: {
636
- height: 0,
637
- opacity: 0
638
- },
639
- transition: {
640
- duration: .2,
641
- ease: [
642
- .25,
643
- .1,
644
- .25,
645
- 1
646
- ]
647
- },
648
- className: "overflow-hidden",
649
- id: panelId,
650
- children: /* @__PURE__ */ jsx(InlineLogViewer, {
651
- slug,
652
- jobName,
653
- stepIndex: index
654
- })
655
- })
656
- }),
657
- /* @__PURE__ */ jsx(AnimatePresence, {
658
- initial: false,
659
- children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
660
- initial: {
661
- height: 0,
662
- opacity: 0
663
- },
664
- animate: {
665
- height: "auto",
666
- opacity: 1
667
- },
668
- exit: {
669
- height: 0,
670
- opacity: 0
671
- },
672
- transition: {
673
- duration: .2,
674
- ease: [
675
- .25,
676
- .1,
677
- .25,
678
- 1
679
- ]
680
- },
681
- className: "overflow-hidden",
682
- id: panelId,
683
- children: /* @__PURE__ */ jsx("div", {
684
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
685
- children: subworkflowJobNames.map((subJobName) => {
686
- const subJob = subworkflowJobs?.[subJobName];
687
- if (!subJob) return null;
688
- const subJobKey = `${jobName}:${index}:${subJobName}`;
689
- const isSubJobExpanded = expandedJobs.has(subJobKey);
690
- return /* @__PURE__ */ jsx(SubworkflowJobRow, {
691
- jobName: subJobName,
692
- job: subJob,
693
- isExpanded: isSubJobExpanded,
694
- onToggle: () => onJobToggle(subJobKey),
695
- dependencies: subworkflowDependenciesMap[subJobName],
696
- slug,
697
- parentJobName: jobName,
698
- parentStepIndex: index,
699
- expandedJobs,
700
- onJobToggle,
701
- expandedSteps,
702
- onStepToggle,
703
- depth: depth + 1
704
- }, subJobName);
705
- })
706
- })
707
- })
708
- })
709
- ] });
710
- }
711
- function SubworkflowJobRow({ jobName, job, isExpanded, onToggle, dependencies, slug, parentJobName, parentStepIndex, subworkflowPrefix, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
712
- const hasSteps = job.steps && job.steps.length > 0;
713
- const duration = getJobDuration(job);
714
- const nestedSubworkflowPrefix = `${subworkflowPrefix || ""}subworkflows/${parentJobName}/step_${parentStepIndex}/`;
715
- const panelId = useId();
716
- return /* @__PURE__ */ jsxs("div", {
717
- className: "border-b border-[var(--theme-border)] last:border-b-0",
718
- children: [/* @__PURE__ */ jsxs("button", {
719
- type: "button",
720
- "aria-expanded": isExpanded,
721
- "aria-controls": hasSteps ? panelId : void 0,
722
- className: cx("flex items-center gap-3 px-5 py-3 transition-colors w-full text-left", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
723
- onClick: onToggle,
724
- children: [
725
- /* @__PURE__ */ jsx(motion.div, {
726
- animate: { rotate: isExpanded ? 90 : 0 },
727
- transition: { duration: .15 },
728
- className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
729
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
730
- }),
731
- /* @__PURE__ */ jsx(Indicator, { status: job.status }),
732
- /* @__PURE__ */ jsxs("div", {
733
- className: "flex flex-col gap-1 min-w-0 flex-1",
734
- children: [/* @__PURE__ */ jsx("span", {
735
- className: "font-mono text-sm text-[var(--theme-app)] truncate",
736
- children: formatJobLabel(jobName, job)
737
- }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
738
- className: "flex items-center gap-1.5",
739
- children: [/* @__PURE__ */ jsx("span", {
740
- className: "text-[10px] text-[var(--theme-muted-text-color)]",
741
- children: "depends on:"
742
- }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
743
- className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
744
- children: formatJobLabel(dep)
745
- }, dep))]
746
- })]
747
- }),
748
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
749
- duration && /* @__PURE__ */ jsx("span", {
750
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
751
- children: duration
752
- })
753
- ]
754
- }), /* @__PURE__ */ jsx(AnimatePresence, {
755
- initial: false,
756
- children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
757
- initial: {
758
- height: 0,
759
- opacity: 0
760
- },
761
- animate: {
762
- height: "auto",
763
- opacity: 1
764
- },
765
- exit: {
766
- height: 0,
767
- opacity: 0
768
- },
769
- transition: {
770
- duration: .2,
771
- ease: [
772
- .25,
773
- .1,
774
- .25,
775
- 1
776
- ]
777
- },
778
- className: "overflow-hidden",
779
- id: panelId,
780
- children: /* @__PURE__ */ jsx("div", {
781
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
782
- children: job.steps.map((step, index) => {
783
- const stepKey = `${nestedSubworkflowPrefix}${jobName}:${index}`;
784
- const isStepExpanded = expandedSteps.has(stepKey);
785
- return /* @__PURE__ */ jsx(SubworkflowStepRow, {
786
- step,
787
- index,
788
- isExpanded: isStepExpanded,
789
- onToggle: () => onStepToggle(stepKey),
790
- slug,
791
- subworkflowPrefix: nestedSubworkflowPrefix,
792
- jobName,
793
- expandedJobs,
794
- onJobToggle,
795
- expandedSteps,
796
- onStepToggle,
797
- depth
798
- }, stepKey);
799
- })
800
- })
801
- })
802
- })]
803
- });
804
- }
805
- function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkflowPrefix, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
806
- const engine = useWorkflowEngine();
807
- const slots = useSlots();
808
- const duration = getStepDuration(step);
809
- const label = engine.stepLabel(step, index);
810
- const isSubworkflow = !!step.subworkflow;
811
- const panelId = useId();
812
- const subworkflowJobs = step.subworkflow?.jobs ?? null;
813
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
814
- const subworkflowDependenciesMap = useMemo(() => {
815
- if (!subworkflowJobs) return {};
816
- const map = {};
817
- for (const subJobName of subworkflowJobNames) {
818
- const job = subworkflowJobs[subJobName];
819
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
820
- }
821
- return map;
822
- }, [subworkflowJobs, subworkflowJobNames]);
823
- return /* @__PURE__ */ jsxs("div", { children: [
824
- /* @__PURE__ */ jsxs("button", {
825
- type: "button",
826
- "aria-expanded": isExpanded,
827
- "aria-controls": panelId,
828
- className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors w-full text-left", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
829
- onClick: onToggle,
830
- children: [
831
- /* @__PURE__ */ jsx(motion.div, {
832
- animate: { rotate: isExpanded ? 90 : 0 },
833
- transition: { duration: .15 },
834
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
835
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
836
- }),
837
- /* @__PURE__ */ jsx(Indicator, { status: step.status }),
838
- isSubworkflow && slots.workflowIcon?.({
839
- image: step.subworkflow?.image,
840
- type: step.subworkflow?.type,
841
- className: "h-4 w-4 flex-shrink-0"
842
- }),
843
- /* @__PURE__ */ jsxs("span", {
844
- className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
845
- children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
846
- className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
847
- children: "(subworkflow)"
848
- })]
849
- }),
850
- duration && /* @__PURE__ */ jsx("span", {
851
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
852
- children: duration
853
- })
854
- ]
855
- }),
856
- /* @__PURE__ */ jsx(AnimatePresence, {
857
- initial: false,
858
- children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
859
- initial: {
860
- height: 0,
861
- opacity: 0
862
- },
863
- animate: {
864
- height: "auto",
865
- opacity: 1
866
- },
867
- exit: {
868
- height: 0,
869
- opacity: 0
870
- },
871
- transition: {
872
- duration: .2,
873
- ease: [
874
- .25,
875
- .1,
876
- .25,
877
- 1
878
- ]
879
- },
880
- className: "overflow-hidden",
881
- id: panelId,
882
- children: /* @__PURE__ */ jsx(SubworkflowInlineLogViewer, {
883
- slug,
884
- subworkflowPrefix,
885
- jobName,
886
- stepIndex: index
887
- })
888
- })
889
- }),
890
- /* @__PURE__ */ jsx(AnimatePresence, {
891
- initial: false,
892
- children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
893
- initial: {
894
- height: 0,
895
- opacity: 0
896
- },
897
- animate: {
898
- height: "auto",
899
- opacity: 1
900
- },
901
- exit: {
902
- height: 0,
903
- opacity: 0
904
- },
905
- transition: {
906
- duration: .2,
907
- ease: [
908
- .25,
909
- .1,
910
- .25,
911
- 1
912
- ]
913
- },
914
- className: "overflow-hidden",
915
- id: panelId,
916
- children: /* @__PURE__ */ jsx("div", {
917
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
918
- children: subworkflowJobNames.map((subJobName) => {
919
- const subJob = subworkflowJobs?.[subJobName];
920
- if (!subJob) return null;
921
- const subJobKey = `${subworkflowPrefix}${jobName}:${index}:${subJobName}`;
922
- const isSubJobExpanded = expandedJobs.has(subJobKey);
923
- return /* @__PURE__ */ jsx(SubworkflowJobRow, {
924
- jobName: subJobName,
925
- job: subJob,
926
- isExpanded: isSubJobExpanded,
927
- onToggle: () => onJobToggle(subJobKey),
928
- dependencies: subworkflowDependenciesMap[subJobName],
929
- slug,
930
- parentJobName: jobName,
931
- parentStepIndex: index,
932
- subworkflowPrefix,
933
- expandedJobs,
934
- onJobToggle,
935
- expandedSteps,
936
- onStepToggle,
937
- depth: depth + 1
938
- }, subJobName);
939
- })
940
- })
941
- })
942
- })
943
- ] });
944
- }
945
- function SubworkflowInlineLogViewer({ slug, subworkflowPrefix, jobName, stepIndex }) {
946
- const logPath = useWorkflowEngine().stepLogPath(subworkflowPrefix, jobName, stepIndex);
947
- const { data: logs, isLoading, error } = useRunFile(slug, logPath, { refreshInterval: 2e3 });
948
- if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
949
- className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
950
- children: [/* @__PURE__ */ jsx(LoaderIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: "Loading logs..." })]
951
- });
952
- if (error || !logs) return /* @__PURE__ */ jsx("div", {
953
- className: "px-5 py-4 text-sm text-[var(--theme-muted-text-color)] italic",
954
- children: "No logs available"
955
- });
956
- return /* @__PURE__ */ jsx("div", {
957
- className: "border-t border-[var(--theme-border)]",
958
- children: /* @__PURE__ */ jsx(LogViewer, {
959
- log: logs,
960
- width: "100%",
961
- height: "unset",
962
- inline: true,
963
- contentClassName: "bg-[var(--theme-app-bg)]",
964
- enableWorkflowCommands: true
965
- })
966
- });
967
- }
968
- function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpandedJobs }) {
969
- const engine = useWorkflowEngine();
970
- const visibleJobNames = useMemo(() => {
971
- return Object.keys(jobs).filter((jobName) => jobs[jobName]?.if !== false);
972
- }, [jobs]);
973
- const [expandedSteps, setExpandedSteps] = useState(/* @__PURE__ */ new Set());
974
- const [expandedSubworkflowJobs, setExpandedSubworkflowJobs] = useState(/* @__PURE__ */ new Set());
975
- const toggleJob = useCallback((jobName) => {
976
- const newExpanded = new Set(expandedJobs);
977
- if (newExpanded.has(jobName)) newExpanded.delete(jobName);
978
- else {
979
- const status = jobs[jobName]?.status;
980
- if (status === "skipped" || status === "skipped-failed") return;
981
- newExpanded.add(jobName);
982
- }
983
- setExpandedJobs(newExpanded);
984
- }, [
985
- expandedJobs,
986
- setExpandedJobs,
987
- jobs
988
- ]);
989
- const toggleSubworkflowJob = useCallback((jobKey) => {
990
- setExpandedSubworkflowJobs((prev) => {
991
- const newSet = new Set(prev);
992
- if (newSet.has(jobKey)) newSet.delete(jobKey);
993
- else newSet.add(jobKey);
994
- return newSet;
995
- });
996
- }, []);
997
- const toggleStep = useCallback((stepKey) => {
998
- setExpandedSteps((prev) => {
999
- const newSet = new Set(prev);
1000
- if (newSet.has(stepKey)) newSet.delete(stepKey);
1001
- else newSet.add(stepKey);
1002
- return newSet;
1003
- });
1004
- }, []);
1005
- const expandAll = useCallback(() => {
1006
- setExpandedJobs(new Set(visibleJobNames));
1007
- }, [visibleJobNames, setExpandedJobs]);
1008
- const collapseAll = useCallback(() => {
1009
- setExpandedJobs(/* @__PURE__ */ new Set());
1010
- setExpandedSteps(/* @__PURE__ */ new Set());
1011
- setExpandedSubworkflowJobs(/* @__PURE__ */ new Set());
1012
- }, [setExpandedJobs]);
1013
- const allExpanded = expandedJobs.size === visibleJobNames.length;
1014
- const dependenciesMap = useMemo(() => {
1015
- const map = {};
1016
- for (const jobName of visibleJobNames) {
1017
- const job = jobs[jobName];
1018
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[jobName] = job.needs;
1019
- }
1020
- return map;
1021
- }, [jobs, visibleJobNames]);
1022
- return /* @__PURE__ */ jsxs("div", {
1023
- className: "flex flex-col",
1024
- children: [/* @__PURE__ */ jsx("div", {
1025
- className: "flex justify-end px-4 py-2 border-b border-[var(--theme-border)]",
1026
- children: /* @__PURE__ */ jsx("button", {
1027
- type: "button",
1028
- onClick: allExpanded ? collapseAll : expandAll,
1029
- className: "text-xs font-mono text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] transition-colors px-3 py-1.5 border border-[var(--theme-border)] rounded",
1030
- children: allExpanded ? "Collapse all" : "Expand all"
1031
- })
1032
- }), /* @__PURE__ */ jsx("div", { children: (() => {
1033
- const matrixGroups = {};
1034
- for (const jobName of visibleJobNames) {
1035
- const matrix = jobs[jobName]?._matrix;
1036
- if (matrix?.originaljob) {
1037
- let group = matrixGroups[matrix.originaljob];
1038
- if (!group) {
1039
- group = {
1040
- originaljob: matrix.originaljob,
1041
- members: []
1042
- };
1043
- matrixGroups[matrix.originaljob] = group;
1044
- }
1045
- group.members.push(jobName);
1046
- }
1047
- }
1048
- for (const group of Object.values(matrixGroups)) group.members.sort((a, b) => (jobs[a]?._matrix?.index ?? 0) - (jobs[b]?._matrix?.index ?? 0));
1049
- const rendered = [];
1050
- const seenMatrixGroups = /* @__PURE__ */ new Set();
1051
- for (const jobName of visibleJobNames) {
1052
- const matrixOrigin = jobs[jobName]?._matrix?.originaljob;
1053
- if (matrixOrigin) {
1054
- if (seenMatrixGroups.has(matrixOrigin)) continue;
1055
- seenMatrixGroups.add(matrixOrigin);
1056
- const group = matrixGroups[matrixOrigin];
1057
- if (!group) continue;
1058
- const isGroupExpanded = expandedJobs.has(`matrix:${matrixOrigin}`);
1059
- const { aggStatus } = engine.matrixStatus(group.members.map((m) => jobs[m]?.status));
1060
- rendered.push(/* @__PURE__ */ jsxs("div", {
1061
- className: "border-b border-[var(--theme-border)]",
1062
- children: [/* @__PURE__ */ jsxs("button", {
1063
- type: "button",
1064
- "aria-expanded": isGroupExpanded,
1065
- "aria-controls": `matrix-panel-${matrixOrigin}`,
1066
- className: "flex items-center gap-3 px-5 py-3 cursor-pointer transition-colors w-full text-left",
1067
- onClick: () => toggleJob(`matrix:${matrixOrigin}`),
1068
- children: [
1069
- /* @__PURE__ */ jsx(motion.div, {
1070
- animate: { rotate: isGroupExpanded ? 90 : 0 },
1071
- transition: { duration: .15 },
1072
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
1073
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isGroupExpanded && "text-[var(--theme-element)]") })
1074
- }),
1075
- /* @__PURE__ */ jsx(Indicator, { status: aggStatus }),
1076
- /* @__PURE__ */ jsxs("span", {
1077
- className: "font-mono text-sm text-[var(--theme-app)]",
1078
- children: [
1079
- "Matrix: ",
1080
- matrixOrigin,
1081
- " (",
1082
- group.members.length,
1083
- " jobs)"
1084
- ]
1085
- })
1086
- ]
1087
- }), /* @__PURE__ */ jsx(AnimatePresence, { children: isGroupExpanded && /* @__PURE__ */ jsx(motion.div, {
1088
- initial: {
1089
- height: 0,
1090
- opacity: 0
1091
- },
1092
- animate: {
1093
- height: "auto",
1094
- opacity: 1
1095
- },
1096
- exit: {
1097
- height: 0,
1098
- opacity: 0
1099
- },
1100
- transition: { duration: .15 },
1101
- className: "overflow-hidden pl-4",
1102
- id: `matrix-panel-${matrixOrigin}`,
1103
- children: group.members.map((memberName) => /* @__PURE__ */ jsx(JobRow, {
1104
- jobName: memberName,
1105
- job: jobs[memberName],
1106
- isExpanded: expandedJobs.has(memberName),
1107
- onToggle: () => toggleJob(memberName),
1108
- dependencies: dependenciesMap[memberName],
1109
- workflowName,
1110
- runNumber,
1111
- slug,
1112
- expandedJobs: expandedSubworkflowJobs,
1113
- onJobToggle: toggleSubworkflowJob,
1114
- expandedSteps,
1115
- onStepToggle: toggleStep
1116
- }, memberName))
1117
- }) })]
1118
- }, `matrix-${matrixOrigin}`));
1119
- } else rendered.push(/* @__PURE__ */ jsx(JobRow, {
1120
- jobName,
1121
- job: jobs[jobName],
1122
- isExpanded: expandedJobs.has(jobName),
1123
- onToggle: () => toggleJob(jobName),
1124
- dependencies: dependenciesMap[jobName],
1125
- workflowName,
1126
- runNumber,
1127
- slug,
1128
- expandedJobs: expandedSubworkflowJobs,
1129
- onJobToggle: toggleSubworkflowJob,
1130
- expandedSteps,
1131
- onStepToggle: toggleStep
1132
- }, jobName));
1133
- }
1134
- return rendered;
1135
- })() })]
1136
- });
1137
- }
1138
- //#endregion
1139
- //#region src/graph/types.ts
1140
- function isJobRecord(value) {
1141
- return typeof value === "object" && value !== null;
1142
- }
1143
- //#endregion
1144
- //#region src/graph/DependencyGraph.tsx
1145
- function ViewToggle({ viewMode, setViewMode }) {
1146
- return /* @__PURE__ */ jsxs("div", {
1147
- className: "flex border border-[var(--theme-border)] rounded overflow-hidden",
1148
- children: [/* @__PURE__ */ jsx("button", {
1149
- type: "button",
1150
- onClick: () => setViewMode("dag"),
1151
- className: cx("px-3 py-1.5 text-xs font-mono transition-colors", viewMode === "dag" ? "bg-[var(--theme-element)] text-[var(--theme-element-text)]" : "text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] hover:bg-[var(--theme-hover)]"),
1152
- children: "DAG"
1153
- }), /* @__PURE__ */ jsx("button", {
1154
- type: "button",
1155
- onClick: () => setViewMode("tree"),
1156
- className: cx("px-3 py-1.5 text-xs font-mono transition-colors", viewMode === "tree" ? "bg-[var(--theme-element)] text-[var(--theme-element-text)]" : "text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] hover:bg-[var(--theme-hover)]"),
1157
- children: "Tree"
1158
- })]
1159
- });
1160
- }
1161
- var addCleanupSteps = ({ jobs }) => {
1162
- const clonedJobs = structuredClone(jobs);
1163
- for (const job in jobs) {
1164
- const source = jobs[job];
1165
- const cloned = clonedJobs[job];
1166
- if (!source || !cloned) continue;
1167
- if (Array.isArray(source.steps)) {
1168
- const sourceSteps = source.steps;
1169
- sourceSteps.slice().reverse().forEach((step, index) => {
1170
- if (!step.cleanup) return;
1171
- const linkedName = step.name ? step.name : `Step ${sourceSteps.length - index - 1}`;
1172
- const newStep = {
1173
- ...step,
1174
- name: `POST ${linkedName}`,
1175
- run: step.cleanup,
1176
- linkedStep: `${sourceSteps.length - index - 1}`
1177
- };
1178
- delete newStep.cleanup;
1179
- const clonedStep = cloned.steps[index];
1180
- if (clonedStep) delete clonedStep.cleanup;
1181
- cloned.steps.push(newStep);
1182
- });
1183
- }
1184
- const sourceCleanup = source.cleanup;
1185
- const clonedCleanup = cloned.cleanup;
1186
- if (Array.isArray(sourceCleanup) && clonedCleanup) {
1187
- sourceCleanup.slice().reverse().forEach((step, index) => {
1188
- if (!step.cleanup) return;
1189
- const linkedName = step.name ? step.name : `Cleanup ${sourceCleanup.length - index - 1}`;
1190
- const newStep = {
1191
- ...step,
1192
- name: `POST ${linkedName}`,
1193
- run: step.cleanup,
1194
- linkedStep: `${sourceCleanup.length - index - 1 + cloned.steps.length}`
1195
- };
1196
- delete newStep.cleanup;
1197
- const clonedStep = clonedCleanup[index];
1198
- if (clonedStep) delete clonedStep.cleanup;
1199
- clonedCleanup.push(newStep);
1200
- });
1201
- for (const clonedStep of clonedCleanup) clonedStep.linkedStep = ".";
1202
- cloned.steps = [...cloned.steps, ...clonedCleanup];
1203
- delete cloned.cleanup;
1204
- }
1205
- }
1206
- return clonedJobs;
1207
- };
1208
- var PREVIEW_PADDING = 24;
1209
- function measurePreviewHeight(content, viewRef) {
1210
- const nodes = content?.querySelectorAll("[id^=\"node_\"]");
1211
- const transform = content?.parentElement ? getComputedStyle(content.parentElement).transform : "none";
1212
- if (!content || !nodes?.length || transform === "none") return null;
1213
- if (!viewRef.current) {
1214
- const view = new DOMMatrixReadOnly(transform);
1215
- viewRef.current = {
1216
- scale: view.a,
1217
- offsetY: view.f
1218
- };
1219
- }
1220
- const { scale, offsetY } = viewRef.current;
1221
- let bottom = 0;
1222
- for (const node of nodes) bottom = Math.max(bottom, offsetWithin(node, content).y + node.offsetHeight);
1223
- return Math.ceil(bottom * scale + offsetY + PREVIEW_PADDING);
1224
- }
1225
- function offsetWithin(el, ancestor) {
1226
- let x = 0;
1227
- let y = 0;
1228
- let cur = el;
1229
- while (cur && cur !== ancestor) {
1230
- x += cur.offsetLeft;
1231
- y += cur.offsetTop;
1232
- cur = cur.offsetParent;
1233
- }
1234
- return {
1235
- x,
1236
- y
1237
- };
1238
- }
1239
- function parsePrefixToPath(prefix) {
1240
- const parts = prefix.split("/").filter(Boolean);
1241
- const path = [];
1242
- for (let i = 0; i + 2 < parts.length; i += 3) {
1243
- const name = parts[i + 1];
1244
- const step = parts[i + 2];
1245
- if (parts[i] === "subworkflows" && name && step?.startsWith("step_")) path.push(name, "steps", Number(step.slice(5)), "subworkflow", "jobs");
1246
- }
1247
- return path;
1248
- }
1249
- function computeGraphLayout(engine, jobs, matrixGroups) {
1250
- const visibleJobs = structuredClone(jobs);
1251
- for (const jobName of Object.keys(jobs ?? {})) if (jobs[jobName]?.if === false) delete visibleJobs[jobName];
1252
- engine.collapseMatrixGroups(matrixGroups, visibleJobs);
1253
- const allDeps = {};
1254
- for (const jobName of Object.keys(visibleJobs)) allDeps[jobName] = engine.jobDependencies(jobName, visibleJobs, allDeps);
1255
- const filteredDeps = {};
1256
- for (const jobName of Object.keys(visibleJobs)) {
1257
- const needs = visibleJobs[jobName]?.needs;
1258
- const oldNeeds = Array.isArray(needs) ? needs : [];
1259
- filteredDeps[jobName] = oldNeeds.filter((dep) => {
1260
- return !oldNeeds.some((other) => {
1261
- if (other === dep) return false;
1262
- return allDeps[other]?.has(dep) === true;
1263
- });
1264
- }).sort();
1265
- }
1266
- const depended_on = {};
1267
- Object.keys(visibleJobs).forEach((jobName) => {
1268
- if (filteredDeps[jobName] && Array.isArray(filteredDeps[jobName])) filteredDeps[jobName].forEach((dep) => {
1269
- if (depended_on[dep]) depended_on[dep].push(jobName);
1270
- else depended_on[dep] = [jobName];
1271
- });
1272
- });
1273
- Object.keys(visibleJobs).forEach((jobName) => {
1274
- if (depended_on[jobName]) depended_on[jobName].sort();
1275
- });
1276
- const unfinished = structuredClone(visibleJobs);
1277
- const rootLevel = [];
1278
- const deps = [rootLevel];
1279
- Object.keys(visibleJobs).forEach((jobName) => {
1280
- if (filteredDeps[jobName]?.length === 0) {
1281
- rootLevel.push(jobName);
1282
- delete unfinished[jobName];
1283
- }
1284
- });
1285
- let prevLen;
1286
- do {
1287
- const toAppend = [];
1288
- const unfinishedArr = Object.keys(unfinished);
1289
- prevLen = unfinishedArr.length;
1290
- unfinishedArr.forEach((jobName) => {
1291
- if ((filteredDeps[jobName] ?? []).every((dep) => !unfinishedArr.includes(dep))) {
1292
- toAppend.push(jobName);
1293
- delete unfinished[jobName];
1294
- }
1295
- });
1296
- if (toAppend.length > 0) deps.push(toAppend);
1297
- } while (prevLen !== Object.keys(unfinished).length);
1298
- const finalDeps = [];
1299
- deps.forEach((col) => {
1300
- const toAppend = [];
1301
- while (col.length > 0) {
1302
- const head = col[0];
1303
- if (head === void 0) break;
1304
- const grouped = [head];
1305
- const toRm = [0];
1306
- col.slice(1).forEach((jobName, j) => {
1307
- if (visibleJobs[head]?._matrixGroup || visibleJobs[jobName]?._matrixGroup) return;
1308
- const depsA = depended_on[head];
1309
- const depsB = depended_on[jobName];
1310
- const depsA2 = filteredDeps[head];
1311
- const depsB2 = filteredDeps[jobName];
1312
- if (!depsA && !depsB || depsA && depsB && depsA.length === depsB.length) {
1313
- if (!depsA2 && !depsB2 || depsA2 && depsB2 && depsA2.length === depsB2.length) {
1314
- if ((!depsA || depsA.every((dep, i) => dep === depsB?.[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2?.[i]))) {
1315
- grouped.push(jobName);
1316
- toRm.push(j + 1);
1317
- }
1318
- }
1319
- }
1320
- });
1321
- col.splice(0, col.length, ...col.filter((_, i) => !toRm.includes(i)));
1322
- toAppend.push(grouped);
1323
- }
1324
- finalDeps.push(toAppend);
1325
- });
1326
- while (finalDeps.length > 0) {
1327
- const first = finalDeps[0];
1328
- if (!first) break;
1329
- if (first.length === 0) {
1330
- finalDeps.splice(0, 1);
1331
- continue;
1332
- }
1333
- if (first[0]?.length !== 0) break;
1334
- while (first.length > 0 && first[0]?.length === 0) first.splice(0, 1);
1335
- }
1336
- const ancestorDists = {};
1337
- const descendantDists = {};
1338
- const jobNames = Object.keys(visibleJobs);
1339
- const reverseDeps = {};
1340
- for (const [job, depList] of Object.entries(filteredDeps)) for (const dep of depList) {
1341
- if (!reverseDeps[dep]) reverseDeps[dep] = [];
1342
- reverseDeps[dep].push(job);
1343
- }
1344
- for (const start of jobNames) {
1345
- const aDist = /* @__PURE__ */ new Map();
1346
- const q1 = [[start, 0]];
1347
- while (q1.length > 0) {
1348
- const [job, d] = q1.shift();
1349
- for (const dep of filteredDeps[job] ?? []) if (!aDist.has(dep)) {
1350
- aDist.set(dep, d + 1);
1351
- q1.push([dep, d + 1]);
1352
- }
1353
- }
1354
- ancestorDists[start] = aDist;
1355
- const dDist = /* @__PURE__ */ new Map();
1356
- const q2 = [[start, 0]];
1357
- while (q2.length > 0) {
1358
- const [job, d] = q2.shift();
1359
- for (const child of reverseDeps[job] ?? []) if (!dDist.has(child)) {
1360
- dDist.set(child, d + 1);
1361
- q2.push([child, d + 1]);
1362
- }
1363
- }
1364
- descendantDists[start] = dDist;
1365
- }
1366
- return {
1367
- dependencyCols: finalDeps,
1368
- directDeps: filteredDeps,
1369
- displayJobs: visibleJobs,
1370
- ancestorDists,
1371
- descendantDists
1372
- };
1373
- }
1374
- function SubworkflowWell({ expanded, onSettled, className, children }) {
1375
- return /* @__PURE__ */ jsx(Reveal, {
1376
- open: expanded,
1377
- growWidth: true,
1378
- onSettled,
1379
- children: /* @__PURE__ */ jsx("div", {
1380
- className,
1381
- style: { zoom: .8 },
1382
- children
1383
- })
1384
- });
1385
- }
1386
- var SUBGRAPH_Z_STEP = 100;
1387
- var FIT_ANIMATION_MS = 300;
1388
- function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflows, toggleSubworkflow, openInNewGraph, setSublogOpen, invalidateConnectors, requestFit, stepsOpen, toggleSteps, matrixOpen, toggleMatrix, onReady }) {
1389
- const engine = useWorkflowEngine();
1390
- const wrapperRef = useRef(null);
1391
- const zBase = depth * SUBGRAPH_Z_STEP;
1392
- const [hoveredJob, setHoveredJob] = useState(null);
1393
- const lastHoveredDistancesRef = useRef(null);
1394
- const maxHoveredDistRef = useRef(0);
1395
- const retractPathLensRef = useRef(/* @__PURE__ */ new Map());
1396
- const matrixGroups = useMemo(() => engine.matrixGroups(jobs), [engine, jobs]);
1397
- const { dependencyCols, directDeps, displayJobs, ancestorDists, descendantDists } = useMemo(() => computeGraphLayout(engine, jobs, matrixGroups), [
1398
- engine,
1399
- jobs,
1400
- matrixGroups
1401
- ]);
1402
- const hoveredDistances = useMemo(() => {
1403
- if (!hoveredJob) return null;
1404
- const dist = /* @__PURE__ */ new Map();
1405
- dist.set(hoveredJob, 0);
1406
- const ancestors = ancestorDists[hoveredJob];
1407
- if (ancestors) for (const [k, v] of ancestors) dist.set(k, v);
1408
- const descendants = descendantDists[hoveredJob];
1409
- if (descendants) for (const [k, v] of descendants) dist.set(k, v);
1410
- return dist;
1411
- }, [
1412
- hoveredJob,
1413
- ancestorDists,
1414
- descendantDists
1415
- ]);
1416
- if (hoveredDistances) {
1417
- lastHoveredDistancesRef.current = hoveredDistances;
1418
- maxHoveredDistRef.current = Math.max(...hoveredDistances.values());
1419
- }
1420
- const activeDists = hoveredDistances ?? lastHoveredDistancesRef.current;
1421
- useEffect(() => {
1422
- if (hoveredJob || !lastHoveredDistancesRef.current) return;
1423
- const maxDelay = maxHoveredDistRef.current * animT + animT;
1424
- const timer = setTimeout(() => {
1425
- lastHoveredDistancesRef.current = null;
1426
- maxHoveredDistRef.current = 0;
1427
- invalidateConnectors();
1428
- }, maxDelay * 1e3 + 200);
1429
- return () => clearTimeout(timer);
1430
- }, [
1431
- hoveredJob,
1432
- invalidateConnectors,
1433
- animT
1434
- ]);
1435
- const hoveredRelated = useMemo(() => {
1436
- if (!hoveredDistances) return null;
1437
- return new Set(hoveredDistances.keys());
1438
- }, [hoveredDistances]);
1439
- useEffect(() => {
1440
- const raf = requestAnimationFrame(() => {
1441
- invalidateConnectors();
1442
- onReady?.();
1443
- });
1444
- return () => cancelAnimationFrame(raf);
1445
- }, [
1446
- dependencyCols,
1447
- invalidateConnectors,
1448
- onReady
1449
- ]);
1450
- useEffect(() => {
1451
- const el = wrapperRef.current;
1452
- if (!el) return;
1453
- const ro = new ResizeObserver(() => invalidateConnectors());
1454
- ro.observe(el);
1455
- return () => ro.disconnect();
1456
- }, [invalidateConnectors]);
1457
- const didRevealMountRef = useRef(false);
1458
- useEffect(() => {
1459
- if (!didRevealMountRef.current) {
1460
- didRevealMountRef.current = true;
1461
- return;
1462
- }
1463
- let raf = 0;
1464
- const start = performance.now();
1465
- const tick = () => {
1466
- flushSync(() => invalidateConnectors());
1467
- if (performance.now() - start < 260) raf = requestAnimationFrame(tick);
1468
- };
1469
- raf = requestAnimationFrame(tick);
1470
- return () => cancelAnimationFrame(raf);
1471
- }, [
1472
- stepsOpen,
1473
- matrixOpen,
1474
- expandedSubworkflows,
1475
- invalidateConnectors
1476
- ]);
1477
- const buildBoxLayer = useCallback(() => {
1478
- const isStepsOpen = (j) => stepsOpen.has(pathPrefix + j);
1479
- const onJobClick = (j) => {
1480
- const status = jobs[j]?.status ?? displayJobs[j]?.status;
1481
- if (!isStepsOpen(j) && (status === "skipped" || status === "skipped-failed")) return;
1482
- toggleSteps(pathPrefix + j);
1483
- };
1484
- const expandInline = (j, s) => {
1485
- toggleSubworkflow(`${pathPrefix}${j}:${s}`);
1486
- requestAnimationFrame(invalidateConnectors);
1487
- };
1488
- const openNew = (j, s) => openInNewGraph(pathPrefix, j, s);
1489
- const onStepClick = (j, s) => {
1490
- const stepStatus = jobs[j]?.steps?.[+s]?.status;
1491
- if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1492
- if (jobs[j]?.steps[+s]?.subworkflow) expandInline(j, +s);
1493
- else setSublogOpen(engine.stepLogPath(pathPrefix, j, s));
1494
- };
1495
- const onStepLogClick = (j, s) => setSublogOpen(engine.stepLogPath(pathPrefix, j, s));
1496
- const onSubworkflowIcon = (j, s) => openNew(j, s);
1497
- const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1498
- const renderSubworkflow = (j, s) => {
1499
- const subJobs = jobs[j]?.steps?.[s]?.subworkflow?.jobs;
1500
- if (!subJobs) return null;
1501
- return /* @__PURE__ */ jsx(SubworkflowWell, {
1502
- expanded: isSubworkflowExpanded(j, s),
1503
- onSettled: requestFit,
1504
- className: "my-2 rounded-lg border-4 border-(--theme-border)",
1505
- children: /* @__PURE__ */ jsx(NestedSubgraph, {
1506
- jobs: subJobs,
1507
- pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1508
- depth: depth + 1,
1509
- preview,
1510
- animT,
1511
- expandedSubworkflows,
1512
- toggleSubworkflow,
1513
- openInNewGraph,
1514
- setSublogOpen,
1515
- invalidateConnectors,
1516
- requestFit,
1517
- stepsOpen,
1518
- toggleSteps,
1519
- matrixOpen,
1520
- toggleMatrix
1521
- })
1522
- });
1523
- };
1524
- return /* @__PURE__ */ jsx("div", {
1525
- className: "flex relative",
1526
- children: dependencyCols.map((col) => {
1527
- return /* @__PURE__ */ jsx("div", { children: col.map((jobNames) => {
1528
- const matrixGroup = displayJobs[jobNames[0]]?._matrixGroup;
1529
- if (matrixGroup) return /* @__PURE__ */ jsx(MatrixGroupNode, {
1530
- matrixGroup,
1531
- jobNames,
1532
- idPrefix: pathPrefix,
1533
- isExpanded: matrixOpen.has(pathPrefix + matrixGroup.originaljob),
1534
- onToggle: () => toggleMatrix(pathPrefix + matrixGroup.originaljob),
1535
- jobs,
1536
- activeDists,
1537
- onMouseEnter: () => setHoveredJob(jobNames[0]),
1538
- onMouseLeave: () => setHoveredJob(null),
1539
- hoveredRelated,
1540
- animT,
1541
- zBase,
1542
- isStepsOpen,
1543
- onJobClick,
1544
- onStepClick,
1545
- onStepLogClick,
1546
- onSubworkflowIcon,
1547
- isSubworkflowExpanded,
1548
- renderSubworkflow,
1549
- preview
1550
- }, "node_" + jobNames[0]);
1551
- return /* @__PURE__ */ jsxs("div", {
1552
- id: "node_" + pathPrefix + jobNames[0],
1553
- role: "none",
1554
- className: "relative m-24",
1555
- style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
1556
- onMouseEnter: () => setHoveredJob(jobNames[0]),
1557
- onMouseLeave: () => setHoveredJob(null),
1558
- onFocus: () => setHoveredJob(jobNames[0]),
1559
- onBlur: () => setHoveredJob(null),
1560
- children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("div", {
1561
- className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
1562
- style: {
1563
- opacity: hoveredRelated && !hoveredRelated.has(jobNames[0]) ? .5 : 1,
1564
- transition: `opacity ${animT}s`
1565
- },
1566
- children: /* @__PURE__ */ jsx(Joblist, {
1567
- jobs: displayJobs,
1568
- jobNames,
1569
- onJobClick,
1570
- isStepsOpen,
1571
- onStepClick,
1572
- onStepLogClick,
1573
- onSubworkflowIcon,
1574
- isSubworkflowExpanded,
1575
- renderSubworkflow,
1576
- growWidth: true,
1577
- preview
1578
- })
1579
- })]
1580
- }, "node_" + jobNames[0]);
1581
- }) }, col[0]?.[0]);
1582
- })
1583
- });
1584
- }, [
1585
- dependencyCols,
1586
- displayJobs,
1587
- jobs,
1588
- stepsOpen,
1589
- matrixOpen,
1590
- expandedSubworkflows,
1591
- hoveredRelated,
1592
- activeDists,
1593
- animT,
1594
- zBase,
1595
- preview,
1596
- pathPrefix,
1597
- depth,
1598
- toggleSteps,
1599
- toggleSubworkflow,
1600
- toggleMatrix,
1601
- openInNewGraph,
1602
- setSublogOpen,
1603
- engine,
1604
- invalidateConnectors,
1605
- requestFit
1606
- ]);
1607
- const memoizedBoxLayer = useMemo(() => buildBoxLayer(), [buildBoxLayer]);
1608
- const hasLiveNestedGraph = Array.from(expandedSubworkflows).some((k) => k.startsWith(pathPrefix));
1609
- if (!Array.isArray(dependencyCols) || dependencyCols.length === 0 || !Array.isArray(dependencyCols[0]) || dependencyCols[0].length === 0 || !Array.isArray(dependencyCols[0][0]) || dependencyCols[0][0].length === 0) return null;
1610
- return /* @__PURE__ */ jsxs("div", {
1611
- ref: wrapperRef,
1612
- className: "relative",
1613
- children: [(() => {
1614
- const wrapper = wrapperRef.current;
1615
- const gid = (name) => document.getElementById("node_" + pathPrefix + name);
1616
- const ox = (el) => offsetWithin(el, wrapper).x;
1617
- const nodeConnectorY = (el) => offsetWithin(el, wrapper).y + 37;
1618
- let colGap = 32;
1619
- if (dependencyCols.length >= 2) {
1620
- const col0 = gid(dependencyCols[0]?.[0]?.[0] ?? "");
1621
- const col1 = gid(dependencyCols[1]?.[0]?.[0] ?? "");
1622
- if (col0 && col1) colGap = ox(col1) - (ox(col0) + col0.offsetWidth);
1623
- }
1624
- let minAdy = Number.POSITIVE_INFINITY;
1625
- for (const col of dependencyCols) for (const jobNames of col) for (const dep of directDeps[jobNames[0]] ?? []) {
1626
- const s = gid(dep);
1627
- const e = gid(jobNames[0]);
1628
- if (!s || !e) continue;
1629
- const dy = Math.abs(nodeConnectorY(e) - nodeConnectorY(s));
1630
- if (dy > 0 && dy < minAdy) minAdy = dy;
1631
- }
1632
- const MAX_ARC_R = 16;
1633
- const arcR = Math.min(minAdy === Number.POSITIVE_INFINITY ? MAX_ARC_R : minAdy / 4, colGap / 2, MAX_ARC_R);
1634
- return dependencyCols.map((col) => /* @__PURE__ */ jsx(React.Fragment, { children: col.map((jobNames) => /* @__PURE__ */ jsx(React.Fragment, { children: (directDeps[jobNames[0]] ?? []).map((dep) => {
1635
- const isHighlighted = hoveredRelated?.has(jobNames[0]) && hoveredRelated?.has(dep);
1636
- const baseColor = "var(--theme-border)";
1637
- const depDist = activeDists?.get(dep) ?? 0;
1638
- const targetDist = activeDists?.get(jobNames[0]) ?? 0;
1639
- const hopDelay = animT;
1640
- const closerDist = Math.min(depDist, targetDist);
1641
- const connectorDelay = closerDist * hopDelay;
1642
- const retractDelay = (maxHoveredDistRef.current - closerDist) * hopDelay;
1643
- const drawRightToLeft = targetDist < depDist;
1644
- const start = gid(dep);
1645
- const end = gid(jobNames[0]);
1646
- if (!start || !end) return null;
1647
- const x1 = ox(start) + start.offsetWidth - 2;
1648
- const y1 = nodeConnectorY(start);
1649
- const x2 = ox(end) + 2;
1650
- const y2 = nodeConnectorY(end);
1651
- const dy = y2 - y1;
1652
- const ady = Math.abs(dy);
1653
- let pathD;
1654
- let pathLen;
1655
- let pathDReversed;
1656
- const r = arcR;
1657
- if (ady === 0) {
1658
- pathD = `M ${x1} ${y1} L ${x2} ${y2}`;
1659
- pathDReversed = `M ${x2} ${y2} L ${x1} ${y1}`;
1660
- pathLen = x2 - x1;
1661
- } else {
1662
- const s = dy > 0 ? 1 : -1;
1663
- const ax2end = x2 - (colGap / 2 - r);
1664
- const ax = ax2end - 2 * r;
1665
- const sw1 = dy > 0 ? 1 : 0;
1666
- const sw2 = dy > 0 ? 0 : 1;
1667
- pathD = [
1668
- `M ${x1} ${y1}`,
1669
- `L ${ax} ${y1}`,
1670
- `A ${r} ${r} 0 0 ${sw1} ${ax + r} ${y1 + s * r}`,
1671
- `L ${ax + r} ${y2 - s * r}`,
1672
- `A ${r} ${r} 0 0 ${sw2} ${ax2end} ${y2}`,
1673
- `L ${x2} ${y2}`
1674
- ].join(" ");
1675
- pathDReversed = [
1676
- `M ${x2} ${y2}`,
1677
- `L ${ax2end} ${y2}`,
1678
- `A ${r} ${r} 0 0 ${sw1} ${ax + r} ${y2 - s * r}`,
1679
- `L ${ax + r} ${y1 + s * r}`,
1680
- `A ${r} ${r} 0 0 ${sw2} ${ax} ${y1}`,
1681
- `L ${x1} ${y1}`
1682
- ].join(" ");
1683
- const arcLen = Math.PI * r / 2;
1684
- const vSeg = Math.max(0, ady - 2 * r);
1685
- pathLen = ax - x1 + arcLen + vSeg + arcLen + (x2 - ax2end);
1686
- }
1687
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1688
- /* @__PURE__ */ jsxs("svg", {
1689
- "aria-hidden": "true",
1690
- overflow: "visible",
1691
- className: "absolute pointer-events-none",
1692
- style: { zIndex: (isHighlighted || activeDists?.has(dep) && activeDists?.has(jobNames[0]) ? 30 : 10) + zBase },
1693
- children: [
1694
- /* @__PURE__ */ jsx("rect", {
1695
- x: x1 - 16,
1696
- y: y1 - 16,
1697
- width: 32,
1698
- height: 32,
1699
- fill: "var(--theme-panel-bg)"
1700
- }),
1701
- /* @__PURE__ */ jsx("rect", {
1702
- x: x2 - 16,
1703
- y: y2 - 16,
1704
- width: 32,
1705
- height: 32,
1706
- fill: "var(--theme-panel-bg)"
1707
- }),
1708
- /* @__PURE__ */ jsx("circle", {
1709
- cx: x1,
1710
- cy: y1,
1711
- r: "8",
1712
- fill: isHighlighted ? "#3B82F6" : baseColor,
1713
- style: {
1714
- opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1715
- transition: isHighlighted ? `fill ${animT}s ${depDist * hopDelay}s, opacity 0s` : `fill ${animT}s ${(maxHoveredDistRef.current - depDist) * hopDelay}s, opacity 0s`
1716
- }
1717
- }),
1718
- /* @__PURE__ */ jsx("circle", {
1719
- cx: x2,
1720
- cy: y2,
1721
- r: "8",
1722
- fill: isHighlighted ? "#3B82F6" : baseColor,
1723
- style: {
1724
- opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1725
- transition: isHighlighted ? `fill ${animT}s ${targetDist * hopDelay}s, opacity 0s` : `fill ${animT}s ${(maxHoveredDistRef.current - targetDist) * hopDelay}s, opacity 0s`
1726
- }
1727
- })
1728
- ]
1729
- }),
1730
- /* @__PURE__ */ jsxs("svg", {
1731
- "aria-hidden": "true",
1732
- overflow: "visible",
1733
- className: "absolute pointer-events-none",
1734
- style: { zIndex: (isHighlighted ? 20 : -1) + zBase },
1735
- children: [/* @__PURE__ */ jsx("path", {
1736
- d: pathD,
1737
- stroke: "var(--theme-panel-bg)",
1738
- strokeWidth: "6",
1739
- fill: "transparent"
1740
- }), /* @__PURE__ */ jsx("path", {
1741
- d: pathD,
1742
- stroke: baseColor,
1743
- strokeWidth: "6",
1744
- fill: "transparent",
1745
- style: {
1746
- opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1747
- transition: `opacity 0s`
1748
- }
1749
- })]
1750
- }),
1751
- /* @__PURE__ */ jsxs("svg", {
1752
- "aria-hidden": "true",
1753
- overflow: "visible",
1754
- className: "absolute pointer-events-none",
1755
- style: { zIndex: 20 + zBase },
1756
- children: [/* @__PURE__ */ jsx("path", {
1757
- d: drawRightToLeft ? pathDReversed : pathD,
1758
- fill: "transparent",
1759
- style: {
1760
- stroke: "#3B82F6",
1761
- strokeWidth: 6,
1762
- strokeDasharray: `${pathLen} ${pathLen}`,
1763
- strokeDashoffset: isHighlighted ? 0 : pathLen,
1764
- opacity: isHighlighted ? 1 : 0,
1765
- transition: isHighlighted ? `stroke-dashoffset ${animT}s ease ${connectorDelay}s, opacity 0s ease ${connectorDelay}s` : "stroke-dashoffset 0s, opacity 0s"
1766
- }
1767
- }), (() => {
1768
- const connKey = `${dep}→${jobNames[0]}`;
1769
- if (isHighlighted) retractPathLensRef.current.set(connKey, pathLen);
1770
- const isRetracting = !isHighlighted && !!activeDists?.has(dep) && !!activeDists?.has(jobNames[0]);
1771
- if (!isHighlighted && !isRetracting) return null;
1772
- const frozenLen = retractPathLensRef.current.get(connKey) ?? pathLen;
1773
- return /* @__PURE__ */ jsx("path", {
1774
- d: drawRightToLeft ? pathDReversed : pathD,
1775
- fill: "transparent",
1776
- style: {
1777
- stroke: "#3B82F6",
1778
- strokeWidth: 6,
1779
- strokeDasharray: `${frozenLen} ${frozenLen}`,
1780
- strokeDashoffset: isHighlighted ? 0 : frozenLen,
1781
- opacity: isHighlighted ? 0 : 1,
1782
- transition: isHighlighted ? "stroke-dashoffset 0s, opacity 0s" : `stroke-dashoffset ${animT}s ease ${retractDelay}s, opacity 0s`
1783
- }
1784
- });
1785
- })()]
1786
- })
1787
- ] }, dep);
1788
- }) }, jobNames[0])) }, col[0]?.[0]));
1789
- })(), hasLiveNestedGraph ? buildBoxLayer() : memoizedBoxLayer]
1790
- });
1791
- }
1792
- var NestedSubgraph = Subgraph;
1793
- function SidebarWell({ expanded, children }) {
1794
- return /* @__PURE__ */ jsx(Collapse, {
1795
- expanded,
1796
- releaseOverflow: false,
1797
- children
1798
- });
1799
- }
1800
- function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expandedSubworkflows, toggleSubworkflow, matrixOpen, toggleMatrix, openInNewGraph, setSublogOpen }) {
1801
- const engine = useWorkflowEngine();
1802
- const matrixGroups = useMemo(() => engine.matrixGroups(jobs), [engine, jobs]);
1803
- const { displayJobs } = useMemo(() => computeGraphLayout(engine, jobs, matrixGroups), [
1804
- engine,
1805
- jobs,
1806
- matrixGroups
1807
- ]);
1808
- const isStepsOpen = (j) => stepsOpen.has(pathPrefix + j);
1809
- const onJobClick = (j) => {
1810
- const status = jobs[j]?.status ?? displayJobs[j]?.status;
1811
- if (!isStepsOpen(j) && (status === "skipped" || status === "skipped-failed")) return;
1812
- toggleSteps(pathPrefix + j);
1813
- };
1814
- const expandInline = (j, s) => toggleSubworkflow(`${pathPrefix}${j}:${s}`);
1815
- const openNew = (j, s) => openInNewGraph(pathPrefix, j, s);
1816
- const onStepClick = (j, s) => {
1817
- const stepStatus = jobs[j]?.steps?.[+s]?.status;
1818
- if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1819
- if (jobs[j]?.steps[+s]?.subworkflow) expandInline(j, +s);
1820
- else setSublogOpen(engine.stepLogPath(pathPrefix, j, s));
1821
- };
1822
- const onStepLogClick = (j, s) => setSublogOpen(engine.stepLogPath(pathPrefix, j, s));
1823
- const onSubworkflowIcon = (j, s) => openNew(j, s);
1824
- const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1825
- const renderSubworkflow = (j, s) => {
1826
- const subJobs = jobs[j]?.steps?.[s]?.subworkflow?.jobs;
1827
- if (!subJobs) return null;
1828
- return /* @__PURE__ */ jsx(SidebarWell, {
1829
- expanded: isSubworkflowExpanded(j, s),
1830
- children: /* @__PURE__ */ jsx("div", {
1831
- className: "ml-6 mt-0.5 border-l border-(--theme-border) pl-4",
1832
- children: /* @__PURE__ */ jsx(SidebarJobs, {
1833
- jobs: subJobs,
1834
- pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1835
- preview,
1836
- stepsOpen,
1837
- toggleSteps,
1838
- expandedSubworkflows,
1839
- toggleSubworkflow,
1840
- matrixOpen,
1841
- toggleMatrix,
1842
- openInNewGraph,
1843
- setSublogOpen
1844
- })
1845
- })
1846
- });
1847
- };
1848
- const seenMatrixGroups = /* @__PURE__ */ new Set();
1849
- const orderedKeys = Object.keys(jobs).flatMap((jobName) => {
1850
- const originaljob = jobs[jobName]?._matrix?.originaljob;
1851
- if (!originaljob) return [jobName];
1852
- if (seenMatrixGroups.has(originaljob)) return [];
1853
- seenMatrixGroups.add(originaljob);
1854
- return [originaljob];
1855
- });
1856
- return /* @__PURE__ */ jsx(Fragment$1, { children: orderedKeys.map((jobName) => {
1857
- const mg = displayJobs[jobName]?._matrixGroup;
1858
- if (mg) return /* @__PURE__ */ jsx(MatrixGroupSummaryItem, {
1859
- matrixGroup: mg,
1860
- status: displayJobs[jobName]?.status ?? "",
1861
- isExpanded: matrixOpen.has(pathPrefix + mg.originaljob),
1862
- onToggle: () => toggleMatrix(pathPrefix + mg.originaljob),
1863
- jobs,
1864
- isStepsOpen,
1865
- onJobClick,
1866
- onStepClick,
1867
- onStepLogClick,
1868
- onSubworkflowIcon,
1869
- isSubworkflowExpanded,
1870
- renderSubworkflow,
1871
- preview
1872
- }, "matrix-" + pathPrefix + mg.originaljob);
1873
- return /* @__PURE__ */ jsx(Joblist, {
1874
- jobs,
1875
- jobNames: [jobName],
1876
- onJobClick,
1877
- isStepsOpen,
1878
- onStepClick,
1879
- onStepLogClick,
1880
- onSubworkflowIcon,
1881
- isSubworkflowExpanded,
1882
- renderSubworkflow,
1883
- preview
1884
- }, "job-" + pathPrefix + jobName);
1885
- }) });
1886
- }
1887
- var RUN_STATUSES = [
1888
- "started",
1889
- "running",
1890
- "completed",
1891
- "error",
1892
- "failed",
1893
- "canceled",
1894
- "skipped",
1895
- "faulted",
1896
- "canceling",
1897
- "skipped-failed",
1898
- ""
1899
- ];
1900
- function toStatus(value) {
1901
- return RUN_STATUSES.find((status) => status === value) ?? "";
1902
- }
1903
- function walkJobPath(root, path) {
1904
- let cursor = root;
1905
- for (const key of path) {
1906
- if (typeof cursor !== "object" || cursor === null) return {};
1907
- cursor = Object.getOwnPropertyDescriptor(cursor, String(key))?.value;
1908
- }
1909
- return isJobRecord(cursor) ? cursor : {};
1910
- }
1911
- function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder, viewMode = "dag", setViewMode }) {
1912
- const engine = useWorkflowEngine();
1913
- const animT = .125;
1914
- const minScale = .2;
1915
- const maxScale = .6;
1916
- const rootJobs = isJobRecord(run.executedJobs) ? run.executedJobs : {};
1917
- const runNumber = run.number ?? 0;
1918
- const runSlug = run.slug ?? "";
1919
- const workflowName = run.workflowName ?? "";
1920
- const runStatus = toStatus(run.status);
1921
- const [sublogOpen, setSublogOpen] = useState("");
1922
- const [stepsOpen, setStepsOpen] = useState(/* @__PURE__ */ new Set());
1923
- const [expandedSubworkflows, setExpandedSubworkflows] = useState(/* @__PURE__ */ new Set());
1924
- const containerRef = useRef(null);
1925
- const graphContentRef = useRef(null);
1926
- const transformRef = useRef(null);
1927
- const [, forceRerender] = useState(0);
1928
- const invalidateConnectors = useCallback(() => forceRerender((c) => c + 1), []);
1929
- const [windowSize, setWindowSize] = useState(() => ({
1930
- w: typeof window !== "undefined" ? window.innerWidth : 800,
1931
- h: typeof window !== "undefined" ? window.innerHeight : 600
1932
- }));
1933
- useEffect(() => {
1934
- const onResize = () => setWindowSize({
1935
- w: window.innerWidth,
1936
- h: window.innerHeight
1937
- });
1938
- window.addEventListener("resize", onResize);
1939
- return () => window.removeEventListener("resize", onResize);
1940
- }, []);
1941
- const [topOffset, setTopOffset] = useState(null);
1942
- const topObserverRef = useRef(null);
1943
- const setContainerElement = useCallback((el) => {
1944
- containerRef.current = el;
1945
- topObserverRef.current?.disconnect();
1946
- topObserverRef.current = null;
1947
- if (!el || preview) return;
1948
- const measure = () => {
1949
- const cur = containerRef.current;
1950
- if (!cur) return;
1951
- setTopOffset((prev) => {
1952
- const next = cur.getBoundingClientRect().top;
1953
- return prev === next ? prev : next;
1954
- });
1955
- };
1956
- requestAnimationFrame(measure);
1957
- const parent = el.parentElement;
1958
- if (parent) {
1959
- const ro = new ResizeObserver(measure);
1960
- ro.observe(parent);
1961
- topObserverRef.current = ro;
1962
- }
1963
- }, [preview]);
1964
- const containerHeight = topOffset !== null ? Math.max(windowSize.h - topOffset - 16, 160) : null;
1965
- const [previewHeight, setPreviewHeight] = useState(null);
1966
- const previewViewRef = useRef(null);
1967
- useEffect(() => {
1968
- const container = containerRef.current;
1969
- if (!preview || !container) return;
1970
- let raf = 0;
1971
- let attempts = 0;
1972
- const measure = () => {
1973
- cancelAnimationFrame(raf);
1974
- raf = requestAnimationFrame(() => {
1975
- const next = measurePreviewHeight(graphContentRef.current, previewViewRef);
1976
- if (next === null) {
1977
- if (attempts++ < 120) measure();
1978
- return;
1979
- }
1980
- attempts = 0;
1981
- setPreviewHeight((prev) => prev === next ? prev : next);
1982
- });
1983
- };
1984
- measure();
1985
- const observer = new MutationObserver(measure);
1986
- observer.observe(container, {
1987
- childList: true,
1988
- subtree: true
1989
- });
1990
- return () => {
1991
- observer.disconnect();
1992
- cancelAnimationFrame(raf);
1993
- };
1994
- }, [preview]);
1995
- const { dag: dagStrings } = useStrings();
1996
- const slots = useSlots();
1997
- const [executedJobsPath, setExecutedJobsPath] = useState([[]]);
1998
- const [executedJobsPathIdx, setExecutedJobsPathIdx] = useState(0);
1999
- const [expandedJobs, setExpandedJobs] = useState(/* @__PURE__ */ new Set());
2000
- const [matrixOpen, setMatrixOpen] = useState(/* @__PURE__ */ new Set());
2001
- const isRunFinished = !!run.completedAt || run.status === "completed" || run.status === "error" || run.status === "canceled";
2002
- const [now, setNow] = useState(() => DateTime.now());
2003
- useEffect(() => {
2004
- if (isRunFinished) return;
2005
- const timer = setInterval(() => setNow(DateTime.now()), 1e3);
2006
- return () => clearInterval(timer);
2007
- }, [isRunFinished]);
2008
- const jobs = walkJobPath(rootJobs, executedJobsPath[executedJobsPathIdx] ?? []);
2009
- const resetGraph = useCallback(() => {
2010
- setSublogOpen("");
2011
- setStepsOpen(/* @__PURE__ */ new Set());
2012
- setExpandedSubworkflows(/* @__PURE__ */ new Set());
2013
- setMatrixOpen(/* @__PURE__ */ new Set());
2014
- }, []);
2015
- const openInNewGraph = useCallback((prefix, job, step) => {
2016
- resetGraph();
2017
- setExecutedJobsPath((prev) => [[
2018
- ...parsePrefixToPath(prefix),
2019
- job,
2020
- "steps",
2021
- step,
2022
- "subworkflow",
2023
- "jobs"
2024
- ], ...prev.slice(executedJobsPathIdx, 10)]);
2025
- setExecutedJobsPathIdx(0);
2026
- }, [executedJobsPathIdx, resetGraph]);
2027
- const toggleSubworkflow = useCallback((key) => {
2028
- setExpandedSubworkflows((prev) => {
2029
- const next = new Set(prev);
2030
- if (next.has(key)) next.delete(key);
2031
- else next.add(key);
2032
- return next;
2033
- });
2034
- }, []);
2035
- const toggleSteps = useCallback((key) => {
2036
- setStepsOpen((prev) => {
2037
- const next = new Set(prev);
2038
- if (next.has(key)) next.delete(key);
2039
- else next.add(key);
2040
- return next;
2041
- });
2042
- requestAnimationFrame(invalidateConnectors);
2043
- }, [invalidateConnectors]);
2044
- const toggleMatrix = useCallback((key) => {
2045
- setMatrixOpen((prev) => {
2046
- const next = new Set(prev);
2047
- if (next.has(key)) next.delete(key);
2048
- else next.add(key);
2049
- return next;
2050
- });
2051
- requestAnimationFrame(invalidateConnectors);
2052
- }, [invalidateConnectors]);
2053
- const { data: sublogs, isLoading: sublogsLoading, error: sublogsError } = useRunFile(runSlug, sublogOpen || null, preview ? void 0 : { refreshInterval: 2e3 });
2054
- useEffect(() => {
2055
- if (sublogOpen.endsWith("/script-unstable-redacted.sh") && !sublogsLoading && (sublogsError || !sublogs)) setSublogOpen(sublogOpen.replace(/script-unstable-redacted\.sh$/, "script-unstable.sh"));
2056
- }, [
2057
- sublogOpen,
2058
- sublogs,
2059
- sublogsError,
2060
- sublogsLoading
2061
- ]);
2062
- const jobAndStep = sublogOpen.split("/").slice(-3, -1);
2063
- if (jobAndStep.length > 1) jobAndStep[1] = jobAndStep[1].split("_")[1] ?? "";
2064
- const sublogIsStep = jobAndStep.length > 1;
2065
- const logName = jobAndStep.length > 1 ? "Job: " + (jobAndStep[0][0].toUpperCase() + jobAndStep[0].slice(1)).replace(/_/g, " ") + " \nStep: " + (jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]?.name || "Step " + jobAndStep[1]) : "";
2066
- const sublogIsSubworkflow = jobAndStep.length > 1 && jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]?.subworkflow;
2067
- const matrixGroups = useMemo(() => engine.matrixGroups(jobs), [engine, jobs]);
2068
- const { dependencyCols } = useMemo(() => computeGraphLayout(engine, jobs, matrixGroups), [
2069
- engine,
2070
- jobs,
2071
- matrixGroups
2072
- ]);
2073
- const fitGraph = useCallback((animationMs = 0) => {
2074
- const container = containerRef.current;
2075
- const content = graphContentRef.current;
2076
- const transform = transformRef.current;
2077
- if (!container || !content || !transform) return;
2078
- const topNodes = content.querySelectorAll(":scope > div > div > div > div[id^=\"node_\"]");
2079
- const nodes = topNodes.length > 0 ? topNodes : content.querySelectorAll("[id^=\"node_\"]");
2080
- if (nodes.length === 0) return;
2081
- let minX = Number.POSITIVE_INFINITY;
2082
- let minY = Number.POSITIVE_INFINITY;
2083
- let maxX = 0;
2084
- let maxY = 0;
2085
- for (const node of nodes) {
2086
- const el = node;
2087
- const pos = offsetWithin(el, content);
2088
- minX = Math.min(minX, pos.x);
2089
- minY = Math.min(minY, pos.y);
2090
- maxX = Math.max(maxX, pos.x + el.offsetWidth);
2091
- maxY = Math.max(maxY, pos.y + el.offsetHeight);
2092
- }
2093
- const graphW = maxX - minX;
2094
- const graphH = maxY - minY;
2095
- const containerW = container.clientWidth;
2096
- const containerH = container.clientHeight;
2097
- if (graphW === 0 || graphH === 0) return;
2098
- const margin = Math.min(containerW, containerH) * .05;
2099
- const scaleW = (containerW - margin * 2) / graphW;
2100
- const scaleH = (containerH - margin * 2) / graphH;
2101
- const scale = Math.min(Math.max(Math.min(scaleW, scaleH), minScale), maxScale);
2102
- const offsetX = margin - minX * scale;
2103
- const offsetY = margin - minY * scale;
2104
- if (animationMs > 0) {
2105
- transform.setTransform(offsetX, offsetY, scale, animationMs, "easeOut");
2106
- return;
2107
- }
2108
- const wrapperEl = container.querySelector(".react-transform-component");
2109
- if (wrapperEl) wrapperEl.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
2110
- requestAnimationFrame(() => {
2111
- transform.setTransform(offsetX, offsetY, scale, 0);
2112
- invalidateConnectors();
2113
- });
2114
- }, [invalidateConnectors]);
2115
- const animatedFit = useCallback(() => fitGraph(FIT_ANIMATION_MS), [fitGraph]);
2116
- useEffect(() => {
2117
- if (preview) return;
2118
- const raf = requestAnimationFrame(() => fitGraph());
2119
- return () => cancelAnimationFrame(raf);
2120
- }, [
2121
- preview,
2122
- fitGraph,
2123
- windowSize,
2124
- executedJobsPathIdx
2125
- ]);
2126
- const currentPath = executedJobsPath[executedJobsPathIdx] ?? [];
2127
- const nestingDepth = currentPath.length / 5;
2128
- let pathAppend = "";
2129
- for (let i = 0; i < nestingDepth; i++) pathAppend += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2]?.toString()}/`;
2130
- let generalLogPath = "logs.out";
2131
- if (nestingDepth > 0) {
2132
- let parentPrefix = "";
2133
- for (let i = 0; i < nestingDepth - 1; i++) parentPrefix += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2]?.toString()}/`;
2134
- const lastJob = String(currentPath[5 * (nestingDepth - 1)] ?? "");
2135
- const lastStep = currentPath[5 * (nestingDepth - 1) + 2] ?? "";
2136
- generalLogPath = engine.stepLogPath(parentPrefix, lastJob, lastStep);
2137
- }
2138
- const missingLogText = sublogIsSubworkflow ? "Subworkflows generally have no logs/scripts at this level.\nClick on the \"Open in new graph\" icon on the subworkflow step to open the subworkflow graph." : sublogsError ? dagStrings.logLoadFailed : (sublogOpen === generalLogPath ? dagStrings.statusReason(run.statusReason) : void 0) ?? dagStrings.noLogFound;
2139
- const currentSublog = sublogOpen === "" ? "" : preview ? engine.toYaml(jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]) : sublogsError || !sublogs ? missingLogText : sublogs;
2140
- const expansionKeys = useMemo(() => engine.expansionKeys(jobs, pathAppend), [
2141
- engine,
2142
- jobs,
2143
- pathAppend
2144
- ]);
2145
- const hasExpandable = expansionKeys.subworkflowKeys.length > 0 || expansionKeys.matrixKeys.length > 0;
2146
- const allExpanded = hasExpandable && expansionKeys.subworkflowKeys.every((k) => expandedSubworkflows.has(k)) && expansionKeys.matrixKeys.every((k) => matrixOpen.has(k));
2147
- const toggleExpandAll = () => {
2148
- if (allExpanded) {
2149
- setExpandedSubworkflows(/* @__PURE__ */ new Set());
2150
- setStepsOpen(/* @__PURE__ */ new Set());
2151
- setMatrixOpen(/* @__PURE__ */ new Set());
2152
- } else {
2153
- setExpandedSubworkflows(new Set(expansionKeys.subworkflowKeys));
2154
- setStepsOpen(new Set(expansionKeys.jobKeys));
2155
- setMatrixOpen(new Set(expansionKeys.matrixKeys));
2156
- }
2157
- requestAnimationFrame(() => {
2158
- invalidateConnectors();
2159
- fitGraph();
2160
- });
2161
- };
2162
- const nextStep = useCallback((backward) => {
2163
- const sublogSplit = sublogOpen.split("/");
2164
- let job = sublogSplit[sublogSplit.length - 3] ?? "";
2165
- let step = parseInt(sublogSplit[sublogSplit.length - 2].slice(5), 10);
2166
- const jobArr = Object.keys(jobs);
2167
- let idx = jobArr.indexOf(job);
2168
- if (backward) {
2169
- if (0 < step) step -= 1;
2170
- else {
2171
- if (idx === 0) idx = jobArr.length;
2172
- job = jobArr[idx - 1] ?? "";
2173
- step = (jobs[job]?.steps.length ?? 0) - 1;
2174
- }
2175
- } else if ((jobs[job]?.steps.length ?? 0) - 1 > step) step += 1;
2176
- else {
2177
- if (idx === jobArr.length - 1) idx = -1;
2178
- job = jobArr[idx + 1] ?? "";
2179
- step = 0;
2180
- }
2181
- setSublogOpen(sublogSplit.slice(0, -3).join("/") + `/${job}/step_${step}/` + sublogSplit.slice(-1)[0]);
2182
- }, [jobs, sublogOpen]);
2183
- const handleKeyDown = useCallback((e) => {
2184
- if (e.key === "ArrowRight") nextStep(false);
2185
- else if (e.key === "ArrowLeft") nextStep(true);
2186
- }, [nextStep]);
2187
- useEffect(() => {
2188
- if (!sublogIsStep) return;
2189
- window.addEventListener("keydown", handleKeyDown);
2190
- return () => window.removeEventListener("keydown", handleKeyDown);
2191
- }, [handleKeyDown, sublogIsStep]);
2192
- const summarySidebar = useMemo(() => /* @__PURE__ */ jsx(SidebarJobs, {
2193
- jobs,
2194
- pathPrefix: pathAppend,
2195
- preview,
2196
- stepsOpen,
2197
- toggleSteps,
2198
- expandedSubworkflows,
2199
- toggleSubworkflow,
2200
- matrixOpen,
2201
- toggleMatrix,
2202
- openInNewGraph,
2203
- setSublogOpen
2204
- }), [
2205
- jobs,
2206
- pathAppend,
2207
- preview,
2208
- stepsOpen,
2209
- toggleSteps,
2210
- expandedSubworkflows,
2211
- toggleSubworkflow,
2212
- matrixOpen,
2213
- toggleMatrix,
2214
- openInNewGraph
2215
- ]);
2216
- if (!Array.isArray(dependencyCols) || dependencyCols.length === 0 || !Array.isArray(dependencyCols[0]) || dependencyCols[0].length === 0 || !Array.isArray(dependencyCols[0][0]) || dependencyCols[0][0].length === 0) return;
2217
- const startDt = run.createdAt ? DateTime.fromISO(run.createdAt) : now;
2218
- const endDt = run.completedAt ? DateTime.fromISO(run.completedAt) : now;
2219
- const runtime = toAbsHumanDuration(startDt, endDt);
2220
- const additionalBottomLeftBarComponents = sublogIsStep ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
2221
- /* @__PURE__ */ jsxs("button", {
2222
- type: "button",
2223
- className: "btn btn-neutral",
2224
- onClick: () => nextStep(true),
2225
- children: [/* @__PURE__ */ jsx(ArrowLeftIcon, {}), /* @__PURE__ */ jsx("span", { children: "Prev" })]
2226
- }),
2227
- /* @__PURE__ */ jsxs("button", {
2228
- type: "button",
2229
- className: "btn btn-neutral",
2230
- onClick: () => nextStep(false),
2231
- children: [/* @__PURE__ */ jsx(ArrowRightIcon, {}), /* @__PURE__ */ jsx("span", { children: "Next" })]
2232
- }),
2233
- /* @__PURE__ */ jsx("span", {
2234
- className: "pt-0.5 whitespace-pre-wrap text-xs",
2235
- children: logName
2236
- })
2237
- ] }) : void 0;
2238
- const additionalBottomRightBarComponents = (!preview || sublogIsSubworkflow) && sublogOpen && sublogIsStep ? /* @__PURE__ */ jsx("button", {
2239
- type: "button",
2240
- className: "btn btn-neutral",
2241
- onClick: () => {
2242
- if (sublogIsSubworkflow) {
2243
- openInNewGraph(pathAppend, jobAndStep[0], parseInt(jobAndStep[1], 10));
2244
- setSublogOpen("");
2245
- } else setSublogOpen(sublogOpen.split("/").slice(-1)[0] === "logs.out" ? [...sublogOpen.split("/").slice(0, -1), "script-unstable-redacted.sh"].join("/") : [...sublogOpen.split("/").slice(0, -1), "logs.out"].join("/"));
2246
- },
2247
- children: /* @__PURE__ */ jsx("span", { children: sublogIsSubworkflow ? "Open Subworkflow" : sublogOpen.split("/").slice(-1)[0] === "logs.out" ? "Show Script" : "Show Logs" })
2248
- }) : void 0;
2249
- return /* @__PURE__ */ jsxs("div", {
2250
- className: "flex flex-col lg:flex-row w-full",
2251
- children: [
2252
- !preview && /* @__PURE__ */ jsxs("div", {
2253
- className: "hidden lg:block lg:w-[280px] flex-shrink-0 lg:mr-1",
2254
- children: [
2255
- slots.runSessions && /* @__PURE__ */ jsx("div", {
2256
- className: "mb-4 overflow-y-auto",
2257
- children: slots.runSessions({
2258
- runId: run.id ?? "",
2259
- runNumber,
2260
- runActive: engine.isRunActive(run.status),
2261
- links: run.links
2262
- })
2263
- }),
2264
- /* @__PURE__ */ jsxs("div", {
2265
- className: "font-bold flex items-center gap-x-2",
2266
- children: ["Summary", /* @__PURE__ */ jsx("button", {
2267
- type: "button",
2268
- "aria-label": "General log",
2269
- onClick: () => setSublogOpen(generalLogPath),
2270
- className: "link",
2271
- children: /* @__PURE__ */ jsx(DocumentIcon, { className: "w-4 h-4" })
2272
- })]
2273
- }),
2274
- /* @__PURE__ */ jsx("div", {
2275
- className: "mx-4 mb-4 overflow-y-auto",
2276
- children: summarySidebar
2277
- })
2278
- ]
2279
- }),
2280
- /* @__PURE__ */ jsxs("div", {
2281
- className: cx(preview ? "w-full" : "flex-1 min-w-0 mt-2", "h-full flex flex-col gap-y-2"),
2282
- children: [
2283
- !preview && /* @__PURE__ */ jsxs("div", {
2284
- className: "panel w-full border rounded-lg px-4 py-2 flex flex-wrap items-center gap-x-16 gap-y-2",
2285
- children: [
2286
- /* @__PURE__ */ jsxs("div", {
2287
- className: "flex flex-col",
2288
- children: [/* @__PURE__ */ jsx("div", {
2289
- className: "text-xs",
2290
- children: dagStrings.workflow
2291
- }), /* @__PURE__ */ jsx("div", {
2292
- className: "text-sm font-semibold flex items-center gap-x-1",
2293
- children: workflowName
2294
- })]
2295
- }),
2296
- /* @__PURE__ */ jsxs("div", {
2297
- className: "flex flex-col",
2298
- children: [/* @__PURE__ */ jsx("div", {
2299
- className: "text-xs",
2300
- children: dagStrings.run
2301
- }), /* @__PURE__ */ jsx("div", {
2302
- className: "text-sm font-semibold flex items-center gap-x-1",
2303
- children: runNumber
2304
- })]
2305
- }),
2306
- /* @__PURE__ */ jsxs("div", {
2307
- className: "flex flex-col",
2308
- children: [/* @__PURE__ */ jsx("div", {
2309
- className: "text-xs",
2310
- children: dagStrings.status
2311
- }), /* @__PURE__ */ jsx("div", {
2312
- className: "text-sm font-semibold flex items-center gap-x-1",
2313
- children: /* @__PURE__ */ jsxs(TooltipInfo, {
2314
- className: "flex items-center gap-x-1",
2315
- text: dagStrings.statusReason(run.statusReason) ?? "",
2316
- children: [/* @__PURE__ */ jsx(Indicator, { status: runStatus }), dagStrings.statusLabel(runStatus)]
2317
- })
2318
- })]
2319
- }),
2320
- /* @__PURE__ */ jsxs("div", {
2321
- className: "flex flex-col",
2322
- children: [/* @__PURE__ */ jsx("div", {
2323
- className: "text-xs",
2324
- children: dagStrings.runtime
2325
- }), /* @__PURE__ */ jsx("div", {
2326
- className: "text-sm font-semibold flex items-center gap-x-1",
2327
- children: runtime
2328
- })]
2329
- }),
2330
- /* @__PURE__ */ jsxs("div", {
2331
- className: "flex flex-col",
2332
- children: [/* @__PURE__ */ jsx("div", {
2333
- className: "text-xs",
2334
- children: dagStrings.submitted
2335
- }), /* @__PURE__ */ jsx("div", {
2336
- className: "text-sm font-semibold flex items-center gap-x-1",
2337
- children: startDt ? startDt.toLocaleString(DateTime.DATETIME_SHORT) : ""
2338
- })]
2339
- }),
2340
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
2341
- setViewMode && /* @__PURE__ */ jsx(ViewToggle, {
2342
- viewMode,
2343
- setViewMode: (mode) => {
2344
- if (mode === "dag") {
2345
- setStepsOpen(/* @__PURE__ */ new Set());
2346
- requestAnimationFrame(fitGraph);
2347
- }
2348
- setViewMode?.(mode);
2349
- }
2350
- })
2351
- ]
2352
- }),
2353
- !preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: jobs }),
2354
- viewMode === "tree" && !preview ? /* @__PURE__ */ jsx("div", {
2355
- className: "panel w-full border",
2356
- children: /* @__PURE__ */ jsx(TreeView, {
2357
- jobs,
2358
- workflowName,
2359
- runNumber,
2360
- slug: runSlug,
2361
- expandedJobs,
2362
- setExpandedJobs
2363
- })
2364
- }) : /* @__PURE__ */ jsx("div", {
2365
- ref: setContainerElement,
2366
- className: "panel w-full " + (removeBorder ? "border-none" : "border") + " overflow-hidden relative",
2367
- style: {
2368
- height: containerHeight !== null ? `${containerHeight}px` : previewHeight !== null ? `${Math.min(previewHeight, windowSize.h * .62)}px` : "62vh",
2369
- minHeight: previewHeight !== null ? 0 : 160
2370
- },
2371
- children: /* @__PURE__ */ jsx(TransformWrapper, {
2372
- ref: transformRef,
2373
- minScale,
2374
- maxScale,
2375
- initialScale: maxScale,
2376
- doubleClick: { disabled: true },
2377
- limitToBounds: false,
2378
- onInit: () => {
2379
- setTimeout(invalidateConnectors, 0);
2380
- },
2381
- onPanning: invalidateConnectors,
2382
- onPinch: invalidateConnectors,
2383
- onTransform: invalidateConnectors,
2384
- onWheel: invalidateConnectors,
2385
- onZoom: invalidateConnectors,
2386
- children: () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
2387
- className: "absolute z-10 top-2 right-2 flex flex-col items-end gap-y-1",
2388
- children: [
2389
- hasExpandable && /* @__PURE__ */ jsx("button", {
2390
- type: "button",
2391
- className: "btn btn-info cursor-pointer h-6",
2392
- onClick: toggleExpandAll,
2393
- children: allExpanded ? dagStrings.collapseAll : dagStrings.expandAll
2394
- }),
2395
- /* @__PURE__ */ jsx("button", {
2396
- type: "button",
2397
- className: "btn btn-info w-21 cursor-pointer h-6",
2398
- "aria-label": "Reset view",
2399
- onClick: () => {
2400
- if ((executedJobsPath[executedJobsPathIdx]?.length ?? 0) > 0) {
2401
- resetGraph();
2402
- setExecutedJobsPath([[], ...executedJobsPath.slice(executedJobsPathIdx, 10)]);
2403
- setExecutedJobsPathIdx(0);
2404
- } else fitGraph(FIT_ANIMATION_MS);
2405
- },
2406
- children: "Reset View"
2407
- }),
2408
- executedJobsPath.length > 1 && /* @__PURE__ */ jsxs("div", {
2409
- className: "flex",
2410
- children: [/* @__PURE__ */ jsx("button", {
2411
- type: "button",
2412
- onClick: () => {
2413
- if (executedJobsPath.length > executedJobsPathIdx + 1) {
2414
- resetGraph();
2415
- setExecutedJobsPathIdx(executedJobsPathIdx + 1);
2416
- }
2417
- },
2418
- className: "btn btn-info mr-1 cursor-pointer w-10 h-6",
2419
- children: "<"
2420
- }), /* @__PURE__ */ jsx("button", {
2421
- type: "button",
2422
- onClick: () => {
2423
- if (executedJobsPathIdx - 1 >= 0) {
2424
- resetGraph();
2425
- setExecutedJobsPathIdx(executedJobsPathIdx - 1);
2426
- }
2427
- },
2428
- className: "btn btn-info cursor-pointer w-10 h-6",
2429
- children: ">"
2430
- })]
2431
- })
2432
- ]
2433
- }), /* @__PURE__ */ jsx(TransformComponent, { children: /* @__PURE__ */ jsx("div", {
2434
- ref: graphContentRef,
2435
- className: "flex relative mr-[8000px]",
2436
- style: { minHeight: 4e3 },
2437
- children: /* @__PURE__ */ jsx(Subgraph, {
2438
- jobs,
2439
- pathPrefix: pathAppend,
2440
- depth: 0,
2441
- preview,
2442
- animT,
2443
- expandedSubworkflows,
2444
- toggleSubworkflow,
2445
- openInNewGraph,
2446
- setSublogOpen,
2447
- invalidateConnectors,
2448
- requestFit: animatedFit,
2449
- stepsOpen,
2450
- toggleSteps,
2451
- matrixOpen,
2452
- toggleMatrix,
2453
- onReady: fitGraph
2454
- }, `sg-${executedJobsPathIdx}-${pathAppend}`)
2455
- }) })] })
2456
- })
2457
- })
2458
- ]
2459
- }),
2460
- /* @__PURE__ */ jsx(BareModal, {
2461
- open: sublogOpen !== "",
2462
- onClose: () => setSublogOpen(""),
2463
- align: "center",
2464
- className: cx(modalPanelClasses, "max-h-full overflow-y-auto p-4 sm:p-6"),
2465
- children: /* @__PURE__ */ jsx("div", {
2466
- className: "flex w-full",
2467
- children: sublogsLoading && (!sublogs || !runNumber) ? /* @__PURE__ */ jsx(Loader, {
2468
- text: dagStrings.loadingLogs,
2469
- size: 70,
2470
- textSize: 18
2471
- }) : preview ? /* @__PURE__ */ jsxs("div", { children: [slots.yamlViewer?.({
2472
- value: currentSublog,
2473
- height: window.innerHeight / 1.4,
2474
- width: window.innerWidth / 1.4,
2475
- contentKey: "editor_" + sublogOpen
2476
- }) ?? /* @__PURE__ */ jsx("pre", {
2477
- className: "h-full overflow-auto text-xs p-4",
2478
- style: {
2479
- height: window.innerHeight / 1.4,
2480
- width: window.innerWidth / 1.4
2481
- },
2482
- children: currentSublog
2483
- }), /* @__PURE__ */ jsxs("div", {
2484
- className: "flex theme-panel select-none items-center gap-x-2 p-2",
2485
- children: [/* @__PURE__ */ jsx("div", {
2486
- className: "w-full flex flex-row gap-x-2 items-center",
2487
- children: /* @__PURE__ */ jsx("div", {
2488
- className: "flex gap-x-2 w-full",
2489
- children: additionalBottomLeftBarComponents
2490
- })
2491
- }), additionalBottomRightBarComponents]
2492
- })] }) : /* @__PURE__ */ jsx(LogViewer, {
2493
- log: currentSublog?.length > 1e3 ? currentSublog.slice(-1e5) : currentSublog,
2494
- height: windowSize.h / 1.2,
2495
- width: windowSize.w / 1.2,
2496
- hideExpand: true,
2497
- additionalBottomLeftBarComponents,
2498
- additionalBottomRightBarComponents,
2499
- enableWorkflowCommands: true
2500
- })
2501
- })
2502
- })
2503
- ]
2504
- });
2505
- }
2506
- function DependencyGraphPreview(inputs) {
2507
- const ymlJobs = inputs.yml?.["jobs"];
2508
- return /* @__PURE__ */ jsx(DependencyGraph, {
2509
- run: {
2510
- executedJobs: addCleanupSteps({ jobs: isJobRecord(ymlJobs) ? ymlJobs : {} }),
2511
- number: 0,
2512
- workflowName: ""
2513
- },
2514
- preview: true,
2515
- initialScale: inputs.initialScale || 1,
2516
- removeBorder: inputs.removeBorder
2517
- });
2518
- }
2519
- //#endregion
1
+ import DependencyGraph, { DependencyGraphPreview } from "./DependencyGraph.js";
2520
2
  export { DependencyGraph, DependencyGraphPreview };