UNPKG

nf-workflow-ui-5

Version:
280 lines (245 loc) 11.5 kB
import React, {Component} from 'react'; import dagreD3 from 'dagre-d3' import d3 from 'd3' import {Tabs, Tab, Table} from 'react-bootstrap'; import Clipboard from 'clipboard'; new Clipboard('.btn'); class Grapher extends Component { constructor(props) { super(props); this.state = {}; this.state.selectedTask = {}; this.state.logs = {}; this.grapher = new dagreD3.render(); this.setSvgRef = elem => this.svgElem = elem; this.setDivRef = elem => this.divElem = elem; this.setPropsDivRef = elem => this.propsDivElem = elem; let starPoints = function (outerRadius, innerRadius) { var results = ""; var angle = Math.PI / 8; for (var i = 0; i < 2 * 8; i++) { // Use outer or inner radius depending on what iteration we are in. var r = (i & 1) == 0 ? outerRadius : innerRadius; var currX = 0 + Math.cos(i * angle) * r; var currY = 0 + Math.sin(i * angle) * r; if (i == 0) { results = currX + "," + currY; } else { results += ", " + currX + "," + currY; } } return results; }; this.grapher.shapes().house = function (parent, bbox, node) { var w = bbox.width, h = bbox.height, points = [ {x: 0, y: 0}, {x: w, y: 0}, {x: w, y: -h}, {x: w / 2, y: -h * 3 / 2}, {x: 0, y: -h} ]; let shapeSvg = parent.insert("polygon", ":first-child") .attr("points", points.map(function (d) { return d.x + "," + d.y; }).join(" ")) .attr("transform", "translate(" + (-w / 2) + "," + (h * 3 / 4) + ")"); node.intersect = function (point) { return dagreD3.intersect.polygon(node, points, point); }; return shapeSvg; }; this.grapher.shapes().star = function (parent, bbox, node) { var w = bbox.width, h = bbox.height, points = [ {x: 0, y: 0}, {x: w, y: 0}, {x: w, y: -h}, {x: w / 2, y: -h * 3 / 2}, {x: 0, y: -h} ]; let shapeSvg = parent.insert("polygon", ":first-child").attr("points", starPoints(w, h)) node.intersect = function (point) { return dagreD3.intersect.polygon(node, points, point); }; return shapeSvg; }; } componentDidMount() { this.forceUpdate(); } componentWillReceiveProps(nextProps) { this.state.innerGraph = nextProps.innerGraph; }; getSubGraph() { let subg = this.state.subGraph; if (subg == null) { return ''; } return <Grapher edges={subg.n} vertices={subg.vx} layout={subg.layout}/>; } render() { const {layout, edges, vertices} = this.props; let g = new dagreD3.graphlib.Graph().setGraph({rankdir: layout}); for (let vk in vertices) { let v = vertices[vk]; let l = v.name; if (!v.system) { l = v.name + '\n \n(' + v.ref + ')'; } else { l = v.ref; } g.setNode(v.ref, { label: l, shape: v.shape, style: v.style, labelStyle: v.labelStyle + '; font-weight:normal; font-size: 11px' }); } edges.forEach(e => { g.setEdge(e.from, e.to, {label: e.label, lineInterpolate: 'basis', style: e.style}); }); g.nodes().forEach(function (v) { var node = g.node(v); if (node == null) { console.log('NO node found ' + v); } node.rx = node.ry = 5; }); let svg = d3.select(this.svgElem); let inner = svg.select("g"); inner.attr("transform", "translate(20,20)"); this.grapher(inner, g); let w = g.graph().width + 200; let h = g.graph().height + 50; svg.attr("width", w + "px").attr("height", h + "px"); let innerGraph = this.state.innerGraph || []; let p = this; let showSubGraphDetails = function () { let id = p.state.subGraphId; window.open('#/workflow/id/' + id, '_new'); }; let hidesub = function () { p.setState({showSubGraph: false}); }; let hideProps = function () { p.setState({showSideBar: false}); }; inner.selectAll("g.node") .on("click", function (v) { if (innerGraph[v] != null) { let data = vertices[v].data; let n = innerGraph[v].edges; let vx = innerGraph[v].vertices; let subg = {n: n, vx: vx, layout: layout}; p.propsDivElem.style.left = (window.innerWidth/2 + 100) + 'px'; p.propsDivElem.style.width = window.innerWidth/2 - 100 + 'px' p.propsDivElem.style.overflowX = "scroll" p.propsDivElem.style.height = window.innerHeight + "px"; p.divElem.style.width = window.outerWidth/2 - 100 + "px"; p.divElem.style.display = "inline-block"; p.setState({ selectedTask: data.task, showSubGraph: true, showSideBar: true, subGraph: subg, subGraphId: innerGraph[v].id }); p.setState({showSubGraph: true}); } else if (vertices[v].tooltip != null) { let data = vertices[v].data; p.propsDivElem.style.left = (window.innerWidth/2 + 100) + 'px'; p.propsDivElem.style.width = window.innerWidth/2 - 100 + 'px' p.propsDivElem.style.height = window.innerHeight + "px"; p.propsDivElem.style.position = "fixed" p.propsDivElem.style.display = "block" p.setState({selectedTask: data.task, showSideBar: true, subGraph: null, showSubGraph: false}); } }); return ( <div className="graph-ui-content" id="graph-ui-content"> <div className="right-prop-overlay" ref={this.setPropsDivRef} style={{overflowX: 'scroll', display: this.state.showSideBar ? '' : 'none', padding: '5px 5px 10px 10px'}}> <h4 className="propsheader"> <i className="fa fa-close fa-1x close-btn" onClick={hideProps}/> {this.state.selectedTask.taskType} ({this.state.selectedTask.status}) </h4> <div style={{ color: '#ff0000', display: this.state.selectedTask.status === 'FAILED' ? '' : 'none' }}>{this.state.selectedTask.reasonForIncompletion}</div> <Tabs defaultActiveKey={1}> <Tab eventKey={1} title="Summary"> <Table responsive={true} striped={false} hover={false} condensed={false} bordered={true}> <tbody> <tr> <th>Task Ref. Name</th> <td colSpan="3" style={{colSpan: 3}}>{this.state.selectedTask.referenceTaskName}</td> </tr> <tr> <th>Poll Count</th> <td>{this.state.selectedTask.pollCount}</td> <th>Callback After</th> <td>{this.state.selectedTask.callbackAfterSeconds ? this.state.selectedTask.callbackAfterSeconds : 0} (second)</td> </tr> <tr> <th colSpan="4">Input <i title="copy to clipboard" className="btn fa fa-clipboard" data-clipboard-target="#t_input"/></th> </tr> <tr> <td colSpan="4"> <pre style={{width:(window.outerWidth/2 - 140) + "px"}} id="t_input">{JSON.stringify(this.state.selectedTask.inputData, null, 3)}</pre> </td> </tr> <tr> <th colSpan="4">Output <i title="copy to clipboard" className="btn fa fa-clipboard" data-clipboard-target="#t_output"/></th> </tr> <tr> <td colSpan="4" > <pre style={{width:(window.outerWidth/2 - 140) + "px"}} id="t_output">{JSON.stringify(this.state.selectedTask.outputData, null, 3)}</pre> </td> </tr> </tbody> </Table> </Tab> <Tab eventKey={2} title="JSON"><br/> <i title="copy to clipboard" className="btn fa fa-clipboard" data-clipboard-target="#t_json"/> <pre id="t_json">{JSON.stringify(this.state.selectedTask, null, 3)}</pre> </Tab> <Tab eventKey={3} title="Logs"><br/> <i title="copy to clipboard" className="btn fa fa-clipboard" data-clipboard-target="#t_logs"/> <pre id="t_logs">{JSON.stringify(this.state.selectedTask.logs, null, 3)}</pre> </Tab> </Tabs> </div> <div style={{overflowX: "auto", width:"100%"}}> <svg ref={this.setSvgRef}> <g transform="translate(20,20)"/> </svg> </div> <div className="right-prop-overlay" ref={this.setDivRef} style={{ overflowX: 'scroll', display: this.state.showSubGraph ? '' : 'none', padding: '5px 5px 10px 10px', zIndex: this.state.showSubGraph ? '' : '-100' }}> <h4 className="propsheader"> <i className="fa fa-close fa-1x close-btn" onClick={hidesub}/> <a onClick={showSubGraphDetails}>Sub Workflow Details</a> </h4> {this.getSubGraph()} </div> </div> ); } }; export default Grapher;