Skip to content

Commit

Permalink
Merge pull request #82 from matthew-t-smith/dev/samir
Browse files Browse the repository at this point in the history
Allow use of local flow nodes
  • Loading branch information
reddigari authored May 7, 2020
2 parents 8a7746f + 0ff3d9d commit 3903abf
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 44 deletions.
1 change: 1 addition & 0 deletions back-end/pyworkflow/pyworkflow/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def get_execution_options(self, workflow, flow_nodes):

if key in flow_nodes:
replacement_value = flow_nodes[key].get_replacement_value()
option.set_value(replacement_value)
else:
replacement_value = option.get_value()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class IntegerNode(FlowNode):
Allows for Strings to replace 'string' fields in Nodes
"""
name = "Integer Input"
num_in = 1
num_out = 1
num_in = 0
num_out = 0
color = 'purple'

OPTIONS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class StringNode(FlowNode):
Allows for Strings to replace 'string' fields in Nodes
"""
name = "String Input"
num_in = 1
num_out = 1
num_in = 0
num_out = 0
color = 'purple'

OPTIONS = {
Expand Down
6 changes: 3 additions & 3 deletions back-end/pyworkflow/pyworkflow/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_all_flow_var_options(self, node_id):
for predecessor_id in self.get_node_predecessors(node_id):
node = self.get_node(predecessor_id)

if node.node_type == 'FlowNode':
if node.node_type == 'flow_control':
flow_variables.append(node.to_json())

return flow_variables
Expand Down Expand Up @@ -314,7 +314,7 @@ def execute(self, node_id):
except NodeException as e:
raise e

if node_to_execute.data is None:
if node_to_execute.data is None and node_to_execute.node_type != "flow_control":
raise WorkflowException('execute', 'There was a problem saving node output.')

return node_to_execute
Expand Down Expand Up @@ -384,7 +384,7 @@ def load_input_data(self, node_id):
if node_to_retrieve is None:
raise WorkflowException('retrieve node data', 'The workflow does not contain node %s' % predecessor_id)

if node_to_retrieve.node_type != 'FlowNode':
if node_to_retrieve.node_type != 'flow_control':
input_data.append(self.retrieve_node_data(node_to_retrieve))

except WorkflowException:
Expand Down
10 changes: 10 additions & 0 deletions front-end/src/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ function fetchWrapper(endpoint, options = {}) {
}


/**
* Retrieve node info from server side workflow
* @param {string} nodeId - ID of node to retrieve
* @returns {Promise<Object>} - server response (node info and flow variables)
*/
export async function getNode(nodeId) {
return fetchWrapper(`/node/${nodeId}`);
}


/**
* Add node to server-side workflow
* @param {CustomNodeModel} node - JS node to add
Expand Down
18 changes: 18 additions & 0 deletions front-end/src/components/CustomNode/CustomNodeModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ export default class CustomNodeModel extends NodeModel {
this.configParams = options.option_types;
this.options.status = options.status || "unconfigured";

// add flow control input port
this.addPort(
new VPPortModel({
in: true,
type: 'vp-port',
name: 'flow-in'
})
);
// if flow node, add flow control output port
if (this.options.node_type === "flow_control") {
this.addPort(
new VPPortModel({
in: false,
type: 'vp-port',
name: 'flow-out'
})
);
}
const nIn = options.num_in === undefined ? 1 : options.num_in;
const nOut = options.num_out === undefined ? 1 : options.num_out;
// setup in and out ports
Expand Down
45 changes: 30 additions & 15 deletions front-end/src/components/CustomNode/CustomNodeWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,30 @@ export default class CustomNodeWidget extends React.Component {

render() {
const engine = this.props.engine;
const ports = _.values(this.props.node.getPorts());
const allPorts = _.values(this.props.node.getPorts());
const ports = allPorts.filter(p => !p.options.name.includes("flow"));
const flowInPort = allPorts.find(p => p.options.name === "flow-in");
const flowOutPort = allPorts.find(p => p.options.name === "flow-out");
// group ports by type (in/out)
const sortedPorts = _.groupBy(ports, p => p.options.in === true ? "in" : "out");
// create PortWidget array for each type
const portWidgets = {};
for (let portType in sortedPorts) {
portWidgets[portType] = sortedPorts[portType].map(port =>
<PortWidget engine={engine} port={port} key={port.getID()}>
<div className="triangle-port" />
<div className="triangle-port" />
</PortWidget>
);
}

const flowPortWidgets = [flowInPort, flowOutPort].filter(p => p).map(port =>
<PortWidget engine={engine} port={port} key={port.getID()}
className={`flow-port-div flow-port-div-${port.options.in ? "in" : "out"}`}>
<div className="flow-port" />
</PortWidget>
);


let graphView;
let width = 40;
if (this.props.node.options.node_type !== "flow_control") {
Expand All @@ -75,19 +86,23 @@ export default class CustomNodeWidget extends React.Component {
<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, width: width }}>
<div className="custom-node-configure" onClick={this.toggleConfig}>{String.fromCharCode(this.icon)}</div>
<NodeConfig node={this.props.node}
globals={this.props.engine.model.globals || []}
show={this.state.showConfig}
toggleShow={this.toggleConfig}
onDelete={this.handleDelete}
onSubmit={this.acceptConfiguration} />
{graphView}
<GraphView node={this.props.node}
show={this.state.showGraph}
toggleShow={this.toggleGraph}
onDelete={this.handleDelete}
onSubmit={this.acceptConfiguration} />
<div className="custom-node-icons">
<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} />
{graphView}
<GraphView node={this.props.node}
show={this.state.showGraph}
toggleShow={this.toggleGraph}
onDelete={this.handleDelete}
onSubmit={this.acceptConfiguration} />
</div>
{flowPortWidgets}
<div className="port-col port-col-in">
{ portWidgets["in"] }
</div>
Expand Down
28 changes: 20 additions & 8 deletions front-end/src/components/CustomNode/NodeConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,25 @@ export default class NodeConfig extends React.Component {
this.state = {
disabled: false,
data: {},
flowData: {}
flowData: {},
flowNodes: []
};
this.updateData = this.updateData.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

getFlowNodes() {
if (!this.props.node) return;
API.getNode(this.props.node.options.id)
.then(node => this.setState({flowNodes: node.flow_variables}))
.catch(err => console.log(err));
}

componentDidUpdate(prevProps) {
if (!prevProps.show && this.props.show) this.getFlowNodes();
}

// callback to update form data in state;
// resulting state will be sent to node config callback
updateData(key, value, flow = false) {
Expand Down Expand Up @@ -82,7 +94,7 @@ export default class NodeConfig extends React.Component {
value={this.props.node.config[key]}
flowValue={this.props.node.options.option_replace ?
this.props.node.options.option_replace[key] : null}
globals={this.props.globals}
flowNodes={this.state.flowNodes}
disableFunc={(v) => this.setState({disabled: v})}/>
)}
<Form.Group>
Expand Down Expand Up @@ -149,7 +161,7 @@ function OptionInput(props) {
}

const hideFlow = props.node.options.is_global
|| props.type === "file" || props.globals.length === 0
|| props.type === "file" || props.flowNodes.length === 0;
return (
<Form.Group>
<Form.Label>{props.label}</Form.Label>
Expand All @@ -159,7 +171,7 @@ function OptionInput(props) {
{hideFlow ? null :
<FlowVariableOverride keyName={props.keyName}
flowValue={props.flowValue || {}}
flowNodes={props.globals || []}
flowNodes={props.flowNodes || []}
checked={isFlow}
onFlowCheck={handleFlowCheck}
onChange={handleFlowVariable} />
Expand Down Expand Up @@ -290,7 +302,7 @@ function FlowVariableOverride(props) {

const handleSelect = (event) => {
const uuid = event.target.value;
const flow = props.flowNodes.find(d => d.id === uuid);
const flow = props.flowNodes.find(d => d.node_id === uuid);
const obj = {
node_id: uuid,
is_global: flow.is_global
Expand All @@ -308,9 +320,9 @@ function FlowVariableOverride(props) {
<Form.Control as="select" name={props.keyName} onChange={handleSelect}
value={props.flowValue.node_id}>
<option/>
{props.flowNodes.map(gfv =>
<option key={gfv.id} value={gfv.id}>
{gfv.options.var_name}
{props.flowNodes.map(fv =>
<option key={fv.node_id} value={fv.node_id}>
{fv.options ? fv.options.var_name : fv.option_values.var_name}
</option>
)}
</Form.Control>
Expand Down
4 changes: 2 additions & 2 deletions front-end/src/components/VPLink/VPLinkModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export default class VPLinkModel extends DefaultLinkModel {
constructor() {
super({
type: 'default',
width: 5,
color: 'orange'
width: 2,
color: 'black'
});
this.registerListener({
targetPortChanged: event => {
Expand Down
14 changes: 11 additions & 3 deletions front-end/src/components/VPPort/VPPortModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ export default class VPPortModel extends DefaultPortModel {
}

canLinkToPort(port) {
// can't both be in or out ports
return port instanceof VPPortModel
&& this.options.in !== port.options.in;
// if connecting to flow port, make sure this is a flow port
// and opposite of other's direction
if (port.options.name.includes("flow")) {
return this.options.name.includes("flow")
&& this.options.in !== port.options.in
// otherwise, make sure this is NOT a flow port, and ensure
// in/out compatibility
} else {
return !this.options.name.includes("flow")
&& this.options.in !== port.options.in
}
}
}
1 change: 0 additions & 1 deletion front-end/src/components/Workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class Workspace extends React.Component {
API.getGlobalVars()
.then(vars => {
this.setState({globals: vars});
this.model.globals = vars;
})
.catch(err => console.log(err));
}
Expand Down
40 changes: 32 additions & 8 deletions front-end/src/styles/CustomNode.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
position: relative;
}

.custom-node-name {
margin-bottom: 3px;
}

.port-col {
display: flex;
flex-direction: column;
Expand All @@ -40,21 +44,41 @@
border-left-color: mediumpurple;
}

.flow-port-div {
position: absolute;
top: -5px;
}

.flow-port-div-in {
left: -5px;
}

.flow-port-div-out {
left: 85%;
}

.flow-port {
width: 10px;
height: 10px;
border-radius: 5px;
background-color: purple;
}

.custom-node-icons {
width: 100%;
position: absolute;
display: flex;
justify-content: space-evenly;
align-items: center;
}

.custom-node-configure {
font-size: 1.5rem;
cursor: pointer;
position: absolute;
left: 25%;
top: 50%;
transform: translate(-50%, -50%);
}

.custom-node-tabular {
cursor: pointer;
position: absolute;
right: 25%;
top: 25%;
transform: translate(50%, -25%);
}

.custom-node-description {
Expand Down

0 comments on commit 3903abf

Please sign in to comment.