Skip to content

Commit e9fbfaa

Browse files
authored
Merge pull request #290 from najeebkp/integrate-workflow-visualizer
feat:integrate orkes-workflow-visualizer
2 parents c105719 + 46ac8e2 commit e9fbfaa

File tree

8 files changed

+141
-53
lines changed

8 files changed

+141
-53
lines changed

.github/workflows/ci.yml

+13-14
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ name: CI
33
on:
44
push:
55
paths-ignore:
6-
- 'conductor-clients/**'
6+
- "conductor-clients/**"
77
pull_request:
88
paths-ignore:
9-
- 'conductor-clients/**'
9+
- "conductor-clients/**"
1010

1111
jobs:
1212
build:
@@ -21,8 +21,8 @@ jobs:
2121
- name: Set up Zulu JDK 17
2222
uses: actions/setup-java@v3
2323
with:
24-
distribution: 'zulu'
25-
java-version: '17'
24+
distribution: "zulu"
25+
java-version: "17"
2626
- name: Cache SonarCloud packages
2727
uses: actions/cache@v3
2828
with:
@@ -53,20 +53,20 @@ jobs:
5353
uses: mikepenz/action-junit-report@v3
5454
if: always()
5555
with:
56-
report_paths: '**/build/test-results/test/TEST-*.xml'
56+
report_paths: "**/build/test-results/test/TEST-*.xml"
5757
- name: Upload build artifacts
5858
uses: actions/upload-artifact@v3
5959
with:
6060
name: build-artifacts
61-
path: '**/build/reports'
61+
path: "**/build/reports"
6262
- name: Store Buildscan URL
6363
uses: actions/upload-artifact@v3
6464
with:
6565
name: build-scan
66-
path: 'buildscan.log'
66+
path: "buildscan.log"
6767
build-ui:
6868
runs-on: ubuntu-latest
69-
container: cypress/browsers:node14.17.6-chrome100-ff98
69+
container: cypress/browsers:node-22.11.0-chrome-130.0.6723.116-1-ff-132.0.1-edge-130.0.2849.68-1
7070
defaults:
7171
run:
7272
working-directory: ui
@@ -81,15 +81,15 @@ jobs:
8181

8282
- name: Run E2E Tests
8383
uses: cypress-io/github-action@v4
84-
with:
84+
with:
8585
working-directory: ui
8686
install: false
8787
start: yarn run serve-build
88-
wait-on: 'http://localhost:5000'
89-
88+
wait-on: "http://localhost:5000"
89+
9090
- name: Run Component Tests
9191
uses: cypress-io/github-action@v4
92-
with:
92+
with:
9393
working-directory: ui
9494
install: false
9595
component: true
@@ -100,11 +100,10 @@ jobs:
100100
with:
101101
name: cypress-screenshots
102102
path: ui/cypress/screenshots
103-
103+
104104
- name: Archive test videos
105105
uses: actions/upload-artifact@v3
106106
if: always()
107107
with:
108108
name: cypress-videos
109109
path: ui/cypress/videos
110-

ui/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020
"moment": "^2.29.2",
2121
"monaco-editor": "^0.44.0",
2222
"node-forge": "^1.3.0",
23+
"orkes-workflow-visualizer": "^1.0.0",
2324
"parse-svg-path": "^0.1.2",
2425
"prop-types": "^15.7.2",
25-
"react": "^16.8.0",
26+
"react": "^18.3.1",
2627
"react-cron-generator": "^1.3.5",
2728
"react-data-table-component": "^6.11.8",
28-
"react-dom": "^16.8.0",
29+
"react-dom": "^18.3.1",
2930
"react-helmet": "^6.1.0",
3031
"react-is": "^17.0.2",
3132
"react-query": "^3.19.4",

ui/src/components/diagram/WorkflowDAG.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ export default class WorkflowDAG {
580580
return this.taskResultsById.get(taskPointer.id);
581581
} else {
582582
const node = this.graph.node(taskPointer.ref);
583-
return _.last(node.taskResults);
583+
return _.last(node?.taskResults);
584584
}
585585
}
586586
}

ui/src/pages/definition/WorkflowDefinition.jsx

+33-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
useWorkflowNamesAndVersions,
1313
} from "../../data/workflow";
1414
import WorkflowDAG from "../../components/diagram/WorkflowDAG";
15-
import WorkflowGraph from "../../components/diagram/WorkflowGraph";
1615
import ResetConfirmationDialog from "./ResetConfirmationDialog";
1716
import {
1817
configureMonaco,
@@ -23,6 +22,7 @@ import SaveWorkflowDialog from "./SaveWorkflowDialog";
2322
import update from "immutability-helper";
2423
import { usePushHistory } from "../../components/NavLink";
2524
import { timestampRenderer } from "../../utils/helpers";
25+
import { WorkflowVisualizer } from "orkes-workflow-visualizer";
2626

2727
import {
2828
KeyboardArrowLeftRounded,
@@ -67,8 +67,8 @@ const useStyles = makeStyles({
6767
gap: 8,
6868
},
6969
editorLineDecorator: {
70-
backgroundColor: "rgb(45, 45, 45, 0.1)"
71-
}
70+
backgroundColor: "rgb(45, 45, 45, 0.1)",
71+
},
7272
});
7373

