Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Front-end bug fixes, GraphView tweaks #79

Merged
merged 8 commits into from
May 2, 2020
8 changes: 8 additions & 0 deletions front-end/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions front-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"react-bootstrap": "^1.0.0-beta.17",
"react-dom": "^16.13.0",
"react-scripts": "3.4.1",
"react-spinners-css": "^1.1.7",
"react-window": "^1.8.5",
"regenerator-runtime": "^0.13.5",
"resize-observer-polyfill": "^1.5.1"
Expand Down
15 changes: 12 additions & 3 deletions front-end/src/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,18 @@ export async function uploadWorkflow(formData) {
async function handleEdge(link, method) {
const sourceId = link.getSourcePort().getNode().options.id;
const targetId = link.getTargetPort().getNode().options.id;
return fetchWrapper(
`/node/edge/${sourceId}/${targetId}`,
{method: method});

let endpoint;

if (link.getSourcePort().options.in) {
// If edge goes from IN port -> OUT port, reverse the ports
endpoint = `/node/edge/${targetId}/${sourceId}`;
} else {
// Otherwise, keep source -> target edge
endpoint = `/node/edge/${sourceId}/${targetId}`;
}

return fetchWrapper(endpoint, {method: method});
}


Expand Down
18 changes: 14 additions & 4 deletions front-end/src/components/CustomNode/CustomNodeWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,29 @@ export default class CustomNodeWidget extends React.Component {
</PortWidget>
);
}

let graphView;
let width = 40;
if (this.props.node.options.node_type !== "flow_control") {
graphView = (
<div className="custom-node-tabular" onClick={this.toggleGraph}>
<img src="tabular-icon.png" alt="Tabular" style={{width:25, height:25}}/>
</div>
);
width = 80;
}

return (
<div className="custom-node-wrapper">
<div className="custom-node-name">{this.props.node.options.name}</div>
<div className="custom-node" style={{ borderColor: this.props.node.options.color }}>
<div className="custom-node" style={{ borderColor: this.props.node.options.color, width: width }}>
<div className="custom-node-configure" onClick={this.toggleConfig}>{String.fromCharCode(this.icon)}</div>
<NodeConfig node={this.props.node}
show={this.state.showConfig}
toggleShow={this.toggleConfig}
onDelete={this.handleDelete}
onSubmit={this.acceptConfiguration} />
<div className="custom-node-tabular" onClick={this.toggleGraph}>
<img src="tabular-icon.png" alt="Tabular" style={{width:25, height:25}}></img>
</div>
{graphView}
<GraphView node={this.props.node}
show={this.state.showGraph}
toggleShow={this.toggleGraph}
Expand Down
195 changes: 107 additions & 88 deletions front-end/src/components/CustomNode/GraphView.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Roller } from 'react-spinners-css';
import { Modal, Button } from 'react-bootstrap';
import propTypes from 'prop-types';
import { VariableSizeGrid as Grid } from 'react-window';
Expand All @@ -13,16 +14,16 @@ export default class GraphView extends React.Component {
this.key_id = props.node.getNodeId();
this.state = {
loading: false,
rowCount: 0,
columnCount: 0,
data: [],
keys: [],
rows: [],
columns: [],
maxWidth: 0,
gridRef: React.createRef()};
}
};

columnWidths = (index) => {
return 10 * this.state.widths[index];
}
};

rowHeights = () => new Array(765)
.fill(true)
Expand All @@ -32,109 +33,127 @@ export default class GraphView extends React.Component {
this.props.toggleShow();
};

computeWidths = (columnCount, rowCount, json) => {
const widths = new Array(columnCount);
const keys = Object.keys(json);
for (let index = 0; index < columnCount; index++) {
const key = keys[index];
widths[index] = key.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const value = json[key][rowIndex.toString()];
if (value != null && value.length > widths[index]) {
widths[index] = value.length;
}
/**
* Compute width of grid columns.
*
* Width is based on the maximum-length cell contained within the JSON data.
*
* @param {Object} columns The column information from the data
* @param {int} rowCount Number of rows in the data
* @param {Object} data The raw data from Node execution
* @returns {any[]}
*/
computeWidths = (columns, rowCount, data) => {
const columnCount = columns.length;
const widths = new Array(columnCount);
let maxWidth = this.state.maxWidth;

for (let index = 0; index < columnCount; index++) {
const column = columns[index];
widths[index] = column.length;

for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = data[column][rowIndex.toString()];

if (row != null && row.length > widths[index]) {
widths[index] = row.length;
}

maxWidth += widths[index] * 10;
}
}
}

return widths;
}
this.setState({maxWidth: maxWidth});
return widths;
};

