Skip to content

Commit 28a4af9

Browse files
Make Node UI collapsible (#88)
* make stdout/stderr capture optional * use `Accordion` * collapse stdout/stderr * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 46ea5c6 commit 28a4af9

9 files changed

+97
-73
lines changed

app/eslint.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import js from "@eslint/js";
2-
import globals from "globals";
32
import reactHooks from "eslint-plugin-react-hooks";
43
import reactRefresh from "eslint-plugin-react-refresh";
4+
import globals from "globals";
55
import tseslint from "typescript-eslint";
66

77
export default tseslint.config(

app/src/App.tsx

+19-19
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
1-
// import { initialNodes, initialEdges } from './initialElements.js';
2-
import ELK from "elkjs/lib/elk.bundled.js";
3-
import { useEffect, useState, useMemo, useRef } from "react";
41
import {
52
Background,
6-
ReactFlow,
7-
ReactFlowProvider,
8-
useNodesState,
9-
useEdgesState,
103
Controls,
114
Panel,
125
Position,
6+
ReactFlow,
7+
ReactFlowProvider,
8+
useEdgesState,
9+
useNodesState,
1310
} from "@xyflow/react";
11+
// import { initialNodes, initialEdges } from './initialElements.js';
12+
import ELK from "elkjs/lib/elk.bundled.js";
13+
import { useEffect, useMemo, useRef, useState } from "react";
1414

15-
import InputGroup from "react-bootstrap/InputGroup";
1615
import Form from "react-bootstrap/Form";
16+
import InputGroup from "react-bootstrap/InputGroup";
1717

1818
import "@xyflow/react/dist/style.css";
19-
import { Card, Button } from "react-bootstrap";
19+
import { Button, Card } from "react-bootstrap";
2020
import Table from "react-bootstrap/Table";
2121
import {
22-
FaPlus,
23-
FaMinus,
24-
FaArrowRight,
2522
FaArrowDown,
26-
FaRedo,
23+
FaArrowRight,
24+
FaMinus,
2725
FaPlay,
26+
FaPlus,
27+
FaRedo,
2828
} from "react-icons/fa";
2929

30-
import GraphStateNode from "./GraphStateNode";
31-
import GraphNodeGroup from "./GraphNodeGroup";
3230
import GraphContext from "./GraphContext";
31+
import GraphNodeGroup from "./GraphNodeGroup";
32+
import GraphStateNode from "./GraphStateNode";
3333
import "./App.css";
3434

35-
import { Jobs, WorkerInfo } from "./types";
3635
import JobStatusTable from "./JobsOverview";
36+
import type { Jobs, WorkerInfo } from "./types";
3737

3838
const elk = new ELK();
3939

@@ -254,7 +254,7 @@ function LayoutFlow({
254254
useEffect(() => {
255255
if (elkGraph) {
256256
// Recursively extract nodes from ELK graph
257-
const extractNodesAndEdges = (graph, currentDepth: number = 0) => {
257+
const extractNodesAndEdges = (graph, currentDepth = 0) => {
258258
const hiddenNodes = excludedNodes[graph.id] || [];
259259
const resultNodes = [];
260260
const resultEdges = [];
@@ -269,7 +269,7 @@ function LayoutFlow({
269269
if (child.children) {
270270
// Subgraph: Recursively process children
271271

272-
let node = {
272+
const node = {
273273
id: child.id,
274274
position: {
275275
x: child.x,
@@ -299,7 +299,7 @@ function LayoutFlow({
299299
resultEdges.push(...subEdges);
300300
} else {
301301
// Individual node
302-
let node = {
302+
const node = {
303303
id: child.id,
304304
position: {
305305
x: child.x,

app/src/GraphNodeGroup.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { useCallback, useContext, useEffect } from "react";
21
import { Handle } from "@xyflow/react";
2+
import { useCallback, useContext } from "react";
3+
import { Button } from "react-bootstrap";
34
import Card from "react-bootstrap/Card";
4-
import { GraphNode } from "./types";
55
import { BsArrowsAngleContract, BsArrowsAngleExpand } from "react-icons/bs";
6-
import { Button } from "react-bootstrap";
76
import GraphContext from "./GraphContext";
7+
import type { GraphNode } from "./types";
88

99
interface GraphStateNodeProps {
1010
data: {

app/src/GraphStateNode.tsx

+59-42
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { useState, useEffect, useContext } from "react";
21
import { Handle } from "@xyflow/react";
3-
import Card from "react-bootstrap/Card";
4-
import { FaSpinner } from "react-icons/fa";
2+
import { useContext, useEffect, useState } from "react";
3+
import Accordion from "react-bootstrap/Accordion";
54
import Button from "react-bootstrap/Button";
5+
import Card from "react-bootstrap/Card";
66
import Modal from "react-bootstrap/Modal";
7+
import { FaSpinner } from "react-icons/fa";
78
import Markdown from "react-markdown";
8-
import { GraphNode } from "./types";
99
import GraphContext from "./GraphContext";
10+
import type { GraphNode } from "./types";
1011

1112
interface GraphStateNodeProps {
1213
data: {
@@ -143,51 +144,67 @@ function GraphStateNode({ data }: GraphStateNodeProps) {
143144
<Modal.Title>{data.node.id}</Modal.Title>
144145
</Modal.Header>
145146
<Modal.Body>
146-
<Markdown>
147-
{`
148-
#### Run this Node
147+
<Accordion defaultActiveKey={["0", "1", "2", "3", "4"]} alwaysOpen>
148+
<Accordion.Item eventKey="0">
149+
<Accordion.Header>Run this Node</Accordion.Header>
150+
<Accordion.Body>
151+
<Markdown>
152+
{`
149153
~~~
150154
paraffin worker --job ${data.node.id} --experiment ${experiment}
151155
~~~
152-
#### DVC Stage Lock
156+
`}
157+
</Markdown>
158+
</Accordion.Body>
159+
</Accordion.Item>
160+
<Accordion.Item eventKey="1">
161+
<Accordion.Header>DVC Stage Lock</Accordion.Header>
162+
<Accordion.Body>
163+
<Markdown>
164+
{`
153165
~~~dict
154166
${JSON.stringify(data.node.lock, null, 4)}
155167
~~~
156-
#### DVC Stage Dependencies Hash
157-
~~~
158-
${data.node.deps_hash}
159-
~~~
160168
`}
161-
{/* TODO: show node-meta.json if requested */}
162-
</Markdown>
163-
{nodeData.stdout && (
164-
<>
165-
<h5>STDOUT</h5>
166-
<pre>{nodeData.stdout}</pre>
167-
</>
168-
)}
169-
{nodeData.stderr && (
170-
<>
171-
<h5>STDERR</h5>
172-
<pre>{nodeData.stderr}</pre>
173-
</>
174-
)}
175-
{nodeData.worker && (
176-
<>
177-
<h5>Worker</h5>
178-
<pre>
179-
{nodeData.worker.name}@{nodeData.worker.machine}
180-
</pre>
181-
<h5>Started At</h5>
182-
<pre>{nodeData.started_at}</pre>
183-
<h5>Finished At</h5>
184-
<pre>{nodeData.finished_at}</pre>
185-
<h5>Working Directory</h5>
186-
<pre>{nodeData.worker.cwd}</pre>
187-
<h5>PID</h5>
188-
<pre>{nodeData.worker.pid}</pre>
189-
</>
190-
)}
169+
</Markdown>
170+
</Accordion.Body>
171+
</Accordion.Item>
172+
173+
{nodeData.stdout && (
174+
<Accordion.Item eventKey="2">
175+
<Accordion.Header>STDOUT</Accordion.Header>
176+
<Accordion.Body>
177+
<pre>{nodeData.stdout}</pre>
178+
</Accordion.Body>
179+
</Accordion.Item>
180+
)}
181+
{nodeData.stderr && (
182+
<Accordion.Item eventKey="3">
183+
<Accordion.Header>STDERR</Accordion.Header>
184+
<Accordion.Body>
185+
<pre>{nodeData.stderr}</pre>
186+
</Accordion.Body>
187+
</Accordion.Item>
188+
)}
189+
{nodeData.worker && (
190+
<Accordion.Item eventKey="4">
191+
<Accordion.Header>Worker</Accordion.Header>
192+
<Accordion.Body>
193+
<pre>
194+
{nodeData.worker.name}@{nodeData.worker.machine}
195+
</pre>
196+
<h5>Started At</h5>
197+
<pre>{nodeData.started_at}</pre>
198+
<h5>Finished At</h5>
199+
<pre>{nodeData.finished_at}</pre>
200+
<h5>Working Directory</h5>
201+
<pre>{nodeData.worker.cwd}</pre>
202+
<h5>PID</h5>
203+
<pre>{nodeData.worker.pid}</pre>
204+
</Accordion.Body>
205+
</Accordion.Item>
206+
)}
207+
</Accordion>
191208
</Modal.Body>
192209
<Modal.Footer>
193210
<Button variant="secondary" onClick={handleClose}>

app/src/JobsOverview.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from "react";
2-
import { Table, Card, ProgressBar } from "react-bootstrap";
3-
import { Jobs, WorkerInfo } from "./types";
2+
import { Card, ProgressBar, Table } from "react-bootstrap";
3+
import type { Jobs, WorkerInfo } from "./types";
44

55
const JobStatusTable = ({
66
workerInfo,

app/src/menu.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Form from "react-bootstrap/Form";
21
import Dropdown from "react-bootstrap/Dropdown";
2+
import Form from "react-bootstrap/Form";
33

44
function RangeExample({
55
value,
@@ -14,7 +14,7 @@ function RangeExample({
1414
min={100}
1515
max={60000}
1616
step={100}
17-
onChange={(e) => setValue(parseInt(e.target.value))}
17+
onChange={(e) => setValue(Number.parseInt(e.target.value))}
1818
value={value}
1919
style={{ marginTop: "10px" }}
2020
/>

app/vite.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineConfig } from "vite";
21
import react from "@vitejs/plugin-react";
2+
import { defineConfig } from "vite";
33

44
// https://vite.dev/config/
55
export default defineConfig({

biome.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
"linter": {
2020
"enabled": true,
2121
"rules": {
22-
"recommended": true
22+
"recommended": true,
23+
"correctness": {
24+
"noUnusedImports": "error"
25+
}
2326
}
2427
},
2528
"javascript": {

paraffin/db.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class Job(SQLModel, table=True):
6767
experiment_id: Optional[int] = Field(foreign_key="experiment.id")
6868
stderr: str = "" # stderr output
6969
stdout: str = "" # stdout output
70+
capture_stderr: bool = True # Capture stderr output
71+
capture_stdout: bool = True # Capture stdout output
7072
started_at: Optional[datetime.datetime] = None
7173
finished_at: Optional[datetime.datetime] = None
7274
worker_id: Optional[int] = Field(foreign_key="worker.id", default=None)
@@ -323,8 +325,10 @@ def complete_job(
323325
job = results.one()
324326
job.status = status
325327
job.lock = json.dumps(lock)
326-
job.stderr = stderr
327-
job.stdout = stdout
328+
if job.capture_stderr:
329+
job.stderr = stderr
330+
if job.capture_stdout:
331+
job.stdout = stdout
328332
job.finished_at = datetime.datetime.now()
329333
# We only write the deps_hash to the database
330334
# once the job has finished successfully!

0 commit comments

Comments
 (0)