7474
const actions = {
@@ -240,21 +240,26 @@ export default function Workflow() {
240240
};
241241

242242
const handleWorkflowNodeClick = (node) => {
243-
let editor = editorRef.current.getModel()
244-
let searchResult = editor.findMatches(`"taskReferenceName": "${node.ref}"`)
245-
if (searchResult.length){
246-
editorRef.current.revealLineInCenter(searchResult[0]?.range?.startLineNumber, 0);
247-
setDecorations(editorRef.current.deltaDecorations(decorations, [
248-
{
249-
range: searchResult[0]?.range,
250-
options: {
251-
isWholeLine: true,
252-
inlineClassName: classes.editorLineDecorator
253-
}
254-
}
255-
]))
243+
let editor = editorRef.current.getModel();
244+
let searchResult = editor.findMatches(`"taskReferenceName": "${node.ref}"`);
245+
if (searchResult.length) {
246+
editorRef.current.revealLineInCenter(
247+
searchResult[0]?.range?.startLineNumber,
248+
0
249+
);
250+
setDecorations(
251+
editorRef.current.deltaDecorations(decorations, [
252+
{
253+
range: searchResult[0]?.range,
254+
options: {
255+
isWholeLine: true,
256+
inlineClassName: classes.editorLineDecorator,
257+
},
258+
},
259+
])
260+
);
256261
}
257-
}
262+
};
258263

259264
return (
260265
<>
@@ -369,8 +374,17 @@ export default function Workflow() {
369374
className={classes.resizer}
370375
onMouseDown={(e) => handleMouseDown(e)}
371376
/>
372-
<div className={classes.workflowGraph}>
373-
{dag && <WorkflowGraph dag={dag} onClick={handleWorkflowNodeClick} />}
377+
<div className={classes.workflowGraph} style={{ overflow: "scroll" }}>
378+
{dag && dag?.workflowDef && (
379+
<WorkflowVisualizer
380+
maxHeightOverride
381+
pannable
382+
zoomable
383+
zoom={0.7}
384+
data={dag?.workflowDef}
385+
onClick={(e, data) => handleWorkflowNodeClick({ ref: data?.id })}
386+
/>
387+
)}
374388
</div>
375389
</div>
376390
</>

ui/src/pages/execution/Execution.jsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export default function Execution() {
126126
const [isFullWidth, setIsFullWidth] = useState(false);
127127
const [isResizing, setIsResizing] = useState(false);
128128
const [drawerWidth, setDrawerWidth] = useState(INIT_DRAWER_WIDTH);
129+
const [selectedNode, setSelectedNode] = useState();
129130

130131
const [tabIndex, setTabIndex] = useQueryState("tabIndex", 0);
131132
const [selectedTaskRison, setSelectedTaskRison] = useQueryState("task", "");
@@ -222,7 +223,12 @@ export default function Execution() {
222223
</div>
223224
)}
224225
<div className={classes.frItem}>
225-
<NavLink newTab path={`/workflowDef/${execution.workflowName}`}>Definition</NavLink>
226+
<NavLink
227+
newTab
228+
path={`/workflowDef/${execution.workflowName}`}
229+
>
230+
Definition
231+
</NavLink>
226232
</div>
227233
<SecondaryButton onClick={refresh} style={{ marginRight: 10 }}>
228234
Refresh
@@ -260,6 +266,7 @@ export default function Execution() {
260266
execution={execution}
261267
setSelectedTask={setSelectedTask}
262268
selectedTask={selectedTask}
269+
setSelectedNode={setSelectedNode}
263270
/>
264271
)}
265272
{tabIndex === 1 && <ExecutionSummary execution={execution} />}
@@ -302,6 +309,8 @@ export default function Execution() {
302309
className={classes.rightPanel}
303310
selectedTask={selectedTask}
304311
dag={dag}
312+
execution={execution}
313+
selectedNode={selectedNode}
305314
onTaskChange={setSelectedTask}
306315
/>
307316
</div>

ui/src/pages/execution/RightPanel.jsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import TaskLogs from "./TaskLogs";
88
import { makeStyles } from "@material-ui/styles";
99
import _ from "lodash";
1010
import TaskPollData from "./TaskPollData";
11+
import {
12+
pendingTaskSelection,
13+
taskWithLatestIteration,
14+
} from "../../utils/helpers";
1115

1216
const useStyles = makeStyles({
1317
banner: {
@@ -24,7 +28,13 @@ const useStyles = makeStyles({
2428
},
2529
});
2630

27-
export default function RightPanel({ selectedTask, dag, onTaskChange }) {
31+
export default function RightPanel({
32+
selectedTask,
33+
dag,
34+
execution,
35+
onTaskChange,
36+
selectedNode,
37+
}) {
2838
const [tabIndex, setTabIndex] = useState("summary");
2939

3040
const classes = useStyles();
@@ -33,10 +43,11 @@ export default function RightPanel({ selectedTask, dag, onTaskChange }) {
3343
setTabIndex("summary"); // Reset to Status Tab on ref change
3444
}, [selectedTask]);
3545

36-
const taskResult = useMemo(
37-
() => dag && dag.resolveTaskResult(selectedTask),
38-
[dag, selectedTask]
39-
);
46+
const taskResult =
47+
selectedNode?.data?.task?.executionData?.status === "PENDING"
48+
? pendingTaskSelection(selectedNode?.data?.task)
49+
: taskWithLatestIteration(execution?.tasks, selectedTask?.ref);
50+
4051
const dfOptions = useMemo(
4152
() => dag && dag.getSiblings(selectedTask),
4253
[dag, selectedTask]

ui/src/pages/execution/TaskDetails.jsx

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import React, { useState } from "react";
1+
import { useState } from "react";
22
import { Tabs, Tab, Paper } from "../../components";
33
import Timeline from "./Timeline";
44
import TaskList from "./TaskList";
5-
import WorkflowGraph from "../../components/diagram/WorkflowGraph";
65
import { makeStyles } from "@material-ui/styles";
6+
import { WorkflowVisualizer } from "orkes-workflow-visualizer";
7+
import {
8+
pendingTaskSelection,
9+
taskWithLatestIteration,
10+
} from "../../utils/helpers";
711

812
const useStyles = makeStyles({
913
taskWrapper: {
@@ -18,6 +22,7 @@ export default function TaskDetails({
1822
dag,
1923
selectedTask,
2024
setSelectedTask,
25+
setSelectedNode,
2126
}) {
2227
const [tabIndex, setTabIndex] = useState(0);
2328
const classes = useStyles();
@@ -32,11 +37,23 @@ export default function TaskDetails({
3237
</Tabs>
3338

3439
{tabIndex === 0 && (
35-
<WorkflowGraph
36-
selectedTask={selectedTask}
40+
<WorkflowVisualizer
41+
maxHeightOverride
42+
pannable
43+
zoomable
44+
zoom={0.7}
45+
data={dag?.execution}
3746
executionMode={true}
38-
dag={dag}
39-
onClick={setSelectedTask}
47+
onClick={(e, data) => {
48+
const selectedTaskRefName =
49+
data?.data?.task?.executionData?.status === "PENDING"
50+
? pendingTaskSelection(data?.data?.task)?.workflowTask
51+
?.taskReferenceName
52+
: taskWithLatestIteration(execution?.tasks, data?.id)
53+
?.referenceTaskName;
54+
setSelectedNode(data);
55+
setSelectedTask({ ref: selectedTaskRefName });
56+
}}
4057
/>
4158
)}
4259
{tabIndex === 1 && (

ui/src/utils/helpers.js

+42-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { format, formatDuration, intervalToDuration } from "date-fns";
22
import _ from "lodash";
3-
import packageJson from '../../package.json';
3+
import packageJson from "../../package.json";
4+
import _nth from "lodash/nth";
45

56
export function timestampRenderer(date) {
67
if (_.isNil(date)) return null;
@@ -91,9 +92,45 @@ export function isEmptyIterable(iterable) {
9192
}
9293

9394
export function getBasename() {
94-
let basename = '/';
95-
try{
95+
let basename = "/";
96+
try {
9697
basename = new URL(packageJson.homepage).pathname;
97-
} catch(e) {}
98-
return _.isEmpty(basename) ? '/' : basename;
98+
} catch (e) {}
99+
return _.isEmpty(basename) ? "/" : basename;
99100
}
101+
102+
export const taskWithLatestIteration = (tasksList, taskReferenceName) => {
103+
const filteredTasks = tasksList?.filter(
104+
(task) =>
105+
task?.workflowTask?.taskReferenceName === taskReferenceName ||
106+
task?.referenceTaskName === taskReferenceName
107+
);
108+
109+
if (filteredTasks && filteredTasks.length === 1) {
110+
// task without any retry/iteration
111+
return _nth(filteredTasks, 0);
112+
} else if (filteredTasks && filteredTasks.length > 1) {
113+
const result = filteredTasks.reduce(
114+
(acc, task, idx) => {
115+
if (task?.seq && acc?.seqNumber < Number(task.seq)) {
116+
return { seqNumber: Number(task.seq), idx };
117+
}
118+
return acc;
119+
},
120+
{ seqNumber: 0, idx: -1 }
121+
);
122+
123+
if (result?.idx > -1) {
124+
return _nth(filteredTasks, result.idx);
125+
}
126+
}
127+
return undefined;
128+
};
129+
130+
export const pendingTaskSelection = (task) => {
131+
const result = {
132+
...task?.executionData,
133+
workflowTask: task,
134+
};
135+
return result;
136+
};

0 commit comments

Comments
 (0)