load = async () => {
this.setState({loading: true})
API.retrieveData(this.key_id)
this.setState({loading: true});

API.retrieveData(this.key_id)
.then(json => {
const keys = Object.keys(json);
const columnCount = keys.length;
const rows = Object.keys(json[keys[0]]);
const rowCount = rows.length;
const widths = this.computeWidths(columnCount, rowCount, json);
this.setState({ data: json,
keys: keys,
columnCount: columnCount,
rowCount: rowCount,
loading: false,
widths: widths});
}).catch(err => console.log(err));
}
const columns = Object.keys(json);
const rows = Object.keys(json[columns[0]]);
const widths = this.computeWidths(columns, rows.length, json);

this.setState({
data: json,
columns: columns,
rows: rows,
loading: false,
widths: widths
});
})
.catch(err => console.error(err));
};

Cell = ({ columnIndex, rowIndex, style }) => {
const className = columnIndex % 2
? rowIndex % 2 === 0
? 'GridItemOdd'
: 'GridItemEven'
: rowIndex % 2
? 'GridItemOdd'
: 'GridItemEven';

if (rowIndex === 0) {
return (
<div className={className} style={style}>
{this.state.keys[columnIndex]}
</div>
);
}
const className = (rowIndex % 2 === 0) ? 'GridItemEven' : 'GridItemOdd';
const column = this.state.columns[columnIndex];

return (
<div className={className} style={style}>
{this.state.data[this.state.keys[columnIndex]][rowIndex.toString()] }
{(rowIndex === 0) ? column : this.state.data[column][rowIndex.toString()]}
</div>
);
}
};


render() {
let body;
let footer;

if (this.state.loading) {
return (<div>Loading data...</div>);
// Print loading spinner
body = (<Roller color="black" />);
} else if (this.state.data.length < 1) {
// Print instructions about loading
body = "Loading the data might take a while depending on how big the data is.";
footer = (
<Modal.Footer>
<Button variant="secondary" onClick={this.onClose}>Cancel</Button>
<Button variant="secondary"
disabled={this.props.node.options.status !== "complete"}
onClick={this.load}>Load
</Button>
</Modal.Footer>
);
} else {
// Display the grid
let displayHeight = this.state.rows.length * 20;
let displayWidth = this.state.maxWidth;

body = (
<Grid
ref={this.state.gridRef}
className="Grid"
columnCount={this.state.columns.length}
columnWidth={index => this.columnWidths(index)}
height={displayHeight < 600 ? displayHeight + 5 : 600}
rowCount={this.state.rows.length}
rowHeight={index => 20}
width={displayWidth < 900 ? displayWidth : 900}
>
{this.Cell}
</Grid>
);
}

if (this.state.columnCount < 1) {
return (
<Modal show={this.props.show} onHide={this.props.toggleShow} centered
onWheel={e => e.stopPropagation()}>
<Modal.Header>
<Modal.Title><b>{this.props.node.options.name}</b> View</Modal.Title>
return (
<Modal
reelmatt marked this conversation as resolved.
Show resolved Hide resolved
show={this.props.show}
onHide={this.props.toggleShow}
centered
dialogClassName={"GraphView"}
onWheel={e => e.stopPropagation()}
>
<Modal.Header closeButton>
<Modal.Title><b>{this.props.node.options.name}</b> View</Modal.Title>
</Modal.Header>
<Modal.Body>
Loading the data might take a while depending on how big the data is.
{body}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.onClose}>Cancel</Button>
<Button variant="secondary" onClick={this.load}>Load</Button>
</Modal.Footer>
</Modal>
);
}

return (
<Modal show={this.props.show} onHide={this.props.toggleShow} centered
onWheel={e => e.stopPropagation()}>
<Modal.Header closeButton>
<Modal.Title><b>{this.props.node.options.name}</b> View</Modal.Title>
</Modal.Header>
<Modal.Body>
<Grid
ref={this.state.gridRef}
className="Grid"
columnCount={this.state.columnCount}
columnWidth={index => this.columnWidths(index)}
height={150}
rowCount={this.state.rowCount}
rowHeight={index => 20}
width={480}
>
{this.Cell}
</Grid>
</Modal.Body>
{footer}
</Modal>
);
}
Expand All @@ -145,4 +164,4 @@ GraphView.propTypes = {
show: propTypes.bool,
toggleShow: propTypes.func,
onClose: propTypes.func,
}
};
7 changes: 7 additions & 0 deletions front-end/src/styles/GraphView.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
.Grid {
border: 1px solid #d9dddd;
width: auto;
margin: auto;
}

.GridItemEven, .GridItemOdd {
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid grey;
border-bottom: 1px solid grey;
}

.GridItemEven {
background-color: #f8f8f0;
}

.GraphView {
max-width: 925px;
}