diff --git a/cloud/package-lock.json b/cloud/package-lock.json index 0dc112e4..f4eaf6e8 100644 --- a/cloud/package-lock.json +++ b/cloud/package-lock.json @@ -4961,6 +4961,11 @@ "type": "^1.0.1" } }, + "dagre-d3-react": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/dagre-d3-react/-/dagre-d3-react-0.2.4.tgz", + "integrity": "sha512-spYk0zaI8Mm+5E+99veMDS7CG2Prpce1CcjQINHTKmJe0XUkmFEey5yRr4Ag7htXdirw1Lf/rlBiZgORcc1Nyg==" + }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", diff --git a/cloud/package.json b/cloud/package.json index 3ac24c56..504a4406 100644 --- a/cloud/package.json +++ b/cloud/package.json @@ -19,6 +19,7 @@ "@types/react-router-dom": "^5.1.5", "@types/react-syntax-highlighter": "^11.0.4", "@types/redux-logger": "^3.0.7", + "dagre-d3-react": "^0.2.4", "eslint-webpack-plugin": "^2.4.3", "lodash": "^4.17.19", "polished": "^3.6.3", diff --git a/cloud/src/components/task/TaskState.tsx b/cloud/src/components/task/TaskState.tsx index 2ce30ded..59092f19 100644 --- a/cloud/src/components/task/TaskState.tsx +++ b/cloud/src/components/task/TaskState.tsx @@ -1,18 +1,41 @@ import React from 'react' import { ContentBlock, Code } from '../ui' +import TaskGraph from './task-ui/TaskGraph' type Props = { - state?: object + state: State +} + +type State = { + ui?: TaskComponent[] + [key: string]: any +} + +type TaskComponent = { + component: string + path: string +} + +function renderComponent(def: TaskComponent, state: State) { + switch(def.component) { + case 'ui.cowait.io/task-graph': + return + default: + throw new Error(`Unknown Task Component ${def.component}`) + } } export const TaskState: React.FC = ({ state }) => { if (!state) { return null } + const components = state.ui || [] + return

State

- {JSON.stringify(state, null, 4)} + {components.map(c => renderComponent(c, state))}
+ // {JSON.stringify(state, null, 4)} } export default TaskState diff --git a/cloud/src/components/task/styled/Log.tsx b/cloud/src/components/task/styled/Log.tsx index c1b07327..598596f9 100644 --- a/cloud/src/components/task/styled/Log.tsx +++ b/cloud/src/components/task/styled/Log.tsx @@ -10,6 +10,7 @@ export const LogOutput = styled.pre` font-family: ${p => p.theme.fonts.monospace}; color: ${p => p.theme.colors.text.secondary}; line-height: 1.25em; + max-width: 100vh; ` export const LogContainer = styled.div` diff --git a/cloud/src/components/task/task-ui/TaskGraph.tsx b/cloud/src/components/task/task-ui/TaskGraph.tsx new file mode 100644 index 00000000..25dcd889 --- /dev/null +++ b/cloud/src/components/task/task-ui/TaskGraph.tsx @@ -0,0 +1,105 @@ +import _ from 'lodash' +import React from 'react' +import { useSelector } from 'react-redux' +import { RootState } from '../../../store' +import DagreGraph from 'dagre-d3-react' +import styled from 'styled-components' + +const DagStyle = styled.div` + .nodes { + fill: darkgray; + cursor: pointer; + rx: 0.5em; + } + .nodes .work { + fill: #82b332; + } + .nodes .done { + fill: green; + } + .nodes .fail { + fill: red; + } + .nodes .stop { + fill: orange; + } + .nodes text { + fill: white; + } + + path { + stroke: white; + fill: white; + stroke-width: 3px; + } +` + +type Props = { + graph: TaskGraph +} + +type TaskGraph = { + [id: string]: TaskNode, +} + +type TaskNode = { + id: string + task: string + depends_on: string[] + task_id?: string +} + +type d3Link = { + source: string + target: string + class?: string + label?: string + config?: object +} + +export const TaskGraph: React.FC = ({ graph }) => { + const tasks = useSelector((state: RootState) => state.tasks.items) + let nodes = _.map(graph, node => { + if (node.task_id && tasks[node.task_id]) { + let task = tasks[node.task_id] + return { + id: node.id, + label: task.id, + class: task.status, + } + } + return { + id: node.id, + label: node.task, + class: 'pending', + } + }) + let links: d3Link[] = [] + _.each(graph, node => { + _.each(node.depends_on, edge => { + links.push({ + source: edge, + target: node.id, + }) + }) + }) + return + console.log(e)} + /> + +} + +export default TaskGraph \ No newline at end of file diff --git a/cowait/tasks/graph/graph_task.py b/cowait/tasks/graph/graph_task.py index bf37c6c6..d3f79265 100644 --- a/cowait/tasks/graph/graph_task.py +++ b/cowait/tasks/graph/graph_task.py @@ -4,6 +4,16 @@ class GraphTask(Task): + def init(self) -> dict: + return { + 'ui': [ + { + 'component': 'ui.cowait.io/task-graph', + 'path': 'graph', + }, + ] + } + async def define(self, graph, **inputs): # this is where you would define your graph nodes # to create a dag, override this function in a subclass @@ -22,12 +32,12 @@ async def send_state(): for node in graph.nodes: task = node_tasks.get(node, None) state[node.id] = { - 'id': node.id, + 'id': str(node.id), 'task': node.task if not issubclass(node.task, Task) else node.task.__module__, - 'depends_on': [edge.id for edge in node.edges], + 'depends_on': [str(edge.id) for edge in node.edges], 'task_id': None if not task else task.id, } - await self.set_state(state) + await self.set_state({'graph': state}) await send_state()