From 41fe84073f47a2ac5526339be6010409a1cc22b8 Mon Sep 17 00:00:00 2001 From: Nathan Freeman Date: Mon, 12 Aug 2024 16:46:11 -0500 Subject: [PATCH] Add layout lib for workflow dag --- package-lock.json | 31 +- package.json | 1 + .../Pipeline/_components/DagView/DagView.tsx | 555 +++++++++++++----- .../Nodes/ArgsNode/ArgsNode.module.scss | 2 +- .../DagView/Nodes/ArgsNode/ArgsNode.tsx | 20 +- .../ConditionalNode.module.scss | 2 +- .../EnvironmentNode.module.scss | 2 +- .../Nodes/EnvironmentNode/EnvironmentNode.tsx | 20 +- .../DagViewDrawer/DagViewDrawer.tsx | 127 ++++ .../_components/DagViewDrawer/index.ts | 1 + 10 files changed, 570 insertions(+), 191 deletions(-) create mode 100644 src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/DagViewDrawer.tsx create mode 100644 src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/index.ts diff --git a/package-lock.json b/package-lock.json index 027dca54..156b124c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@xyflow/react": "^12.0.4", "base-64": "^1.0.0", "bootstrap": "^4.6.0", + "elkjs": "^0.9.3", "formik": "^2.2.9", "js-cookie": "^3.0.0", "jwt-decode": "^3.1.2", @@ -9381,7 +9382,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -9787,7 +9787,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, "dependencies": { "glob": "^7.0.5", "minimatch": "^3.0.3", @@ -9807,7 +9806,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11597,6 +11595,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" + }, "node_modules/elliptic": { "version": "6.5.6", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.6.tgz", @@ -21094,7 +21097,6 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "readable-stream": "~1.0.31" @@ -30004,7 +30006,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, "engines": { "node": ">=8" } @@ -32928,7 +32929,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -32992,7 +32992,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -33007,7 +33006,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -33018,8 +33016,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", @@ -33084,7 +33081,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -33106,7 +33102,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -33133,7 +33128,6 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, "engines": { "node": ">=10" } @@ -33210,11 +33204,20 @@ "@tapis/tapis-typescript": "^0.0.37", "@tapis/tapisui-common": "file:../tapisui-common", "@tapis/tapisui-extensions-core": "file:../tapisui-extensions-core", - "react": "^18.3.1" + "@tapis/tapisui-hooks": "file:../tapisui-hooks", + "copyfiles": "^2.4.1", + "formik": "^2.2.9", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^5.2.0", + "reactstrap": "^8.9.0", + "uuid": "^7.0.2", + "yup": "^0.32.11" }, "devDependencies": { "@tapis/tapisui-extension-devtools": "file:../tapisui-extension-devtools", "@types/node": "^18.19.33", + "sass": "^1.77.6", "typescript": "^4.9.5" } }, diff --git a/package.json b/package.json index d6aa2e41..e5760d77 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@xyflow/react": "^12.0.4", "base-64": "^1.0.0", "bootstrap": "^4.6.0", + "elkjs": "^0.9.3", "formik": "^2.2.9", "js-cookie": "^3.0.0", "jwt-decode": "^3.1.2", diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx index 7a6732be..82f39137 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx @@ -1,29 +1,9 @@ -import React, { useCallback, useMemo, useState, useEffect } from 'react'; +import React, { useCallback, useMemo, useState, useLayoutEffect } from 'react'; import { EnvironmentNode, TaskNode, ArgsNode, ConditionalNode } from './Nodes'; import { Workflows } from '@tapis/tapis-typescript'; -import { Workflows as Hooks } from '@tapis/tapisui-hooks'; -import { useExtension } from 'extensions'; import styles from './DagView.module.scss'; -import { - ListItemText, - ListItemIcon, - Divider, - Chip, - Drawer, - Box, - List, - ListItem, - ListItemButton, - ListSubheader, -} from '@mui/material'; -import { - DataObject, - Share, - Add, - Bolt, - Publish, - AltRoute, -} from '@mui/icons-material'; +import { Chip } from '@mui/material'; +import { DataObject, Share, Bolt, AltRoute } from '@mui/icons-material'; import { ReactFlow, MiniMap, @@ -37,131 +17,69 @@ import { Edge, Node, Panel, + ReactFlowProvider, + useReactFlow, } from '@xyflow/react'; import { CreateTaskModal, RunPipelineModal, } from 'app/Workflows/_components/Modals'; - +import { DagViewDrawer } from '../DagViewDrawer'; +import ELK, { + ElkNode, + ElkExtendedEdge, + LayoutOptions, +} from 'elkjs/lib/elk.bundled.js'; import '@xyflow/react/dist/style.css'; -type DagViewDrawerProps = { - groupId: string; - pipelineId: string; - toggle: () => void; - open: boolean; - onClickCreateTask: () => void; - onClickRunPipeline: () => void; +const elk = new ELK(); + +const elkOptions = { + 'elk.algorithm': 'mrtree', + 'elk.layered.spacing.nodeNodeBetweenLayers': '100', + 'elk.spacing.nodeNode': '80', }; -const DagViewDrawer: React.FC = ({ - groupId, - pipelineId, - toggle, - open, - onClickCreateTask, - onClickRunPipeline, -}) => { - const { extension } = useExtension(); - const { create } = Hooks.Tasks.useCreate(); - - const handleCreateDagTask = (task: Workflows.FunctionTask) => { - create( - { - groupId, - pipelineId, - reqTask: { - ...task, - id: task.id!, - type: Workflows.EnumTaskType.Function, - runtime: task.runtime!, - installer: task.installer!, - code: task.code! || undefined, - }, - }, - { - onSuccess: toggle, - } - ); +const getLayoutedElements: any = ( + nodes: Array, + edges: Array, + options: LayoutOptions = {} +) => { + const graph = { + id: 'root', + layoutOptions: options, + children: nodes.map((node) => ({ + ...node, + targetPosition: 'left', + sourcePosition: 'right', + + // Hardcode a width and height for elk to use when layouting. + width: 300, + height: 100, + })), + edges: edges, }; - const sidebarTasks = - extension?.serviceCustomizations?.workflows?.dagTasks || []; - return ( -
- - - - - - - - - - - - - - - - - - - - - - { - event.stopPropagation(); - }} - > - Add predefined tasks to the workflow - - } - > - {sidebarTasks.map((task, i) => ( - - { - console.log({ task }); - handleCreateDagTask(task as Workflows.Task); - }} - > - - - - - - - ))} - - - -
- ); -}; + return elk + .layout(graph) + .then((layoutedGraph: any) => ({ + nodes: layoutedGraph.children.map((node: ElkNode) => ({ + ...node, + // React Flow expects a position property on the node instead of `x` + // and `y` fields. + position: { x: node.x, y: node.y }, + })), -type DagViewProps = { - pipeline: Workflows.Pipeline; - groupId: string; + edges: layoutedGraph.edges, + })) + .catch(console.error); }; -type View = 'data' | 'dependencies' | 'conditionals'; +const ELKLayoutFlow: React.FC = ({ groupId, pipeline }) => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const { fitView } = useReactFlow(); -const DagView: React.FC = ({ groupId, pipeline }) => { const tasks = pipeline.tasks!; const [modal, setModal] = useState(undefined); const [drawerOpen, setDrawerOpen] = useState(false); @@ -188,11 +106,6 @@ const DagView: React.FC = ({ groupId, pipeline }) => { }); }; - useEffect(() => { - setNodes(calculateNodes()); - setEdges(calculateEdges()); - }, [views, groupId, pipeline]); - const calculateNodes = () => { let conditionalOffset = 0; let initialNodes: Array = []; @@ -236,12 +149,17 @@ const DagView: React.FC = ({ groupId, pipeline }) => { position: { x: 0, y: 0 }, type: 'env', data: { pipeline }, + height: 200 + 25 * Object.keys(pipeline.env ? pipeline.env : {}).length, + width: 300, }, { id: `${pipeline.id}-args`, position: { x: 0, y: Object.entries(pipeline.env!).length * 25 + 100 }, type: 'args', data: { pipeline }, + height: + 200 + 25 * Object.keys(pipeline.params ? pipeline.params : {}).length, + width: 300, }, ]; @@ -270,6 +188,54 @@ const DagView: React.FC = ({ groupId, pipeline }) => { style: { stroke: '#000000', strokeWidth: '3px' }, }); } + const taskInput = task.input ? task.input : {}; + for (let input of Object.entries(taskInput)) { + let [_, v] = input; + let hasArgsDep = false; + let hasEnvDep = false; + if (hasArgsDep && hasEnvDep) { + break; + } + if (v.value_from?.env !== undefined) { + // Edge from the environment to the task + initialEdges.push({ + id: `e-${pipeline.id}-env-${task.id}`, + type: 'step', + source: `${pipeline.id}-env`, + target: task.id!, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 10, + height: 10, + color: '#000000', + }, + animated: true, + style: { stroke: '#000000', strokeWidth: '3px' }, + }); + hasEnvDep = true; + continue; + } + + if (v.value_from?.args !== undefined) { + // Edge from the environment to the task + initialEdges.push({ + id: `e-${pipeline.id}-env-${task.id}`, + type: 'step', + source: `${pipeline.id}-args`, + target: task.id!, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 10, + height: 10, + color: '#000000', + }, + animated: true, + style: { stroke: '#000000', strokeWidth: '3px' }, + }); + hasArgsDep = true; + continue; + } + } for (const dep of task.depends_on!) { // Add edges from the conditional node to the dependent task if (task.conditions!.length > 0 && views.conditionals) { @@ -308,23 +274,49 @@ const DagView: React.FC = ({ groupId, pipeline }) => { return initialEdges; }; - const [nodes, setNodes, onNodesChange] = useNodesState(calculateNodes()); - const [edges, setEdges, onEdgesChange] = useEdgesState(calculateEdges()); - const onConnect = useCallback( - (params: any) => setEdges((eds) => addEdge(params, eds)), - [setEdges] + (params: any) => setEdges((eds: any) => addEdge(params, eds) as any), + [] + ); + + const onLayout = useCallback( + ({ + direction, + useInitialNodes = false, + }: { + direction: string; + useInitialNodes: boolean; + }) => { + const opts = { 'elk.direction': direction, ...elkOptions }; + const ns = useInitialNodes ? calculateNodes() : nodes; + const es = useInitialNodes ? calculateEdges() : edges; + + getLayoutedElements(ns, es, opts).then( + ({ nodes: layoutedNodes, edges: layoutedEdges }: any) => { + setNodes(layoutedNodes); + setEdges(layoutedEdges); + + window.requestAnimationFrame(() => fitView()); + } + ); + }, + [views, setViews, nodes, edges] ); + // Calculate the initial layout on mount. + useLayoutEffect(() => { + onLayout({ direction: 'RIGHT', useInitialNodes: true }); + }, [views, setViews, groupId, pipeline]); + return (
= ({ groupId, pipeline }) => { ); }; +type DagViewProps = { + pipeline: Workflows.Pipeline; + groupId: string; +}; + +type View = 'data' | 'dependencies' | 'conditionals'; + +// const DagView: React.FC = ({ groupId, pipeline }) => { +// const tasks = pipeline.tasks!; +// const [modal, setModal] = useState(undefined); +// const [drawerOpen, setDrawerOpen] = useState(false); + +// const nodeTypes = useMemo( +// () => ({ +// standard: TaskNode, +// args: ArgsNode, +// env: EnvironmentNode, +// conditional: ConditionalNode, +// }), +// [] +// ); +// const [views, setViews] = useState<{ [K in View]: boolean }>({ +// conditionals: true, +// data: true, +// dependencies: true, +// }); + +// const handleToggleView = (view: View) => { +// setViews({ +// ...views, +// [view]: !views[view], +// }); +// }; + +// useEffect(() => { +// setNodes(calculateNodes()); +// setEdges(calculateEdges()); +// }, [views, groupId, pipeline]); + +// const calculateNodes = () => { +// let conditionalOffset = 0; +// let initialNodes: Array = []; +// let i = 0; +// for (let task of tasks) { +// if (task.conditions!.length > 0 && views.conditionals) { +// initialNodes.push({ +// id: `conditional-${task.id!}`, +// position: { +// x: (i + 1 + conditionalOffset) * 350, +// y: Object.entries(pipeline.env!).length * 25 + 30, +// }, +// type: 'conditional', +// data: { task: task, groupId, pipelineId: pipeline.id }, +// }); +// conditionalOffset++; +// } + +// initialNodes.push({ +// id: task.id!, +// position: { +// x: (i + 1 + conditionalOffset) * 350, +// y: Object.entries(pipeline.env!).length * 25 + 30, +// }, +// type: 'standard', +// data: { +// label: task.id!, +// task: task, +// groupId, +// pipelineId: pipeline.id, +// tasks, +// }, +// }); +// i++; +// } + +// initialNodes = [ +// ...initialNodes, +// { +// id: `${pipeline.id}-env`, +// position: { x: 0, y: 0 }, +// type: 'env', +// data: { pipeline }, +// }, +// { +// id: `${pipeline.id}-args`, +// position: { x: 0, y: Object.entries(pipeline.env!).length * 25 + 100 }, +// type: 'args', +// data: { pipeline }, +// }, +// ]; + +// return initialNodes; +// }; + +// const calculateEdges = () => { +// if (!views.dependencies) { +// return []; +// } +// const initialEdges: Array = []; +// for (const task of tasks) { +// if (task.conditions!.length > 0 && views.conditionals) { +// initialEdges.push({ +// id: `e-conditional-${task.id}-${task.id}`, +// type: 'step', +// source: `conditional-${task.id}`, +// target: task.id!, +// markerEnd: { +// type: MarkerType.ArrowClosed, +// width: 10, +// height: 10, +// color: '#000000', +// }, +// animated: true, +// style: { stroke: '#000000', strokeWidth: '3px' }, +// }); +// } +// for (const dep of task.depends_on!) { +// // Add edges from the conditional node to the dependent task +// if (task.conditions!.length > 0 && views.conditionals) { +// // Edge from the conditional to the parent task +// initialEdges.push({ +// id: `e-conditional-${dep.id}-c-${task.id}`, +// type: 'step', +// source: dep.id!, +// target: `conditional-${task.id}`, +// markerEnd: { +// type: MarkerType.ArrowClosed, +// width: 10, +// height: 10, +// color: '#000000', +// }, +// animated: true, +// style: { stroke: '#000000', strokeWidth: '3px' }, +// }); +// } +// initialEdges.push({ +// id: `e-${dep.id}-${task.id}`, +// type: 'step', +// source: dep.id!, +// target: task.id!, +// markerEnd: { +// type: MarkerType.ArrowClosed, +// width: 10, +// height: 10, +// color: '#000000', +// }, +// animated: true, +// style: { stroke: '#000000', strokeWidth: '3px' }, +// }); +// } +// } +// return initialEdges; +// }; + +// const [nodes, setNodes, onNodesChange] = useNodesState(calculateNodes()); +// const [edges, setEdges, onEdgesChange] = useEdgesState(calculateEdges()); + +// const onConnect = useCallback( +// (params: any) => setEdges((eds) => addEdge(params, eds)), +// [setEdges] +// ); + +// return ( +//
+// +// { +// setDrawerOpen(false); +// }} +// onClickCreateTask={() => { +// setModal('createtask'); +// }} +// onClickRunPipeline={() => { +// setModal('runpipeline'); +// }} +// /> +// +// { +// setDrawerOpen(true); +// }} +// color={'primary'} +// size="small" +// label="actions" +// icon={} +// /> +// +// +// { +// handleToggleView('data'); +// }} +// variant={views.data ? 'filled' : 'outlined'} +// color="primary" +// style={{ marginLeft: '8px' }} +// size="small" +// label="data" +// icon={} +// /> +// { +// handleToggleView('dependencies'); +// }} +// variant={views.dependencies ? 'filled' : 'outlined'} +// color="primary" +// style={{ marginLeft: '8px' }} +// size="small" +// label="dependenies" +// icon={} +// /> +// { +// handleToggleView('conditionals'); +// }} +// variant={views.conditionals ? 'filled' : 'outlined'} +// color="primary" +// style={{ marginLeft: '8px' }} +// size="small" +// label="conditionals" +// icon={} +// /> +// +// +// +// +// +// { +// setModal(undefined); +// }} +// groupId={groupId} +// pipelineId={pipeline.id!} +// /> +// { +// setModal(undefined); +// }} +// groupId={groupId} +// pipelineId={pipeline.id!} +// pipeline={pipeline} +// /> +//
+// ); +// }; + +const DagView: React.FC = ({ groupId, pipeline }) => { + return ( + + + + ); +}; export default DagView; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.module.scss b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.module.scss index bc5856b9..37cf048c 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.module.scss +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.module.scss @@ -1,5 +1,5 @@ .node { - border: 2px solid #000000; + border: 4px solid #1465c0; border-radius: 3px; width: 300px; color: #000000; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.tsx index a3bcaf64..4eb1734d 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ArgsNode/ArgsNode.tsx @@ -25,22 +25,12 @@ const ArgsNode: React.FC = ({ data }) => { Runtime arguments that can be used as task input
-
- {Object.entries(pipeline.params!).map(([k, v], i) => { - return
{k}
; - })} -
- {Object.entries(pipeline.params!).map(([k, v], i) => { - return ( - - ); - })} + ); }; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ConditionalNode/ConditionalNode.module.scss b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ConditionalNode/ConditionalNode.module.scss index ca3d6fed..1d458c4c 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ConditionalNode/ConditionalNode.module.scss +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/ConditionalNode/ConditionalNode.module.scss @@ -1,5 +1,5 @@ .node { - border: 2px solid magenta; + border: 4px solid magenta; border-radius: 3px; width: 300px; color: #000000; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.module.scss b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.module.scss index 991267b6..6ae1619f 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.module.scss +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.module.scss @@ -1,5 +1,5 @@ .node { - border: 2px solid #000000; + border: 4px solid #1465c0; border-radius: 3px; width: 300px; color: #000000; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.tsx index 27d68efb..52ce684e 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/Nodes/EnvironmentNode/EnvironmentNode.tsx @@ -30,22 +30,12 @@ const EnvironmentNode: React.FC = ({ id, data }) => { Pipeline envrionment variables -
- {Object.entries(pipeline.env!).map(([k, v], i) => { - return
{k}
; - })} -
- {Object.entries(pipeline.env!).map(([k, v], i) => { - return ( - - ); - })} + ); }; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/DagViewDrawer.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/DagViewDrawer.tsx new file mode 100644 index 00000000..f73f2f72 --- /dev/null +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/DagViewDrawer.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { Workflows } from '@tapis/tapis-typescript'; +import { Workflows as Hooks } from '@tapis/tapisui-hooks'; +import { useExtension } from 'extensions'; +import { + ListItemText, + ListItemIcon, + Divider, + Drawer, + Box, + List, + ListItem, + ListItemButton, + ListSubheader, +} from '@mui/material'; +import { Add, Publish } from '@mui/icons-material'; + +type DagViewDrawerProps = { + groupId: string; + pipelineId: string; + toggle: () => void; + open: boolean; + onClickCreateTask: () => void; + onClickRunPipeline: () => void; +}; + +const DagViewDrawer: React.FC = ({ + groupId, + pipelineId, + toggle, + open, + onClickCreateTask, + onClickRunPipeline, +}) => { + const { extension } = useExtension(); + const { create } = Hooks.Tasks.useCreate(); + + const handleCreateDagTask = (task: Workflows.FunctionTask) => { + create( + { + groupId, + pipelineId, + reqTask: { + ...task, + id: task.id!, + type: Workflows.EnumTaskType.Function, + runtime: task.runtime!, + installer: task.installer!, + code: task.code! || undefined, + }, + }, + { + onSuccess: toggle, + } + ); + }; + + const sidebarTasks = + extension?.serviceCustomizations?.workflows?.dagTasks || []; + return ( +
+ + + + + + + + + + + + + + + + + + + + + + { + event.stopPropagation(); + }} + > + Add predefined tasks to the workflow + + } + > + {sidebarTasks.map((task, i) => ( + + { + console.log({ task }); + handleCreateDagTask(task as Workflows.Task); + }} + > + + + + + + + ))} + + + +
+ ); +}; + +export default DagViewDrawer; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/index.ts b/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/index.ts new file mode 100644 index 00000000..fe047cfd --- /dev/null +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagViewDrawer/index.ts @@ -0,0 +1 @@ +export { default as DagViewDrawer } from './DagViewDrawer';