From 0b7039251d177d0f276d1c95fdfd0ad028f520f3 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 28 May 2020 13:53:44 -0700 Subject: [PATCH 01/12] implement sync and drop down menu buttons in tree and graph components. Revert docker and travis file from previous commit --- .travis.yml | 4 +- Dockerfile | 6 +- components/VFBMain.js | 2 +- .../VFBGraph/graphConfiguration.js | 54 ++++- .../VFBTree/VFBTreeConfiguration.js | 65 +++++- components/interface/VFBGraph/VFBGraph.js | 107 ++++++++-- components/interface/VFBTree/VFBTree.js | 192 ++++++++++++------ 7 files changed, 348 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index cbec99cbe..0b22bea43 100755 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ script: - echo -e "travis_fold:end:Startup_Server1" || true - echo -e "travis_fold:start:Startup_Server3" || true - echo "$http_status" - - while [ "$http_status" != "200" ]; do + - while [ "$http_status" != "404" ]; do echo "Printing logs for debugging purposes"; sudo docker cp $CONTAINER_NAME:/home/developer/virgo/serviceability/logs/log.log /etc; tail /etc/log.log; @@ -65,7 +65,7 @@ script: travis_terminate 1; done; - echo -e "travis_fold:end:Startup_Server3" || true - - travis_wait 40 npm test -- --verbose --colors --runInBand --logHeapUsage --maxConcurrency=2 --forceExit + - travis_wait 35 npm test -- --verbose --colors - echo -e "travis_fold:start:Deploy" || true - docker ps -a - sudo docker cp $CONTAINER_NAME:/home/developer/virgo/serviceability/logs/log.log /etc diff --git a/Dockerfile b/Dockerfile index c91738f72..02a3118d7 100755 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,9 @@ ARG geppettoCoreRelease=development ARG geppettoSimulationRelease=development ARG geppettoDatasourceRelease=development ARG geppettoModelSwcRelease=v1.0.1 -ARG geppettoFrontendRelease=VFBv2.1.0.3 -ARG geppettoClientRelease=VFBv2.1.0.6 -ARG ukAcVfbGeppettoRelease=v2.1.0.3 +ARG geppettoFrontendRelease=v1.0.1 +ARG geppettoClientRelease=VFBv2.1.0.5 +ARG ukAcVfbGeppettoRelease=v2.1.0.3-rc1 ARG mvnOpt="-Dhttps.protocols=TLSv1.2 -DskipTests --quiet -Pmaster" diff --git a/components/VFBMain.js b/components/VFBMain.js index d054c429e..43fd04e31 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -1233,7 +1233,7 @@ export default class VFBMain extends React.Component { // Update the graph component if (this.graphReference !== undefined && this.graphReference !== null) { - this.graphReference.updateGraph(this.instanceOnFocus, this.idOnFocus); + this.graphReference.instanceFocusChange(this.instanceOnFocus); } // Update the term info component diff --git a/components/configuration/VFBGraph/graphConfiguration.js b/components/configuration/VFBGraph/graphConfiguration.js index 8f21d2caf..3645b3914 100644 --- a/components/configuration/VFBGraph/graphConfiguration.js +++ b/components/configuration/VFBGraph/graphConfiguration.js @@ -34,7 +34,59 @@ var styling = { // Title bar (in node) background color nodeTitleBackgroundColor : "#11bffe", // Description area (in node) background color - nodeDescriptionBackgroundColor : "white" + nodeDescriptionBackgroundColor : "white", + icons : { + home : "fa fa-home", + zoomIn : "fa fa-search-plus", + zoomOut : "fa fa-search-minus", + sync : "fa fa-refresh", + dropdown : "fa fa-bars", + }, + dropDownQueries : [ + { + label : "Load Graph for 'fru-M-400042'", + query : () => ({ + "statements": [ + { + "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" + + "has_postsynaptic_terminal_in|overlaps*..]->(x)" + + "WHERE n.short_form = 'VFB_00001567' return distinct n,r,x,n.short_form as root", + "resultDataContents": ["graph"] + } + ] + }) + }, + { + label : "Load Graph for 'adult brain template JFRC2'", + query : () => ({ + "statements": [ + { + "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" + + "has_postsynaptic_terminal_in|overlaps*..]->(x)" + + "WHERE n.short_form = 'VFB_00017894' return distinct n,r,x,n.short_form as root", + "resultDataContents": ["graph"] + } + ] + }) + }, + { + label : "Load Graph for 'Medulla'", + query : () => ({ + "statements": [ + { + "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" + + "has_postsynaptic_terminal_in|overlaps*..]->(x)" + + "WHERE n.short_form = 'VFB_00030624' return distinct n,r,x,n.short_form as root", + "resultDataContents": ["graph"] + } + ] + }) + } + ], + dropDownHoverBackgroundColor : "#11bffe", + dropDownHoverTextColor : "black", + dropDownBackgroundColor : "#4f4f4f", + dropDownTextColor : "white" } var restPostConfig = { diff --git a/components/configuration/VFBTree/VFBTreeConfiguration.js b/components/configuration/VFBTree/VFBTreeConfiguration.js index 48c29647b..9299c5dd8 100644 --- a/components/configuration/VFBTree/VFBTreeConfiguration.js +++ b/components/configuration/VFBTree/VFBTreeConfiguration.js @@ -17,8 +17,71 @@ var treeCypherQuery = instance => ({ ] }); +var styling = { + // Font Awesome Icons to display as Tree's controls + icons : { + sync : "fa fa-refresh", + dropdown : "fa fa-bars", + }, + // Drop down menu options, consists of label and query + dropDownQueries : [ + { + label : "Load Tree for 'adult protocerebrum'", + query : () => ({ + "statements": [ + { + "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'FBbt_00007145'})" + + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" + + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat)) " + + "RETURN collect(distinct { node_id: id(anat), short_form: anat.short_form, image: image.short_form })" + + " AS image_nodes, id(root) AS root, collect(p)", + "resultDataContents": ["row", "graph"] + } + ] + }) + }, + { + label : "Load Tree for 'adult brain template JFRC2'", + query : () => ({ + "statements": [ + { + "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'VFB_00017894'})" + + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" + + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat)) " + + "RETURN collect(distinct { node_id: id(anat), short_form: anat.short_form, image: image.short_form })" + + " AS image_nodes, id(root) AS root, collect(p)", + "resultDataContents": ["row", "graph"] + } + ] + }) + }, + { + label : "Load Tree for 'Adult Cerebrum'", + query : () => ({ + "statements": [ + { + "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'FBbt_00007050'})" + + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" + + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat)) " + + "RETURN collect(distinct { node_id: id(anat), short_form: anat.short_form, image: image.short_form })" + + " AS image_nodes, id(root) AS root, collect(p)", + "resultDataContents": ["row", "graph"] + } + ] + }) + } + ], + dropDownHoverBackgroundColor : "#11bffe", + dropDownHoverTextColor : "black", + dropDownBackgroundColor : "#4f4f4f", + dropDownTextColor : "white" +} module.exports = { restPostConfig, - treeCypherQuery + treeCypherQuery, + styling }; diff --git a/components/interface/VFBGraph/VFBGraph.js b/components/interface/VFBGraph/VFBGraph.js index 8bee303a3..0a8382223 100644 --- a/components/interface/VFBGraph/VFBGraph.js +++ b/components/interface/VFBGraph/VFBGraph.js @@ -2,6 +2,8 @@ import React, { Component } from 'react' import axios from 'axios'; import GeppettoGraphVisualization from 'geppetto-client/js/components/interface/graph-visualization/Graph' import CircularProgress from '@material-ui/core/CircularProgress'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; /** * Read configuration from graphConfiguration.js @@ -113,11 +115,13 @@ export default class VFBGraph extends Component { constructor (props) { super(props); - this.state = { graph : { nodes : [], links : [] } , loading : true, currentQuery : this.props.instance }; + this.state = { graph : { nodes : [], links : [] } , loading : true, currentQuery : this.props.instance , dropDownAnchorEl : null }; this.updateGraph = this.updateGraph.bind(this); + this.instanceFocusChange = this.instanceFocusChange.bind(this); this.queryResults = this.queryResults.bind(this); this.handleNodeLeftClick = this.handleNodeLeftClick.bind(this); this.handleNodeRightClick = this.handleNodeRightClick.bind(this); + this.handleMenuClick = this.handleMenuClick.bind(this); this.queryNewInstance = this.queryNewInstance.bind(this); this.resetCamera = this.resetCamera.bind(this); this.zoomIn = this.zoomIn.bind(this); @@ -132,6 +136,7 @@ export default class VFBGraph extends Component { this.shiftOn = false; this.objectsLoaded = 0; this.focused = false; + this.focusedInstance = null; } componentDidMount () { @@ -139,7 +144,8 @@ export default class VFBGraph extends Component { this.__isMounted = true; if (this.state.currentQuery !== undefined && this.state.currentQuery !== null){ - this.updateGraph(this.props.instance); + this.focusedInstance = this.props.instance; + this.updateGraph(); } // Keyboard listener, detect when shift is pressed down @@ -148,6 +154,12 @@ export default class VFBGraph extends Component { self.shiftOn = true; } }); + + document.addEventListener("keyup", event => { + if (event.isComposing || event.keyCode === 16) { + self.shiftOn = false; + } + }); } componentDidUpdate () { @@ -211,6 +223,18 @@ export default class VFBGraph extends Component { this.queryNewInstance(node.title); } + /** + * Handle Menu drop down clicks + */ + handleMenuClick (query) { + if (this.__isMounted){ + // Show loading spinner while cypher query search occurs + this.setState({ loading : true , dropDownAnchorEl : null }); + // Perform cypher query + this.queryResults(query()) + } + } + /** * Query new instance by using 'addVfbId' functionality */ @@ -219,23 +243,33 @@ export default class VFBGraph extends Component { window.addVfbId(id); } + /** + * Gets notified every time the instance focused changes + */ + instanceFocusChange (instance) { + // Keep track of latest instance loaded/focused, will be needed to synchronize/update graph. + this.focusedInstance = instance; + + // Force an update on the graph only if there's no previous graph rendered. + if ( this.state.graph.nodes.length === 0 && this.state.graph.links.length === 0 ){ + this.updateGraph(); + } + } + /** * Re-render graph with a new instance */ - updateGraph (instance) { + updateGraph () { + var idToSearch = null; /* * function handler called by the VFBMain whenever there is an update of the instance on focus, * this will reflect and move to the node (if it exists) that we have on focus. */ - var innerInstance = undefined; - if (instance.getParent() !== null) { - innerInstance = instance.getParent(); + if (this.focusedInstance.getParent() !== null) { + idToSearch = this.focusedInstance.getParent().id; } else { - innerInstance = instance; - } - - // ID of instance used to perform cypher query - var idToSearch = innerInstance.id; + idToSearch = this.focusedInstance.id; + } if (this.__isMounted){ // Show loading spinner while cypher query search occurs @@ -413,13 +447,56 @@ export default class VFBGraph extends Component { linkWidth={1.25} controls = {
- - - + + + + + self.setState( { dropDownAnchorEl : event.currentTarget } )} + /> + self.setState( { dropDownAnchorEl : null } )} + PaperProps={{ + style: { + backgroundColor: stylingConfiguration.dropDownBackgroundColor, + marginTop: '30px', + color : stylingConfiguration.dropDownTextColor + } + }} + > + {stylingConfiguration.dropDownQueries.map(item => ( + self.handleMenuClick(item.query)} + style={{ fontSize : "12px" }} + onMouseEnter={e => { + e.target.style.color = stylingConfiguration.dropDownHoverTextColor; + e.target.style.backgroundColor = stylingConfiguration.dropDownHoverBackgroundColor; + } + } + onMouseLeave={e => { + e.target.style.color = stylingConfiguration.dropDownTextColor; + e.target.style.backgroundColor = stylingConfiguration.dropDownBackgroundColor; + } + } + > + {item.label} + + ))} +
} click={() => self.graphRef.current.ggv.current.zoomToFit()} - // Function triggered when hovering over a node + // Function triggered when hovering over a nodeoptions onNodeHover={node => { // Reset maps of hover nodes and links self.highlightNodes.clear(); diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js index 0a5863348..a4d7fd759 100644 --- a/components/interface/VFBTree/VFBTree.js +++ b/components/interface/VFBTree/VFBTree.js @@ -4,6 +4,8 @@ import { SliderPicker } from 'react-color'; import Tree from 'geppetto-client/js/components/interface/tree/Tree'; import CircularProgress from '@material-ui/core/CircularProgress'; import Tooltip from '@material-ui/core/Tooltip'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; import { createMuiTheme, MuiThemeProvider @@ -14,6 +16,7 @@ import 'react-sortable-tree/style.css'; var $ = require('jquery'); const restPostConfig = require('../../configuration/VFBTree/VFBTreeConfiguration').restPostConfig; const treeCypherQuery = require('../../configuration/VFBTree/VFBTreeConfiguration').treeCypherQuery; +const stylingConfiguration = require('../../configuration/VFBTree/VFBTreeConfiguration').styling; export default class VFBTree extends React.Component { @@ -29,7 +32,8 @@ export default class VFBTree extends React.Component { nodes: undefined, nodeSelected: undefined, displayColorPicker: false, - pickerAnchor: undefined + pickerAnchor: undefined, + dropDownAnchorEl : null }; this.initTree = this.initTree.bind(this); @@ -45,7 +49,9 @@ export default class VFBTree extends React.Component { this.updateSubtitle = this.updateSubtitle.bind(this); this.monitorMouseClick = this.monitorMouseClick.bind(this); this.convertDataForTree = this.convertDataForTree.bind(this); - + this.handleMenuClick = this.handleMenuClick.bind(this); + this.refineData = this.refineData.bind(this); + this.isNumber = require('./helper').isNumber; this.sortData = require('./helper').sortData; this.findRoot = require('./helper').findRoot; @@ -228,49 +234,7 @@ export default class VFBTree extends React.Component { errors: undefined, }); this.restPost(treeCypherQuery(instance)).done(data => { - /* - * we take the data provided by the cypher query and consume the until we obtain the treeData that can be given - * to the react-sortable-tree since it understands this data structure - */ - if (data.errors.length > 0) { - console.log("-- ERROR TREE COMPONENT --"); - console.log(data.errors); - this.setState({ errors: "Error retrieving the data - check the console for additional information" }); - } - - if (data.results.length > 0 && data.results[0].data.length > 0) { - var dataTree = this.parseGraphResultData(data); - var vertix = this.findRoot(data); - var imagesMap = this.buildDictClassToIndividual(data); - var nodes = this.sortData(this.convertNodes(dataTree.nodes, imagesMap), "id", this.defaultComparator); - var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); - var treeData = this.convertDataForTree(nodes, edges, vertix, imagesMap); - this.setState({ - loading: false, - errors: undefined, - dataTree: treeData, - root: vertix, - edges: edges, - nodes: nodes, - nodeSelected: (this.props.instance === undefined - ? treeData[0] - : (this.props.instance.getParent() === null - ? { subtitle: this.props.instance.getId() } - : { subtitle: this.props.instance.getParent().getId() })) - }); - } else { - var treeData = [{ - title: "No data available.", - subtitle: null, - children: [] - }]; - this.setState({ - dataTree: treeData, - root: undefined, - loading: false, - errors: undefined, - }); - } + that.refineData(data); }); } @@ -279,6 +243,65 @@ export default class VFBTree extends React.Component { this.selectNode(rowInfo.node); } } + + refineData (data) { + /* + * we take the data provided by the cypher query and consume the until we obtain the treeData that can be given + * to the react-sortable-tree since it understands this data structure + */ + if (data.errors.length > 0) { + console.log("-- ERROR TREE COMPONENT --"); + console.log(data.errors); + this.setState({ errors: "Error retrieving the data - check the console for additional information" }); + } + + if (data.results.length > 0 && data.results[0].data.length > 0) { + var dataTree = this.parseGraphResultData(data); + var vertix = this.findRoot(data); + var imagesMap = this.buildDictClassToIndividual(data); + var nodes = this.sortData(this.convertNodes(dataTree.nodes, imagesMap), "id", this.defaultComparator); + var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); + var treeData = this.convertDataForTree(nodes, edges, vertix, imagesMap); + this.setState({ + loading: false, + errors: undefined, + dataTree: treeData, + root: vertix, + edges: edges, + nodes: nodes, + nodeSelected: (this.props.instance === undefined + ? treeData[0] + : (this.props.instance.getParent() === null + ? { subtitle: this.props.instance.getId() } + : { subtitle: this.props.instance.getParent().getId() })) + }); + } else { + var treeData = [{ + title: "No data available.", + subtitle: null, + children: [] + }]; + this.setState({ + dataTree: treeData, + root: undefined, + loading: false, + errors: undefined, + }); + } + } + + /** + * Handle Menu drop down selections + */ + handleMenuClick (query) { + let self = this; + // Show loading spinner while cypher query search occurs + this.setState({ loading : true , dropDownAnchorEl : null , errors: undefined }); + // Perform cypher query + this.restPost(query()).done(data => { + self.refineData(data); + }) + } monitorMouseClick (e) { const clickCoord = { @@ -313,21 +336,23 @@ export default class VFBTree extends React.Component { var buttons = []; var fillCondition = "unknown"; var instanceLoaded = false; - if (rowInfo.node.instanceId.indexOf("VFB_") > -1) { - fillCondition = "3dAvailable"; - for (var i = 1; i < Instances.length; i++) { - if (Instances[i].id !== undefined && Instances[i].id === rowInfo.node.instanceId) { - instanceLoaded = true; - break; + if (rowInfo.node.instanceId !== undefined) { + if (rowInfo.node.instanceId.indexOf("VFB_") > -1) { + fillCondition = "3dAvailable"; + for (var i = 1; i < Instances.length; i++) { + if (Instances[i].id !== undefined && Instances[i].id === rowInfo.node.instanceId) { + instanceLoaded = true; + break; + } } - } - if (!instanceLoaded) { - fillCondition = "3dToLoad"; - } else { - if ((typeof Instances[rowInfo.node.instanceId].isVisible !== "undefined") && (Instances[rowInfo.node.instanceId].isVisible())) { - fillCondition = "3dVisible"; + if (!instanceLoaded) { + fillCondition = "3dToLoad"; } else { - fillCondition = "3dHidden"; + if ((typeof Instances[rowInfo.node.instanceId].isVisible !== "undefined") && (Instances[rowInfo.node.instanceId].isVisible())) { + fillCondition = "3dVisible"; + } else { + fillCondition = "3dHidden"; + } } } } @@ -502,6 +527,7 @@ export default class VFBTree extends React.Component { } render () { + let self = this; if (this.state.dataTree === undefined) { var treeData = [{ title: "No data available.", @@ -543,12 +569,60 @@ export default class VFBTree extends React.Component { treeData={treeData} activateParentsNodeOnClick={true} handleClick={this.nodeClick} - style={{ width: this.props.size.width, height: this.props.size.height, float: 'left', clear: 'both' }} + style={{ width: this.props.size.width, height: this.props.size.height, float: 'left', clear: 'both', left : "3rem" }} rowHeight={this.styles.row_height} getButtons={this.getButtons} getNodesProps={this.getNodes} searchQuery={this.state.nodeSelected === undefined ? this.props.instance.getParent().getId() : this.state.nodeSelected.subtitle} onlyExpandSearchedNodes={false} + // Controls for the Tree + controls = { +
+ + self.setState( { dropDownAnchorEl : event.currentTarget } )} + /> + self.setState( { dropDownAnchorEl : null } )} + PaperProps={{ + style: { + backgroundColor: stylingConfiguration.dropDownBackgroundColor, + marginTop: '30px', + color : stylingConfiguration.dropDownTextColor + } + }} + > + {stylingConfiguration.dropDownQueries.map(item => ( + self.handleMenuClick(item.query)} + style={{ fontSize : "12px" }} + onMouseEnter={e => { + e.target.style.color = stylingConfiguration.dropDownHoverTextColor; + e.target.style.backgroundColor = stylingConfiguration.dropDownHoverBackgroundColor; + } + } + onMouseLeave={e => { + e.target.style.color = stylingConfiguration.dropDownTextColor; + e.target.style.backgroundColor = stylingConfiguration.dropDownBackgroundColor; + } + } + > + {item.label} + + ))} + +
+ } /> } From 3024f88c1e6ff1e2be306f28b79bade02b8084f9 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 28 May 2020 16:05:59 -0700 Subject: [PATCH 02/12] Put document calls inside evaluate function --- tests/jest/vfb/utils.js | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/jest/vfb/utils.js b/tests/jest/vfb/utils.js index 9a7c436b4..337ba8b17 100644 --- a/tests/jest/vfb/utils.js +++ b/tests/jest/vfb/utils.js @@ -38,19 +38,23 @@ export const closeModalWindow = async (page) => { } export const flexWindowClick = async (title, selector) => { - if (document.getElementsByClassName("flexlayout__tab_button_content")[0].innerText == title) { - await document.getElementsByClassName(selector)[0].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[1].innerText == title) { - await document.getElementsByClassName(selector)[1].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[2].innerText == title) { - await document.getElementsByClassName(selector)[2].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[3].innerText == title) { - await document.getElementsByClassName(selector)[3].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[4].innerText == title) { - await document.getElementsByClassName(selector)[4].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[5].innerText == title) { - await document.getElementsByClassName(selector)[5].click(); - }else if (document.getElementsByClassName("flexlayout__tab_button_content")[6].innerText == title) { - await document.getElementsByClassName(selector)[6].click(); - } + await page.evaluate(() => + { + if (document.getElementsByClassName("flexlayout__tab_button_content")[0].innerText == title) { + document.getElementsByClassName(selector)[0].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[1].innerText == title) { + document.getElementsByClassName(selector)[1].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[2].innerText == title) { + document.getElementsByClassName(selector)[2].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[3].innerText == title) { + document.getElementsByClassName(selector)[3].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[4].innerText == title) { + document.getElementsByClassName(selector)[4].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[5].innerText == title) { + document.getElementsByClassName(selector)[5].click(); + }else if (document.getElementsByClassName("flexlayout__tab_button_content")[6].innerText == title) { + document.getElementsByClassName(selector)[6].click(); + } + } + ); } \ No newline at end of file From bf087fe6757e94b63d610618e2d795208834ad80 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 28 May 2020 17:04:56 -0700 Subject: [PATCH 03/12] pass title and selector to evaluate function --- tests/jest/vfb/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/jest/vfb/utils.js b/tests/jest/vfb/utils.js index 337ba8b17..a5086694b 100644 --- a/tests/jest/vfb/utils.js +++ b/tests/jest/vfb/utils.js @@ -38,7 +38,7 @@ export const closeModalWindow = async (page) => { } export const flexWindowClick = async (title, selector) => { - await page.evaluate(() => + await page.evaluate((title, selector) => { if (document.getElementsByClassName("flexlayout__tab_button_content")[0].innerText == title) { document.getElementsByClassName(selector)[0].click(); @@ -56,5 +56,5 @@ export const flexWindowClick = async (title, selector) => { document.getElementsByClassName(selector)[6].click(); } } - ); + , title, selector); } \ No newline at end of file From fcf8e7eb6d1bd207d5af864f3330620c2d111d5f Mon Sep 17 00:00:00 2001 From: jrmartin Date: Mon, 8 Jun 2020 13:57:34 -0700 Subject: [PATCH 04/12] Remove sync mechanism from VFTree --- components/VFBMain.js | 4 ++-- components/configuration/VFBMain/layoutModel.js | 5 +++++ components/interface/VFBTree/VFBTree.js | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/VFBMain.js b/components/VFBMain.js index 18aeb082d..ddeaf5eea 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -14,7 +14,7 @@ import HTMLViewer from 'geppetto-client/js/components/interface/htmlViewer/HTMLV import ControlPanel from 'geppetto-client/js/components/interface/controlPanel/controlpanel'; import * as FlexLayout from 'geppetto-client/js/components/interface/flexLayout2/src/index'; import VFBQuickHelp from './interface/VFBOverview/QuickHelp'; -// import VFBGraph from './interface/VFBGraph/VFBGraph'; +import VFBGraph from './interface/VFBGraph/VFBGraph'; require('../css/base.less'); require('../css/VFBMain.less'); @@ -35,7 +35,7 @@ export default class VFBMain extends React.Component { canvasVisible: true, termInfoVisible: true, treeBrowserVisible: true, - graphVisible : false, + graphVisible : true, sliceViewerVisible: true, tutorialWidgetVisible: false, spotlightVisible: true, diff --git a/components/configuration/VFBMain/layoutModel.js b/components/configuration/VFBMain/layoutModel.js index 2b3bb4516..f58478f5e 100644 --- a/components/configuration/VFBMain/layoutModel.js +++ b/components/configuration/VFBMain/layoutModel.js @@ -61,6 +61,11 @@ var modelJson = { "type": "tab", "name": "Template ROI Browser", "component": "treeBrowser" + }, + { + "type": "tab", + "name": "Graph", + "component": "vfbGraph" } ] }, diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js index 934394c9e..20c85d5f9 100644 --- a/components/interface/VFBTree/VFBTree.js +++ b/components/interface/VFBTree/VFBTree.js @@ -578,7 +578,6 @@ export default class VFBTree extends React.Component { // Controls for the Tree controls = {
- Date: Tue, 9 Jun 2020 16:26:28 -0700 Subject: [PATCH 05/12] Graph styling configuration. Makes refresh icon red whenever there's a new sync ready, shows tooltips and updates options --- components/VFBMain.js | 2 +- .../VFBGraph/graphConfiguration.js | 63 +++++--------- components/interface/VFBGraph/VFBGraph.js | 84 ++++++++++++------- 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/components/VFBMain.js b/components/VFBMain.js index ddeaf5eea..b9281bfa2 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -861,7 +861,7 @@ export default class VFBMain extends React.Component { this.UIElementsVisibility[component] = node.isVisible(); let _height = node.getRect().height; let _width = node.getRect().width; - return (
+ return (
this.graphReference = ref} instance={this.instanceOnFocus} visible={graphVisibility} />
); } diff --git a/components/configuration/VFBGraph/graphConfiguration.js b/components/configuration/VFBGraph/graphConfiguration.js index 2ed5203a3..99bfb1c7c 100644 --- a/components/configuration/VFBGraph/graphConfiguration.js +++ b/components/configuration/VFBGraph/graphConfiguration.js @@ -1,3 +1,14 @@ +var locationCypherQuery = instance => ({ + "statements": [ + { + "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" + + "has_postsynaptic_terminal_in|overlaps*..]->(x)" + + "WHERE n.short_form = '" + instance + "' return distinct n,r,x,n.short_form as root", + "resultDataContents": ["graph"] + } + ] +}); + var configuration = { resultsMapping: { @@ -42,45 +53,26 @@ var styling = { sync : "fa fa-refresh", dropdown : "fa fa-bars", }, + defaultRefreshIconColor : "white", + outOfSyncIconColor : "red", dropDownQueries : [ { - label : "Load Graph for 'fru-M-400042'", - query : () => ({ - "statements": [ - { - "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" - + "has_postsynaptic_terminal_in|overlaps*..]->(x)" - + "WHERE n.short_form = 'VFB_00001567' return distinct n,r,x,n.short_form as root", - "resultDataContents": ["graph"] - } - ] - }) - }, - { - label : "Load Graph for 'adult brain template JFRC2'", - query : () => ({ + label : instance => "Show classification of " + instance, + query : instance => ({ "statements": [ { - "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" - + "has_postsynaptic_terminal_in|overlaps*..]->(x)" - + "WHERE n.short_form = 'VFB_00017894' return distinct n,r,x,n.short_form as root", + "statement": "MATCH p=(n:Entity)-[:INSTANCEOF|:SUBCLASSOF*..]->(x)" + + "WHERE 'Anatomy' IN labels(x)" + + "AND n.short_form = '" + instance + "'" + + "RETURN p, n.short_form as root", "resultDataContents": ["graph"] } ] }) }, { - label : "Load Graph for 'Medulla'", - query : () => ({ - "statements": [ - { - "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" - + "has_postsynaptic_terminal_in|overlaps*..]->(x)" - + "WHERE n.short_form = 'VFB_00030624' return distinct n,r,x,n.short_form as root", - "resultDataContents": ["graph"] - } - ] - }) + label : instance => "Show location of " + instance , + query : instance => locationCypherQuery(instance) } ], dropDownHoverBackgroundColor : "#11bffe", @@ -94,20 +86,9 @@ var restPostConfig = { contentType: "application/json" }; -var cypherQuery = instance => ({ - "statements": [ - { - "statement": "MATCH p=(n:Entity)-[r:INSTANCEOF|part_of|has_synaptic_terminal_in|has_presynaptic_terminal_in" - + "has_postsynaptic_terminal_in|overlaps*..]->(x)" - + "WHERE n.short_form = '" + instance + "' return distinct n,r,x,n.short_form as root", - "resultDataContents": ["graph"] - } - ] -}); - module.exports = { configuration, styling, restPostConfig, - cypherQuery + locationCypherQuery }; diff --git a/components/interface/VFBGraph/VFBGraph.js b/components/interface/VFBGraph/VFBGraph.js index 0a8382223..0abc65042 100644 --- a/components/interface/VFBGraph/VFBGraph.js +++ b/components/interface/VFBGraph/VFBGraph.js @@ -4,13 +4,14 @@ import GeppettoGraphVisualization from 'geppetto-client/js/components/interface/ import CircularProgress from '@material-ui/core/CircularProgress'; import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; +import Tooltip from '@material-ui/core/Tooltip'; /** * Read configuration from graphConfiguration.js */ const configuration = require('../../configuration/VFBGraph/graphConfiguration').configuration; const restPostConfig = require('../../configuration/VFBGraph/graphConfiguration').restPostConfig; -const cypherQuery = require('../../configuration/VFBGraph/graphConfiguration').cypherQuery; +const cypherQuery = require('../../configuration/VFBGraph/graphConfiguration').locationCypherQuery; const stylingConfiguration = require('../../configuration/VFBGraph/graphConfiguration').styling; /** @@ -115,7 +116,13 @@ export default class VFBGraph extends Component { constructor (props) { super(props); - this.state = { graph : { nodes : [], links : [] } , loading : true, currentQuery : this.props.instance , dropDownAnchorEl : null }; + this.state = { + graph : { nodes : [], links : [] }, + loading : true, + currentQuery : this.props.instance, + dropDownAnchorEl : null, + optionsIconColor : stylingConfiguration.defaultRefreshIconColor + } this.updateGraph = this.updateGraph.bind(this); this.instanceFocusChange = this.instanceFocusChange.bind(this); this.queryResults = this.queryResults.bind(this); @@ -207,20 +214,15 @@ export default class VFBGraph extends Component { * Handle Left click on Nodes */ handleNodeLeftClick (node, event) { - if ( this.shiftOn ){ - this.queryNewInstance(node.title); - this.shiftOn = false; - } else { - this.graphRef.current.ggv.current.centerAt(node.x , node.y, 1000); - this.graphRef.current.ggv.current.zoom(2, 1000); - } + this.queryNewInstance(node.title); } - + /** - * Handle Right click on Nodes, creates a new graph using the clicked node's ID as instance for cypher query + * Handle Right click on Nodes */ handleNodeRightClick (node, event) { - this.queryNewInstance(node.title); + this.graphRef.current.ggv.current.centerAt(node.x , node.y, 1000); + this.graphRef.current.ggv.current.zoom(2, 1000); } /** @@ -231,7 +233,7 @@ export default class VFBGraph extends Component { // Show loading spinner while cypher query search occurs this.setState({ loading : true , dropDownAnchorEl : null }); // Perform cypher query - this.queryResults(query()) + this.queryResults(query(this.state.currentQuery)) } } @@ -253,6 +255,8 @@ export default class VFBGraph extends Component { // Force an update on the graph only if there's no previous graph rendered. if ( this.state.graph.nodes.length === 0 && this.state.graph.links.length === 0 ){ this.updateGraph(); + } else { + this.setState( { optionsIconColor : stylingConfiguration.outOfSyncIconColor } ); } } @@ -273,7 +277,7 @@ export default class VFBGraph extends Component { if (this.__isMounted){ // Show loading spinner while cypher query search occurs - this.setState({ loading : true, currentQuery : idToSearch }); + this.setState({ loading : true, currentQuery : idToSearch, optionsIconColor : stylingConfiguration.defaultRefreshIconColor }); // Perform cypher query this.queryResults(cypherQuery(idToSearch), idToSearch) } @@ -302,9 +306,7 @@ export default class VFBGraph extends Component { url: url, headers: { 'content-type': contentType }, data: request, - }).then( function (response) { - console.log(response); - + }).then( function (response) { var blob = new Blob(["onmessage = " + refineData ]); var blobUrl = window.URL.createObjectURL(blob); @@ -328,7 +330,7 @@ export default class VFBGraph extends Component { worker.postMessage({ message: "refine", params: { results: response.data, value: instanceID, configuration : configuration, NODE_WIDTH : NODE_WIDTH, NODE_HEIGHT : NODE_HEIGHT } }); }) .catch( function (error) { - console.log(error); + console.log("HTTP Request Error: ", error); self.setState( { loading : false } ); }) } @@ -376,7 +378,6 @@ export default class VFBGraph extends Component { ?

No Graph Available for {this.state.currentQuery}

:
- - self.setState( { dropDownAnchorEl : event.currentTarget } )} - /> + Refresh}> + + + + Options}> + self.setState( { dropDownAnchorEl : event.currentTarget } )} + /> + {stylingConfiguration.dropDownQueries.map(item => ( self.handleMenuClick(item.query)} style={{ fontSize : "12px" }} onMouseEnter={e => { @@ -489,7 +513,7 @@ export default class VFBGraph extends Component { } } > - {item.label} + {item.label(self.state.currentQuery)} ))} From 87d512d54db4a57df7851ecf7ab43682d3c1577b Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 12 Jun 2020 13:55:48 +0100 Subject: [PATCH 06/12] reverted the VFBTree as per sprint discussion --- components/interface/VFBTree/VFBTree.js | 191 ++++++++---------------- 1 file changed, 59 insertions(+), 132 deletions(-) diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js index 20c85d5f9..bb54c3d0c 100644 --- a/components/interface/VFBTree/VFBTree.js +++ b/components/interface/VFBTree/VFBTree.js @@ -4,8 +4,6 @@ import { SliderPicker } from 'react-color'; import Tree from 'geppetto-client/js/components/interface/tree/Tree'; import CircularProgress from '@material-ui/core/CircularProgress'; import Tooltip from '@material-ui/core/Tooltip'; -import Menu from '@material-ui/core/Menu'; -import MenuItem from '@material-ui/core/MenuItem'; import { createMuiTheme, MuiThemeProvider @@ -16,7 +14,6 @@ import 'react-sortable-tree/style.css'; var $ = require('jquery'); const restPostConfig = require('../../configuration/VFBTree/VFBTreeConfiguration').restPostConfig; const treeCypherQuery = require('../../configuration/VFBTree/VFBTreeConfiguration').treeCypherQuery; -const stylingConfiguration = require('../../configuration/VFBTree/VFBTreeConfiguration').styling; export default class VFBTree extends React.Component { @@ -32,8 +29,7 @@ export default class VFBTree extends React.Component { nodes: undefined, nodeSelected: undefined, displayColorPicker: false, - pickerAnchor: undefined, - dropDownAnchorEl : null + pickerAnchor: undefined }; this.initTree = this.initTree.bind(this); @@ -49,9 +45,7 @@ export default class VFBTree extends React.Component { this.updateSubtitle = this.updateSubtitle.bind(this); this.monitorMouseClick = this.monitorMouseClick.bind(this); this.convertDataForTree = this.convertDataForTree.bind(this); - this.handleMenuClick = this.handleMenuClick.bind(this); - this.refineData = this.refineData.bind(this); - + this.isNumber = require('./helper').isNumber; this.sortData = require('./helper').sortData; this.findRoot = require('./helper').findRoot; @@ -234,7 +228,49 @@ export default class VFBTree extends React.Component { errors: undefined, }); this.restPost(treeCypherQuery(instance)).done(data => { - that.refineData(data); + /* + * we take the data provided by the cypher query and consume the until we obtain the treeData that can be given + * to the react-sortable-tree since it understands this data structure + */ + if (data.errors.length > 0) { + console.log("-- ERROR TREE COMPONENT --"); + console.log(data.errors); + this.setState({ errors: "Error retrieving the data - check the console for additional information" }); + } + + if (data.results.length > 0 && data.results[0].data.length > 0) { + var dataTree = this.parseGraphResultData(data); + var vertix = this.findRoot(data); + var imagesMap = this.buildDictClassToIndividual(data); + var nodes = this.sortData(this.convertNodes(dataTree.nodes, imagesMap), "id", this.defaultComparator); + var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); + var treeData = this.convertDataForTree(nodes, edges, vertix, imagesMap); + this.setState({ + loading: false, + errors: undefined, + dataTree: treeData, + root: vertix, + edges: edges, + nodes: nodes, + nodeSelected: (this.props.instance === undefined + ? treeData[0] + : (this.props.instance.getParent() === null + ? { subtitle: this.props.instance.getId() } + : { subtitle: this.props.instance.getParent().getId() })) + }); + } else { + var treeData = [{ + title: "No data available.", + subtitle: null, + children: [] + }]; + this.setState({ + dataTree: treeData, + root: undefined, + loading: false, + errors: undefined, + }); + } }); } @@ -243,65 +279,6 @@ export default class VFBTree extends React.Component { this.selectNode(rowInfo.node); } } - - refineData (data) { - /* - * we take the data provided by the cypher query and consume the until we obtain the treeData that can be given - * to the react-sortable-tree since it understands this data structure - */ - if (data.errors.length > 0) { - console.log("-- ERROR TREE COMPONENT --"); - console.log(data.errors); - this.setState({ errors: "Error retrieving the data - check the console for additional information" }); - } - - if (data.results.length > 0 && data.results[0].data.length > 0) { - var dataTree = this.parseGraphResultData(data); - var vertix = this.findRoot(data); - var imagesMap = this.buildDictClassToIndividual(data); - var nodes = this.sortData(this.convertNodes(dataTree.nodes, imagesMap), "id", this.defaultComparator); - var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); - var treeData = this.convertDataForTree(nodes, edges, vertix, imagesMap); - this.setState({ - loading: false, - errors: undefined, - dataTree: treeData, - root: vertix, - edges: edges, - nodes: nodes, - nodeSelected: (this.props.instance === undefined - ? treeData[0] - : (this.props.instance.getParent() === null - ? { subtitle: this.props.instance.getId() } - : { subtitle: this.props.instance.getParent().getId() })) - }); - } else { - var treeData = [{ - title: "No data available.", - subtitle: null, - children: [] - }]; - this.setState({ - dataTree: treeData, - root: undefined, - loading: false, - errors: undefined, - }); - } - } - - /** - * Handle Menu drop down selections - */ - handleMenuClick (query) { - let self = this; - // Show loading spinner while cypher query search occurs - this.setState({ loading : true , dropDownAnchorEl : null , errors: undefined }); - // Perform cypher query - this.restPost(query()).done(data => { - self.refineData(data); - }) - } monitorMouseClick (e) { const clickCoord = { @@ -336,23 +313,21 @@ export default class VFBTree extends React.Component { var buttons = []; var fillCondition = "unknown"; var instanceLoaded = false; - if (rowInfo.node.instanceId !== undefined) { - if (rowInfo.node.instanceId.indexOf("VFB_") > -1) { - fillCondition = "3dAvailable"; - for (var i = 1; i < Instances.length; i++) { - if (Instances[i].id !== undefined && Instances[i].id === rowInfo.node.instanceId) { - instanceLoaded = true; - break; - } + if (rowInfo.node.instanceId != undefined && rowInfo.node.instanceId.indexOf("VFB_") > -1) { + fillCondition = "3dAvailable"; + for (var i = 1; i < Instances.length; i++) { + if (Instances[i].id !== undefined && Instances[i].id === rowInfo.node.instanceId) { + instanceLoaded = true; + break; } - if (!instanceLoaded) { - fillCondition = "3dToLoad"; + } + if (!instanceLoaded) { + fillCondition = "3dToLoad"; + } else { + if ((typeof Instances[rowInfo.node.instanceId].isVisible !== "undefined") && (Instances[rowInfo.node.instanceId].isVisible())) { + fillCondition = "3dVisible"; } else { - if ((typeof Instances[rowInfo.node.instanceId].isVisible !== "undefined") && (Instances[rowInfo.node.instanceId].isVisible())) { - fillCondition = "3dVisible"; - } else { - fillCondition = "3dHidden"; - } + fillCondition = "3dHidden"; } } } @@ -527,7 +502,6 @@ export default class VFBTree extends React.Component { } render () { - let self = this; if (this.state.dataTree === undefined) { var treeData = [{ title: "No data available.", @@ -569,59 +543,12 @@ export default class VFBTree extends React.Component { treeData={treeData} activateParentsNodeOnClick={true} handleClick={this.nodeClick} - style={{ width: this.props.size.width, height: this.props.size.height, float: 'left', clear: 'both', left : "3rem" }} + style={{ width: this.props.size.width, height: this.props.size.height, float: 'left', clear: 'both' }} rowHeight={this.styles.row_height} getButtons={this.getButtons} getNodesProps={this.getNodes} searchQuery={this.state.nodeSelected === undefined ? this.props.instance.getParent().getId() : this.state.nodeSelected.subtitle} onlyExpandSearchedNodes={false} - // Controls for the Tree - controls = { -
- self.setState( { dropDownAnchorEl : event.currentTarget } )} - /> - self.setState( { dropDownAnchorEl : null } )} - PaperProps={{ - style: { - backgroundColor: stylingConfiguration.dropDownBackgroundColor, - marginTop: '30px', - color : stylingConfiguration.dropDownTextColor - } - }} - > - {stylingConfiguration.dropDownQueries.map(item => ( - self.handleMenuClick(item.query)} - style={{ fontSize : "12px" }} - onMouseEnter={e => { - e.target.style.color = stylingConfiguration.dropDownHoverTextColor; - e.target.style.backgroundColor = stylingConfiguration.dropDownHoverBackgroundColor; - } - } - onMouseLeave={e => { - e.target.style.color = stylingConfiguration.dropDownTextColor; - e.target.style.backgroundColor = stylingConfiguration.dropDownBackgroundColor; - } - } - > - {item.label} - - ))} - -
- } /> }
From c59fa871e4611e9d8ac7d365c200722e704da082 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 12 Jun 2020 14:04:03 +0100 Subject: [PATCH 07/12] eye / eye-slash fix --- components/interface/VFBTree/VFBTree.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js index bb54c3d0c..2adb12e0c 100644 --- a/components/interface/VFBTree/VFBTree.js +++ b/components/interface/VFBTree/VFBTree.js @@ -334,7 +334,7 @@ export default class VFBTree extends React.Component { switch (fillCondition) { case "3dToLoad": - buttons.push(