diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44e8dfaba..c557d4e87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: push: paths-ignore: - - 'conductor-clients/**' + - "conductor-clients/**" pull_request: paths-ignore: - - 'conductor-clients/**' + - "conductor-clients/**" jobs: build: @@ -21,8 +21,8 @@ jobs: - name: Set up Zulu JDK 17 uses: actions/setup-java@v3 with: - distribution: 'zulu' - java-version: '17' + distribution: "zulu" + java-version: "17" - name: Cache SonarCloud packages uses: actions/cache@v3 with: @@ -53,20 +53,20 @@ jobs: uses: mikepenz/action-junit-report@v3 if: always() with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: "**/build/test-results/test/TEST-*.xml" - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-artifacts - path: '**/build/reports' + path: "**/build/reports" - name: Store Buildscan URL uses: actions/upload-artifact@v3 with: name: build-scan - path: 'buildscan.log' + path: "buildscan.log" build-ui: runs-on: ubuntu-latest - container: cypress/browsers:node14.17.6-chrome100-ff98 + container: cypress/browsers:node-22.11.0-chrome-130.0.6723.116-1-ff-132.0.1-edge-130.0.2849.68-1 defaults: run: working-directory: ui @@ -81,15 +81,15 @@ jobs: - name: Run E2E Tests uses: cypress-io/github-action@v4 - with: + with: working-directory: ui install: false start: yarn run serve-build - wait-on: 'http://localhost:5000' - + wait-on: "http://localhost:5000" + - name: Run Component Tests uses: cypress-io/github-action@v4 - with: + with: working-directory: ui install: false component: true @@ -100,11 +100,10 @@ jobs: with: name: cypress-screenshots path: ui/cypress/screenshots - + - name: Archive test videos uses: actions/upload-artifact@v3 if: always() with: name: cypress-videos path: ui/cypress/videos - diff --git a/ui/package.json b/ui/package.json index 623a5c6dc..6b384d93e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -20,12 +20,13 @@ "moment": "^2.29.2", "monaco-editor": "^0.44.0", "node-forge": "^1.3.0", + "orkes-workflow-visualizer": "^1.0.0", "parse-svg-path": "^0.1.2", "prop-types": "^15.7.2", - "react": "^16.8.0", + "react": "^18.3.1", "react-cron-generator": "^1.3.5", "react-data-table-component": "^6.11.8", - "react-dom": "^16.8.0", + "react-dom": "^18.3.1", "react-helmet": "^6.1.0", "react-is": "^17.0.2", "react-query": "^3.19.4", diff --git a/ui/src/components/diagram/WorkflowDAG.js b/ui/src/components/diagram/WorkflowDAG.js index cabf35fa0..684fec032 100644 --- a/ui/src/components/diagram/WorkflowDAG.js +++ b/ui/src/components/diagram/WorkflowDAG.js @@ -580,7 +580,7 @@ export default class WorkflowDAG { return this.taskResultsById.get(taskPointer.id); } else { const node = this.graph.node(taskPointer.ref); - return _.last(node.taskResults); + return _.last(node?.taskResults); } } } diff --git a/ui/src/pages/definition/WorkflowDefinition.jsx b/ui/src/pages/definition/WorkflowDefinition.jsx index 77963f518..7f18244ca 100644 --- a/ui/src/pages/definition/WorkflowDefinition.jsx +++ b/ui/src/pages/definition/WorkflowDefinition.jsx @@ -12,7 +12,6 @@ import { useWorkflowNamesAndVersions, } from "../../data/workflow"; import WorkflowDAG from "../../components/diagram/WorkflowDAG"; -import WorkflowGraph from "../../components/diagram/WorkflowGraph"; import ResetConfirmationDialog from "./ResetConfirmationDialog"; import { configureMonaco, @@ -23,6 +22,7 @@ import SaveWorkflowDialog from "./SaveWorkflowDialog"; import update from "immutability-helper"; import { usePushHistory } from "../../components/NavLink"; import { timestampRenderer } from "../../utils/helpers"; +import { WorkflowVisualizer } from "orkes-workflow-visualizer"; import { KeyboardArrowLeftRounded, @@ -67,8 +67,8 @@ const useStyles = makeStyles({ gap: 8, }, editorLineDecorator: { - backgroundColor: "rgb(45, 45, 45, 0.1)" - } + backgroundColor: "rgb(45, 45, 45, 0.1)", + }, }); const actions = { @@ -240,21 +240,26 @@ export default function Workflow() { }; const handleWorkflowNodeClick = (node) => { - let editor = editorRef.current.getModel() - let searchResult = editor.findMatches(`"taskReferenceName": "${node.ref}"`) - if (searchResult.length){ - editorRef.current.revealLineInCenter(searchResult[0]?.range?.startLineNumber, 0); - setDecorations(editorRef.current.deltaDecorations(decorations, [ - { - range: searchResult[0]?.range, - options: { - isWholeLine: true, - inlineClassName: classes.editorLineDecorator - } - } - ])) + let editor = editorRef.current.getModel(); + let searchResult = editor.findMatches(`"taskReferenceName": "${node.ref}"`); + if (searchResult.length) { + editorRef.current.revealLineInCenter( + searchResult[0]?.range?.startLineNumber, + 0 + ); + setDecorations( + editorRef.current.deltaDecorations(decorations, [ + { + range: searchResult[0]?.range, + options: { + isWholeLine: true, + inlineClassName: classes.editorLineDecorator, + }, + }, + ]) + ); } - } + }; return ( <> @@ -369,8 +374,17 @@ export default function Workflow() { className={classes.resizer} onMouseDown={(e) => handleMouseDown(e)} /> -
- {dag && } +
+ {dag && dag?.workflowDef && ( + handleWorkflowNodeClick({ ref: data?.id })} + /> + )}
diff --git a/ui/src/pages/execution/Execution.jsx b/ui/src/pages/execution/Execution.jsx index 656713697..b05970bb5 100644 --- a/ui/src/pages/execution/Execution.jsx +++ b/ui/src/pages/execution/Execution.jsx @@ -126,6 +126,7 @@ export default function Execution() { const [isFullWidth, setIsFullWidth] = useState(false); const [isResizing, setIsResizing] = useState(false); const [drawerWidth, setDrawerWidth] = useState(INIT_DRAWER_WIDTH); + const [selectedNode, setSelectedNode] = useState(); const [tabIndex, setTabIndex] = useQueryState("tabIndex", 0); const [selectedTaskRison, setSelectedTaskRison] = useQueryState("task", ""); @@ -222,7 +223,12 @@ export default function Execution() { )}
- Definition + + Definition +
Refresh @@ -260,6 +266,7 @@ export default function Execution() { execution={execution} setSelectedTask={setSelectedTask} selectedTask={selectedTask} + setSelectedNode={setSelectedNode} /> )} {tabIndex === 1 && } @@ -302,6 +309,8 @@ export default function Execution() { className={classes.rightPanel} selectedTask={selectedTask} dag={dag} + execution={execution} + selectedNode={selectedNode} onTaskChange={setSelectedTask} /> diff --git a/ui/src/pages/execution/RightPanel.jsx b/ui/src/pages/execution/RightPanel.jsx index 7e2e0572b..b3dc7b79e 100644 --- a/ui/src/pages/execution/RightPanel.jsx +++ b/ui/src/pages/execution/RightPanel.jsx @@ -8,6 +8,10 @@ import TaskLogs from "./TaskLogs"; import { makeStyles } from "@material-ui/styles"; import _ from "lodash"; import TaskPollData from "./TaskPollData"; +import { + pendingTaskSelection, + taskWithLatestIteration, +} from "../../utils/helpers"; const useStyles = makeStyles({ banner: { @@ -24,7 +28,13 @@ const useStyles = makeStyles({ }, }); -export default function RightPanel({ selectedTask, dag, onTaskChange }) { +export default function RightPanel({ + selectedTask, + dag, + execution, + onTaskChange, + selectedNode, +}) { const [tabIndex, setTabIndex] = useState("summary"); const classes = useStyles(); @@ -33,10 +43,11 @@ export default function RightPanel({ selectedTask, dag, onTaskChange }) { setTabIndex("summary"); // Reset to Status Tab on ref change }, [selectedTask]); - const taskResult = useMemo( - () => dag && dag.resolveTaskResult(selectedTask), - [dag, selectedTask] - ); + const taskResult = + selectedNode?.data?.task?.executionData?.status === "PENDING" + ? pendingTaskSelection(selectedNode?.data?.task) + : taskWithLatestIteration(execution?.tasks, selectedTask?.ref); + const dfOptions = useMemo( () => dag && dag.getSiblings(selectedTask), [dag, selectedTask] diff --git a/ui/src/pages/execution/TaskDetails.jsx b/ui/src/pages/execution/TaskDetails.jsx index 6948f645f..8cbfa192f 100644 --- a/ui/src/pages/execution/TaskDetails.jsx +++ b/ui/src/pages/execution/TaskDetails.jsx @@ -1,9 +1,13 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { Tabs, Tab, Paper } from "../../components"; import Timeline from "./Timeline"; import TaskList from "./TaskList"; -import WorkflowGraph from "../../components/diagram/WorkflowGraph"; import { makeStyles } from "@material-ui/styles"; +import { WorkflowVisualizer } from "orkes-workflow-visualizer"; +import { + pendingTaskSelection, + taskWithLatestIteration, +} from "../../utils/helpers"; const useStyles = makeStyles({ taskWrapper: { @@ -18,6 +22,7 @@ export default function TaskDetails({ dag, selectedTask, setSelectedTask, + setSelectedNode, }) { const [tabIndex, setTabIndex] = useState(0); const classes = useStyles(); @@ -32,11 +37,23 @@ export default function TaskDetails({ {tabIndex === 0 && ( - { + const selectedTaskRefName = + data?.data?.task?.executionData?.status === "PENDING" + ? pendingTaskSelection(data?.data?.task)?.workflowTask + ?.taskReferenceName + : taskWithLatestIteration(execution?.tasks, data?.id) + ?.referenceTaskName; + setSelectedNode(data); + setSelectedTask({ ref: selectedTaskRefName }); + }} /> )} {tabIndex === 1 && ( diff --git a/ui/src/utils/helpers.js b/ui/src/utils/helpers.js index 11bf45e94..407732f63 100644 --- a/ui/src/utils/helpers.js +++ b/ui/src/utils/helpers.js @@ -1,6 +1,7 @@ import { format, formatDuration, intervalToDuration } from "date-fns"; import _ from "lodash"; -import packageJson from '../../package.json'; +import packageJson from "../../package.json"; +import _nth from "lodash/nth"; export function timestampRenderer(date) { if (_.isNil(date)) return null; @@ -91,9 +92,45 @@ export function isEmptyIterable(iterable) { } export function getBasename() { - let basename = '/'; - try{ + let basename = "/"; + try { basename = new URL(packageJson.homepage).pathname; - } catch(e) {} - return _.isEmpty(basename) ? '/' : basename; + } catch (e) {} + return _.isEmpty(basename) ? "/" : basename; } + +export const taskWithLatestIteration = (tasksList, taskReferenceName) => { + const filteredTasks = tasksList?.filter( + (task) => + task?.workflowTask?.taskReferenceName === taskReferenceName || + task?.referenceTaskName === taskReferenceName + ); + + if (filteredTasks && filteredTasks.length === 1) { + // task without any retry/iteration + return _nth(filteredTasks, 0); + } else if (filteredTasks && filteredTasks.length > 1) { + const result = filteredTasks.reduce( + (acc, task, idx) => { + if (task?.seq && acc?.seqNumber < Number(task.seq)) { + return { seqNumber: Number(task.seq), idx }; + } + return acc; + }, + { seqNumber: 0, idx: -1 } + ); + + if (result?.idx > -1) { + return _nth(filteredTasks, result.idx); + } + } + return undefined; +}; + +export const pendingTaskSelection = (task) => { + const result = { + ...task?.executionData, + workflowTask: task, + }; + return result; +};