diff --git a/Dockerfile b/Dockerfile
index a82f3095b..b389561f6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,7 @@ ARG geppettoSimulationRelease=vfb_20200604_a
ARG geppettoDatasourceRelease=vfb_20200604_a
ARG geppettoModelSwcRelease=v1.0.1
ARG geppettoFrontendRelease=development
-ARG geppettoClientRelease=VFBv2.2.0.7
+ARG geppettoClientRelease=VFBv2.2.2
ARG ukAcVfbGeppettoRelease=download
ARG mvnOpt="-Dhttps.protocols=TLSv1.2 -DskipTests --quiet -Pmaster"
@@ -38,6 +38,7 @@ ENV VFB_OWL_SERVER=${VFB_OWL_SERVER_ARG}
ENV VFB_R_SERVER=${VFB_R_SERVER_ARG}
ENV SOLR_SERVER=${SOLR_SERVER_ARG}
ENV googleAnalyticsSiteCode=${googleAnalyticsSiteCode_ARG}
+ENV LOG4J_FORMAT_MSG_NO_LOOKUPS=true
RUN /bin/echo -e "\e[1;35mORIGIN BRANCH ------------ $originBranch\e[0m" &&\
/bin/echo -e "\e[1;35mTARGET BRANCH ------------ $targetBranch\e[0m" &&\
diff --git a/components/VFBMain.js b/components/VFBMain.js
index e16e44f06..aefbb6272 100644
--- a/components/VFBMain.js
+++ b/components/VFBMain.js
@@ -9,6 +9,7 @@ import VFBTermInfoWidget from './interface/VFBTermInfo/VFBTermInfo';
import Logo from '@geppettoengine/geppetto-client/components/interface/logo/Logo';
import Canvas from '@geppettoengine/geppetto-client/components/interface/3dCanvas/Canvas';
import QueryBuilder from '@geppettoengine/geppetto-client/components/interface/query/queryBuilder';
+import VFBDownloadContents from './interface/VFBDownloadContents/VFBDownloadContents';
import VFBUploader from './interface/VFBUploader/VFBUploader';
import HTMLViewer from '@geppettoengine/geppetto-ui/html-viewer/HTMLViewer';
import VFBListViewer from './interface/VFBListViewer/VFBListViewer';
@@ -52,6 +53,7 @@ class VFBMain extends React.Component {
quickHelpVisible: undefined,
UIUpdated: true,
wireframeVisible: false,
+ downloadContentsVisible : true,
uploaderContentsVisible : true
};
@@ -488,6 +490,12 @@ class VFBMain extends React.Component {
[buttonState]: !this.state[buttonState]
});
break;
+ case 'downloadContentsVisible':
+ this.refs.downloadContentsRef?.openDialog();
+ break;
+ case 'uploaderContentsVisible':
+ this.refs.uploaderContentsRef?.openDialog();
+ break;
case 'quickHelpVisible':
if (this.state[buttonState] === undefined) {
this.setState({
@@ -527,6 +535,9 @@ class VFBMain extends React.Component {
case 'triggerSetTermInfo':
this.handlerInstanceUpdate(click.value[0]);
break;
+ case 'downloadContentsVisible':
+ this.refs.downloadContentsRef?.openDialog();
+ break;
case 'uploaderContentsVisible':
this.refs.uploaderContentsRef?.openDialog();
break;
@@ -1286,63 +1297,6 @@ class VFBMain extends React.Component {
idsList += ",";
}
idsList += this.idFromURL;
- // populate page meta for this term for indexing
- try {
- window.ga('vfb.send', 'pageview', window.location.href );
- if ( window.XMLHttpRequest ) {
- var xhr = new XMLHttpRequest();
- xhr.onload = function () {
- try {
- if (this.responseXML.title.indexOf("404 Not Found") < 0) {
- document.title = 'Virtual Fly Brain (' + this.responseXML.title + ')';
- document.body.style.font = "x-large";
- document.querySelector('meta[property="og:title"]').setAttribute("content",this.responseXML.title);
- document.querySelector('meta[name="description"]').setAttribute("content",this.responseXML.getElementById('json').innerText.substring(0, 4900));
- document.querySelector('meta[property="og:description"]').setAttribute("content",this.responseXML.getElementById('json').innerText.substring(0, 4900));
- if (document.getElementById('metaDesc') != null) {
- if (this.responseXML.head != undefined && this.responseXML.head.getElementsByTagName('script') != undefined && this.responseXML.head.getElementsByTagName('script') != null && this.responseXML.head.getElementsByTagName('script')[1] != undefined) {
- document.getElementById('metaDesc').innerHTML = this.responseXML.head.getElementsByTagName('script')[1].innerHTML;
- }
- } else {
- if (this.responseXML.head != undefined && this.responseXML.head.getElementsByTagName('script') != undefined && this.responseXML.head.getElementsByTagName('script') != null && this.responseXML.head.getElementsByTagName('script')[1] != undefined) {
- var script = document.createElement('script');
- script.type = 'application/ld+json';
- script.id = 'metaDesc';
- script.innerHTML = this.responseXML.head.getElementsByTagName('script')[1].innerHTML;
- document.getElementsByTagName('head')[0].appendChild(script);
- }
- }
- var viewport = !!document.querySelector("meta[name='viewport']");
- viewport = viewport ? document.querySelector("meta[name='viewport']") : document.createElement('meta');
- viewport.setAttribute('name', 'viewport');
- viewport.setAttribute('content', 'width=device-width, initial-scale=1');
- document.head.appendChild(viewport);
- }
- } catch (err) {
- console.log(err);
- }
- }
- xhr.open( 'GET', 'https://virtualflybrain.org/data/VFB/json/' + this.idFromURL + '.html')
- xhr.responseType = 'document';
- xhr.send();
- }
- } catch (err) {
- console.error(err);
- }
- try {
- var link = !!document.querySelector("link[rel='amphtml']");
- link = link ? document.querySelector("link[rel='amphtml']") : document.createElement('link');
- link.setAttribute('rel', 'amphtml');
- link.setAttribute('href', 'https://virtualflybrain.org/data/VFB/json/' + this.idFromURL + '.html');
- document.head.appendChild(link);
- var conlink = !!document.querySelector("link[rel='canonical']");
- conlink = conlink ? document.querySelector("link[rel='canonical']") : document.createElement('link');
- conlink.setAttribute('rel', 'canonical');
- conlink.setAttribute('href', 'https://virtualflybrain.org/reports/' + this.idFromURL);
- document.head.appendChild(conlink);
- } catch (err) {
- console.error(err);
- }
} else if (idList[list].indexOf("i=") > -1) {
if (idsList.length > 0) {
idsList = "," + idsList;
@@ -1351,7 +1305,7 @@ class VFBMain extends React.Component {
} else if (idList[list].indexOf("q=") > -1) {
const multipleQueries = idList[list].replace("q=","").replace("%20", " ").split(";");
let that = this;
- multipleQueries?.forEach( query => {
+ multipleQueries?.forEach( query => {
const querySplit = query.split(",");
that.urlQueryLoader.push({ id : querySplit[0].trim(), selection : querySplit[1].trim() });
});
@@ -1392,7 +1346,7 @@ class VFBMain extends React.Component {
GEPPETTO.on(GEPPETTO.Events.Instance_added, function (instance) {
that.props.instanceAdded(instance);
});
-
+
GEPPETTO.on(GEPPETTO.Events.Instances_created, function (instances) {
// Set template Instance to be not clickable in 3D viewer
if ( instances[0]?.id?.includes(window.templateID) ) {
@@ -1529,6 +1483,10 @@ class VFBMain extends React.Component {
GEPPETTO.on(GEPPETTO.Events.Websocket_disconnected, function () {
window.ga('vfb.send', 'event', 'disconnected', 'websocket-disconnect', (window.location.pathname + window.location.search));
+ if (GEPPETTO.MessageSocket.protocol == 'wss://' && location.protocol !== 'https:') {
+ console.log("%c Unsecure connection used reloading with HTTPS connection... ", 'background: #444; color: #bada55');
+ location.replace(`https:${location.href.substring(location.protocol.length)}`);
+ }
if (GEPPETTO.MessageSocket.socketStatus == GEPPETTO.Resources.SocketStatus.CLOSE) {
if (GEPPETTO.MessageSocket.attempts < 10) {
window.ga('vfb.send', 'event', 'reconnect-attempt:' + GEPPETTO.MessageSocket.attempts, 'websocket-disconnect', (window.location.pathname + window.location.search));
@@ -1752,7 +1710,10 @@ class VFBMain extends React.Component {
searchConfiguration={this.searchConfiguration}
datasourceConfiguration={this.datasourceConfiguration} />
+
+
+
{this.htmlToolbarRender}
);
diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js
index 66339c535..d73bcc55f 100644
--- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js
+++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js
@@ -19,11 +19,20 @@ var locationCypherQuery = ( instances, paths, weight ) => ({
+ " WITH * ORDER BY index DESC"
+ " UNWIND relationships(path) as sr"
+ " OPTIONAL MATCH cp=(x:Neuron:has_neuron_connectivity)-[:synapsed_to]-(y:Neuron:has_neuron_connectivity) WHERE x=apoc.rel.startNode(sr) AND y=apoc.rel.endNode(sr) OPTIONAL MATCH fp=(x)-[r:synapsed_to]->(y) WHERE r.weight[0] >= " + weight?.toString()
- + " RETURN distinct a as root, collect(distinct fp) as pp, collect(distinct cp) as p, collect(distinct id(r)) as fr, sourceNode as source, targetNode as target, max(length(path)) as maxHops, collect(distinct toString(id(r))+':'+toString(index)) as relationshipY ",
+ + " OPTIONAL MATCH (x)-[xio:INSTANCEOF]->(xpc:Class) OPTIONAL MATCH (y)-[yio:INSTANCEOF]->(ypc:Class) WITH *,'\"'+ x.short_form+'\":{\"'+xpc.short_form+'\":\"' + xpc.label + '\"},\"'+ y.short_form+'\":{\"'+ypc.short_form+'\":\"' + ypc.label + '\"}' as Class"
+ + " RETURN distinct a as root, collect(distinct fp) as pp, collect(distinct cp) as p, collect(distinct id(r)) as fr, sourceNode as source, targetNode as target, max(length(path)) as maxHops, collect(distinct toString(id(r))+':'+toString(index)) as relationshipY, "
+ + " apoc.convert.fromJsonMap('{' + apoc.text.join(collect(Class),',') + '}') as class ",
"resultDataContents": ["row", "graph"]
}
]
});
+
+var Neo4jLabels = {
+ FAFB : "FAFB",
+ L1EM : "L1EM",
+ FlyEM_HB : "FlyEM_HB"
+}
+
// See query explanation on https://github.com/VirtualFlyBrain/graph_queries/blob/main/weighted_path.md
var configuration = {
@@ -63,7 +72,7 @@ var styling = {
// Color apply to target and source nodes when hovering over a link or a node.
defaultNeighborNodesHoverColor : "orange",
// Font used for text in nodes
- defaultNodeFont : "5px sans-serif",
+ defaultNodeFont : "8px sans-serif",
// Color of font in node's text
defaultNodeFontColor : "black",
// Node border color
@@ -71,38 +80,35 @@ var styling = {
// When hovering over a node, the node's border color changes to create a halo effect
defaultNodeHoverBoderColor : "red",
// Title bar (in node) background color
- defaultNodeTitleBackgroundColor : "#11bffe",
+ defaultNodeTitleBackgroundColor : "grey",
// Description area (in node) background color
defaultNodeDescriptionBackgroundColor : "white",
nodeColorsByLabel : {
- "Template" : "#ff6cc8",
- "GABAergic" : "#9551ff",
- "Dopaminergic" : "#3551ff",
- "Cholinergic" : "#95515f",
- "Glutamatergic" : "#95f1ff",
- "Octopaminergic" : "#f3511f",
- "Serotonergic" : "#9501f0",
- "Motor_neuron" : "#fffa30",
- "Sensory_neuron" : "#ff3a3a",
- "Peptidergic_neuron" : "#5f6a3a",
- "Glial_cell" : "#ff3a6a",
- "Clone" : "#d6007d",
- "Synaptic_neuropil" : "#00a2aa",
- "License" : "#0164d8",
- "Person" : "#023f00",
- "Neuron" : "#7f2100",
- "Neuron_projection_bundle" : "#d6327d",
- "Resource" : "#005f1d",
- "Site" : "#005f1d",
- "Expression_pattern" : "#534700",
- "Split" : "#e012e3",
- "DataSet" : "#b700b5",
- "Ganglion" : "#d6007d",
- "Neuromere" : "#d6507d",
- "Cell" : "#ff6a3a",
- "Property" : "#005f1d",
- "Anatomy" : "#00a2aa",
- "_Class" : "#0164d8"
+ "GABAergic" : "#377eb8",
+ "Neuron_projection_bundle" : "#ff7f00",
+ "Nervous_system" : "#4daf4a",
+ "Dopaminergic" : "#f781bf",
+ "Motor_neuron" : "#a65628",
+ "Cholinergic" : "#984ea3",
+ "Neuromere" : "#999999",
+ "Expression_pattern_fragment" : "#e41a1c",
+ "Peptidergic_neuron" : "#dede00",
+ "Ganglion" : "#377eb8",
+ "Muscle" : "#ff7f00",
+ "Glutamatergic" : "#4daf4a",
+ "Cluster" : "#f781bf",
+ "Clone" : "#a65628",
+ "Octopaminergic" : "#984ea3",
+ "Anatomy" : "#999999",
+ "Adult" : "#e41a1c",
+ "Synaptic_neuropil_subdomain" : "#dede00",
+ "Expression_pattern" : "#377eb8",
+ "Synaptic_neuropil_block" : "#ff7f00",
+ "Synaptic_neuropil_domain" : "#4daf4a",
+ "Serotonergic" : "#f781bf",
+ "Larva" : "#a65628",
+ "Neuron" : "#984ea3",
+ "Sensory_neuron" : "#999999"
},
controlIcons : {
home : "fa fa-home",
@@ -120,5 +126,6 @@ module.exports = {
configuration,
styling,
restPostConfig,
- locationCypherQuery
+ locationCypherQuery,
+ Neo4jLabels
};
diff --git a/components/configuration/VFBDownloadContents/configuration.json b/components/configuration/VFBDownloadContents/configuration.json
new file mode 100644
index 000000000..55ad5f5ce
--- /dev/null
+++ b/components/configuration/VFBDownloadContents/configuration.json
@@ -0,0 +1,34 @@
+{
+ "postURL":"https://zip.virtualflybrain.org/download",
+ "contentType": "application/json",
+ "zipName" : "VFB Files.zip",
+ "options" :{
+ "obj": {
+ "label" : "OBJ",
+ "tooltip" : "Download OBJ"
+ },
+ "swc": {
+ "label" : "SWC",
+ "tooltip" : "Download SWC"
+ },
+ "nrrd": {
+ "label" : "NRRD",
+ "tooltip" : "Download NRRD"
+ },
+ "reference": {
+ "label" : "References",
+ "tooltip" : "Download References"
+ }
+ },
+ "text" : {
+ "title" : "Download Data",
+ "typesSubtitle" : "Please select the desired types",
+ "variablesSubtitle" : "Please select Variables:",
+ "noVariablesSubtitle" : "No loaded variables",
+ "errorMessage" : "Something went wrong... We were not able to download the data. Please try again.",
+ "noEntriesFound" : "No entries found for the types and variables selected.",
+ "cancelButton" : "Cancel",
+ "downloadButton" : "Download",
+ "tryAgainButton" : "Try Again"
+ }
+}
\ No newline at end of file
diff --git a/components/configuration/VFBDownloadContents/nrrd.png b/components/configuration/VFBDownloadContents/nrrd.png
new file mode 100644
index 000000000..beee12a5c
Binary files /dev/null and b/components/configuration/VFBDownloadContents/nrrd.png differ
diff --git a/components/configuration/VFBDownloadContents/obj.png b/components/configuration/VFBDownloadContents/obj.png
new file mode 100644
index 000000000..fbcc914c7
Binary files /dev/null and b/components/configuration/VFBDownloadContents/obj.png differ
diff --git a/components/configuration/VFBDownloadContents/reference.png b/components/configuration/VFBDownloadContents/reference.png
new file mode 100644
index 000000000..b821fe7e1
Binary files /dev/null and b/components/configuration/VFBDownloadContents/reference.png differ
diff --git a/components/configuration/VFBDownloadContents/swc.png b/components/configuration/VFBDownloadContents/swc.png
new file mode 100644
index 000000000..3eb5d02b6
Binary files /dev/null and b/components/configuration/VFBDownloadContents/swc.png differ
diff --git a/components/configuration/VFBMain/searchConfiguration.js b/components/configuration/VFBMain/searchConfiguration.js
index 859ef3327..b2c8c1037 100644
--- a/components/configuration/VFBMain/searchConfiguration.js
+++ b/components/configuration/VFBMain/searchConfiguration.js
@@ -60,7 +60,7 @@ var searchStyle = {
singleResult: {
"color": "white",
"fontSize": "18px",
-
+ "whiteSpace" : "normal",
":hover": {
"color": "#11bffe",
"background-color": "#252323",
@@ -91,7 +91,7 @@ var datasourceConfiguration = {
"defType": "edismax",
"qf": "label^100 synonym^100 label_autosuggest_ws label_autosuggest_e label_autosuggest synonym_autosuggest_ws synonym_autosuggest shortform_autosuggest",
"indent": "true",
- "fl": "short_form,label,synonym,id,facets_annotation",
+ "fl": "short_form,label,synonym,id,facets_annotation,unique_facets",
"start": "0",
"pf":"true",
"fq": [
@@ -99,7 +99,7 @@ var datasourceConfiguration = {
],
"rows": "100",
"wt": "json",
- "bq": "shortform_autosuggest:VFB*^110.0 shortform_autosuggest:FBbt*^100.0 label_s:\"\"^2 synonym_s:\"\" short_form:FBbt_00003982^2 facets_annotation:Deprecated^0.001"
+ "bq": "shortform_autosuggest:VFBexp*^10.0 shortform_autosuggest:VFB*^100.0 shortform_autosuggest:FBbt*^100.0 label_s:\"\"^2 synonym_s:\"\" short_form:FBbt_00003982^2 facets_annotation:Deprecated^0.001"
}
};
@@ -107,9 +107,13 @@ var searchConfiguration = {
"resultsMapping":
{
"name": "label",
- "id": "short_form"
+ "id": "short_form",
+ "labels" : "unique_facets"
},
+ "label_manipulation" : label => label,
"filters_expanded": true,
+ "filter_positive" : "^100",
+ "filter_negative" : "^0.001",
"filters": [
{
"key": "facets_annotation",
@@ -305,6 +309,13 @@ var searchConfiguration = {
if (b.label.toLowerCase().indexOf(InputString.toLowerCase()) > -1 && b.label.toLowerCase().indexOf(InputString.toLowerCase()) < a.label.toLowerCase().indexOf(InputString.toLowerCase())) {
return 1;
}
+ // move up expression (VFBexp) terms
+ if (a.id.indexOf("VFBexp") > -1 && b.id.indexOf("VFBexp") < 0) {
+ return -1;
+ }
+ if (b.id.indexOf("VFBexp") > -1 && a.id.indexOf("VFBexp") < 0) {
+ return 1;
+ }
// if the match in the id is closer to start then move up
if (a.id.toLowerCase().indexOf(InputString.toLowerCase()) > -1 && a.id.toLowerCase().indexOf(InputString.toLowerCase()) < b.id.toLowerCase().indexOf(InputString.toLowerCase())) {
return -1;
@@ -323,11 +334,16 @@ var searchConfiguration = {
},
"clickHandler": function (id) {
window.addVfbId(id);
+ },
+ "Neo4jLabels" : {
+ "FAFB" : "FAFB",
+ "L1EM" : "L1EM",
+ "FlyEM_HB" : "FlyEM_HB"
}
};
module.exports = {
searchStyle,
searchConfiguration,
- datasourceConfiguration,
+ datasourceConfiguration
};
diff --git a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
index 68c59639d..423a6fe34 100644
--- a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
+++ b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
@@ -110,16 +110,16 @@ var toolbarMenu = {
label: "About",
icon: "",
action: {
- handlerAction: "clickAbout",
- parameters: []
+ handlerAction: "openNewTab",
+ parameters: ["https://www.virtualflybrain.org/about/"]
}
},
{
label: "Contribute",
icon: "",
action: {
- handlerAction: "clickContribute",
- parameters: []
+ handlerAction: "openNewTab",
+ parameters: ["http://www.virtualflybrain.org/docs/contribution-guidelines/"]
}
},
{
@@ -156,19 +156,11 @@ var toolbarMenu = {
}
},
{
- label: "Blog",
+ label: "Latest News & Releases",
icon: "",
action: {
handlerAction: "openNewTab",
- parameters: ["https://virtualflybrain.tumblr.com/"]
- }
- },
- {
- label: "Rss",
- icon: "fa fa-rss",
- action: {
- handlerAction: "openNewTab",
- parameters: ["http://blog.virtualflybrain.org/rss"]
+ parameters: ["https://www.virtualflybrain.org/about/blog"]
}
}
]
@@ -253,6 +245,14 @@ var toolbarMenu = {
parameters: ["circuitBrowserVisible"]
}
},
+ {
+ label: "Download Contents",
+ icon: "fa fa-download",
+ action: {
+ handlerAction: "downloadContentsVisible",
+ parameters: []
+ }
+ },
{
label: "NBLAST Uploader",
icon: "fa fa-upload",
@@ -299,7 +299,7 @@ var toolbarMenu = {
trailerIcon: "fa fa-external-link",
action: {
handlerAction: "openNewTab",
- parameters: ["http://catmaid.readthedocs.io/"]
+ parameters: ["https://www.virtualflybrain.org/blog/releases/catmaid/"]
}
},
{
@@ -760,11 +760,11 @@ var toolbarMenu = {
position: "bottom-start",
list: [
{
- label: "F.A.Q.",
+ label: "Documentation",
icon: "fa fa-comments-o",
action: {
handlerAction: "openNewTab",
- parameters: ["https://groups.google.com/g/vfb-suport"]
+ parameters: ["https://www.virtualflybrain.org/docs/"]
}
},
{
@@ -775,6 +775,14 @@ var toolbarMenu = {
parameters: ["https://groups.google.com/g/vfb-suport"]
}
},
+ {
+ label: "Contribute",
+ icon: "",
+ action: {
+ handlerAction: "openNewTab",
+ parameters: ["http://www.virtualflybrain.org/docs/contribution-guidelines/"]
+ }
+ },
{
label: "Circuit Browser Query (Shortest Weighted Path Algorithm)",
icon: "fa fa-connectdevelop",
diff --git a/components/configuration/VFBTree/VFBTreeConfiguration.js b/components/configuration/VFBTree/VFBTreeConfiguration.js
index c30f27754..abd9f9a69 100644
--- a/components/configuration/VFBTree/VFBTreeConfiguration.js
+++ b/components/configuration/VFBTree/VFBTreeConfiguration.js
@@ -8,7 +8,7 @@ var treeCypherQuery = instance => ({
{
"statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Template {short_form:'" + instance + "'})"
+ "<-[:depicts]-(tc:Template)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:"
- + "Individual)-[r:INSTANCEOF]->(anat:Class:Nervous_system) WHERE exists(ie.index) WITH root, anat,r,image"
+ + "Individual)-[r:INSTANCEOF]->(anat:Class:Anatomy) WHERE exists(ie.index) WITH root, anat,r,image"
+ " MATCH p=allshortestpaths((root)<-[:SUBCLASSOF|part_of*..]-(anat)) "
+ "UNWIND nodes(p) as n UNWIND nodes(p) as m WITH * WHERE id(n) < id(m) "
+ "MATCH path = allShortestPaths( (n)-[:SUBCLASSOF|part_of*..1]-(m) ) "
diff --git a/components/configuration/VFBUploader/configuration.json b/components/configuration/VFBUploader/configuration.json
index a6d1a1091..45126dc53 100644
--- a/components/configuration/VFBUploader/configuration.json
+++ b/components/configuration/VFBUploader/configuration.json
@@ -1,12 +1,27 @@
{
- "nblastURL" : "https://zip.virtualflybrain.org/download",
- "contentType" : "",
- "templates" : [
- { "VFB_00101567" : "VFB_00101567 Template" },
- { "VFB_00000001" : "VFB_00000001 Template" }
- ],
- "acceptedFiles" : [".swc"],
- "filesLimit" : 10,
+ "nblastURL" : "http://upload.virtualflybrain.org/files/UNIQUE_ID?token=bec3a40f0ab377c39103",
+ "contentType" : "multipart/form-data",
+ "templates" : [{"short_form":"VFB_00101567","label":"JRC2018Unisex"},{"short_form":"VFB_00200000","label":"JRC2018UnisexVNC"}] ,
+ "acceptedFiles" : [".swc", ".nrrd"],
+ "filesLimit" : 1,
"maxFileSize" : 3000000,
- "dropZoneMessage" : "Drag and drop a SWC file here or click"
+ "queryType": "uploaderQuery",
+ "cookieStorageDays" : 100,
+ "cookiesLearnLink" : "https://en.wikipedia.org/wiki/HTTP_cookie",
+ "text" : {
+ "dialogTitle" : "Upload Data File",
+ "dialogSubtitle" : "Generate a nblast query from your own data.",
+ "selectTemplate" : "1. Select a Template",
+ "select" : "Select",
+ "addYourFile" : "2. Add your file (Please use a .swc)",
+ "dropZoneMessage" : "Click or Drag & Drop your file here",
+ "agreeTerms" : "I agree to the use of cookies to store the NBLAST QUERY URL. ",
+ "learnMore" : "Learn More",
+ "blastButtonText" : "Generate a nblast query link",
+ "restartButtonText" : "Restart",
+ "copyButtonText" : "Copy",
+ "infoMessage" : "Generating a nblast query can take up to 30 min. Save this link to find the result of your query.",
+ "errorDialog" : "Something went wrong... We were not able to generate a nblast query link from your data. Please try again.",
+ "errorButtonText" : "Try Again"
+ }
}
\ No newline at end of file
diff --git a/components/configuration/VFBUploader/file-icon.png b/components/configuration/VFBUploader/file-icon.png
new file mode 100644
index 000000000..7ac1b6ff8
Binary files /dev/null and b/components/configuration/VFBUploader/file-icon.png differ
diff --git a/components/configuration/VFBUploader/upload-icon.png b/components/configuration/VFBUploader/upload-icon.png
new file mode 100644
index 000000000..d10bcbbb2
Binary files /dev/null and b/components/configuration/VFBUploader/upload-icon.png differ
diff --git a/components/interface/ErrorCatcher.js b/components/interface/ErrorCatcher.js
index 63f6fcf64..181793148 100644
--- a/components/interface/ErrorCatcher.js
+++ b/components/interface/ErrorCatcher.js
@@ -38,22 +38,44 @@ const styles = {
class ErrorCatcher extends React.Component {
constructor (props) {
super(props);
- this.state = {
- hasError: false,
+ this.state = {
+ hasError: false,
open: true,
error: undefined
};
}
-
handleClose = () => {
var url = "https://github.com/VirtualFlyBrain/VFB2/issues/new";
var customMessage = "Steps to reproduce the problem: \n\nPlease fill the below with the necessary steps to reproduce the problem\n\n\n\nError Information:\n\n"
- var body = customMessage + this.state.error.message + "\n\n" + this.state.error.stack.replace("#",escape("#")) + "\n\n```diff\n" + window.console.logs.slice(-50).join('\n').replace("#",escape("#")) + "\n```\n";
+ // return as much of the log up to the last 10 events < 1000 characters:
+ var logLength = -1;
+ var limitedLog = window.console.logs.slice(logLength).join('%0A').replace(
+ /\&/g,escape('&')
+ ).replace(
+ /\#/g,escape('#')
+ ).replace(
+ /\-/g,'%2D'
+ ).replace(
+ /\+/g,'%2B'
+ );
+ while (limitedLog.length < 1000 && logLength > -50) {
+ logLength -= 1;
+ limitedLog = window.console.logs.slice(logLength).join('%0A').replace(
+ /\&/g,escape('&')
+ ).replace(
+ /\#/g,escape('#')
+ ).replace(
+ /\-/g,'%2D'
+ ).replace(
+ /\+/g,'%2B'
+ );
+ }
+ var body = customMessage + this.state.error.message + "\n\n" + this.state.error.stack.replace("#",escape("#")) + "\n\n```diff\n" + limitedLog + "\n```\n";
var form = document.createElement("form");
form.setAttribute("method", "get");
form.setAttribute("action", url);
form.setAttribute("target", "view");
- var hiddenField = document.createElement("input");
+ var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "body");
hiddenField.setAttribute("value", body);
@@ -62,7 +84,7 @@ class ErrorCatcher extends React.Component {
window.open('', 'view');
form.submit();
};
-
+
componentDidCatch (error, info) {
// Report error to GA
window.ga('vfb.send', 'event', 'error', 'react', error.message + " - " + error.stack.replace("#",escape("#")));
diff --git a/components/interface/VFBCircuitBrowser/Controls.js b/components/interface/VFBCircuitBrowser/Controls.js
index a6c6e1838..32eb45cc9 100644
--- a/components/interface/VFBCircuitBrowser/Controls.js
+++ b/components/interface/VFBCircuitBrowser/Controls.js
@@ -37,23 +37,10 @@ const theme = createMuiTheme({
overrides : {
MuiSlider: {
markLabelActive: { color: 'white' },
- markLabel: { color: 'white' },
- markActive: { color: 'red' }
- },
- MuiButton: {
- label: {
- fontSize: '12px',
- fontFamily: ['Barlow Condensed', 'Khand', "sans-serif"]
- },
- button : { padding : "" }
+ markLabel: { color: 'white' }
}
},
- typography: {
- body1: {
- fontSize: 15,
- fontFamily : ['Barlow Condensed', 'Khand', "sans-serif"]
- }
- }
+ typography: { body1: { fontFamily : ['Barlow Condensed', 'Khand', "sans-serif"] } }
});
/**
@@ -97,20 +84,26 @@ const styles = theme => ({
height : "20px",
border : "none !important",
backgroundColor: "#80808040 !important",
- paddingLeft : "10px !important"
+ paddingLeft : "10px !important",
+ fontSize : "15px !important"
},
weightInputDiv : { width : "100% !important" },
refreshButton : {
backgroundColor : "#0AB7FE",
flexBasis: "100%",
fontWeight : 600,
+ fontSize: '12px',
+ fontFamily: ['Barlow Condensed', 'Khand', "sans-serif"]
},
clearButton : {
backgroundColor : "#E53935",
flexBasis: "100%",
fontWeight : 600,
+ fontSize: '12px',
+ fontFamily: ['Barlow Condensed', 'Khand', "sans-serif"]
},
- slider : { color: '#0AB7FE' }
+ slider : { color: '#0AB7FE' },
+ typography : { fontSize : "15px" }
});
/**
@@ -120,9 +113,11 @@ const configuration = require('../../configuration/VFBCircuitBrowser/circuitBrow
const restPostConfig = require('../../configuration/VFBCircuitBrowser/circuitBrowserConfiguration').restPostConfig;
const cypherQuery = require('../../configuration/VFBCircuitBrowser/circuitBrowserConfiguration').locationCypherQuery;
const stylingConfiguration = require('../../configuration/VFBCircuitBrowser/circuitBrowserConfiguration').styling;
+const Neo4jLabels = require('../../configuration/VFBCircuitBrowser/circuitBrowserConfiguration').Neo4jLabels;
const searchConfiguration = require('./../../configuration/VFBCircuitBrowser/datasources/SOLRclient').searchConfiguration;
-const datasourceConfiguration = require('./../../configuration/VFBCircuitBrowser/datasources/SOLRclient').datasourceConfiguration;
+const defaultDatasourceConfiguration = require('./../../configuration/VFBCircuitBrowser/datasources/SOLRclient').datasourceConfiguration;
+const datasourceConfiguration = JSON.parse(JSON.stringify(defaultDatasourceConfiguration));
/**
* Create custom marks for Paths slider.
@@ -161,11 +156,15 @@ class AutocompleteResults extends Component {
this.setState({ filteredResults : results });
}
+ clearResults () {
+ this.setState({ filteredResults : {} });
+ }
+
getFilteredResults (){
return this.state.filteredResults;
}
- shouldComponentUpdate(nextProps, nextState) {
+ shouldComponentUpdate (nextProps, nextState) {
this.fieldLabel = nextProps.getLatestNeuronFields()[this.props.index].label;
return true;
}
@@ -179,11 +178,13 @@ class AutocompleteResults extends Component {
fullWidth
freeSolo
disableClearable
+ clearOnEscape
disablePortal
autoHighlight
+ clearOnBlur
value={this.fieldLabel}
id={this.props.index.toString()}
- ListboxProps={{ style: { maxHeight: "10rem" } }}
+ ListboxProps={{ style: { maxHeight: "10rem", fontSize: "15px" } }}
onChange={this.props.resultSelectedChanged}
options={Object.keys(this.state.filteredResults).map(option => this.state.filteredResults[option].label)}
renderInput={params => (
@@ -194,8 +195,8 @@ class AutocompleteResults extends Component {
className={label.replace(/ +/g, "").toLowerCase()}
onChange={this.props.neuronTextfieldModified}
onDelete={this.props.neuronTextfieldModified}
- inputProps={{ ...params.inputProps, id: this.props.index, style: { height : "20px", color: "white" ,paddingLeft : "10px", border : "none", backgroundColor: "#80808040" } }}
- InputLabelProps={{ ...params.inputProps,style: { color: "white", paddingLeft : "10px" } }}
+ inputProps={{ ...params.inputProps, id: this.props.index, style: { height : "20px", color: "white" ,paddingLeft : "10px", fontSize: "15px", border : "none", backgroundColor: "#80808040" } }}
+ InputLabelProps={{ ...params.inputProps,style: { color: "white", paddingLeft : "10px", fontSize: "15px" } }}
/>
)}
/>
@@ -267,6 +268,11 @@ class Controls extends Component {
this.props.vfbCircuitBrowser(UPDATE_CIRCUIT_QUERY, neurons);
delete this.autocompleteRef[id.toString()];
this.neuronFields = neurons;
+ if ( !this.state.neurons.find( neuron => neuron.id != "") ) {
+ // reset configuration of fq to default
+ datasourceConfiguration.query_settings.fq = defaultDatasourceConfiguration.query_settings.fq;
+ }
+
this.forceUpdate();
}
@@ -292,6 +298,7 @@ class Controls extends Component {
// User has added the maximum number of neurons allowed in query search
this.neuronFields = neuronFields;
this.autocompleteRef[(neuronFields.length - 1).toString()] = React.createRef();
+ datasourceConfiguration.query_settings.fq = defaultDatasourceConfiguration.query_settings.fq;
this.forceUpdate();
}
@@ -360,6 +367,12 @@ class Controls extends Component {
getResultsSOLR( event.target.value, this.autocompleteRef[this.setInputValue].current.handleResults,searchConfiguration.sorter,datasourceConfiguration );
}
this.neuronFields = neurons;
+
+ if ( !this.neuronFields.find( neuron => neuron.id != "") ) {
+ // reset configuration of fq to default
+ this.autocompleteRef[this.setInputValue].current.clearResults();
+ datasourceConfiguration.query_settings.fq = defaultDatasourceConfiguration.query_settings.fq;
+ }
}
/**
@@ -369,9 +382,17 @@ class Controls extends Component {
// Copy neurons and add selection to correct array index
let neurons = this.neuronFields;
let textFieldId = event.target.id.toString().split("-")[0];
- let shortForm = this.autocompleteRef[textFieldId].current.getFilteredResults()[value] && this.autocompleteRef[textFieldId].current.getFilteredResults()[value].short_form;
+ let result = this.autocompleteRef[textFieldId].current.getFilteredResults()[value];
+ let shortForm = result && result.short_form;
neurons[index] = { id : shortForm, label : value };
+ result.facets_annotation.forEach( annotation => {
+ let facet = "facets_annotation:" + annotation;
+ if ( Object.values(Neo4jLabels).includes(annotation) && !datasourceConfiguration.query_settings.fq.includes(facet) ) {
+ datasourceConfiguration.query_settings.fq.push(facet);
+ }
+ });
+
// Keep track of query selected, and send an event to redux store that circuit has been updated
this.circuitQuerySelected = neurons;
this.props.vfbCircuitBrowser(UPDATE_CIRCUIT_QUERY, neurons);
@@ -401,6 +422,12 @@ class Controls extends Component {
this.props.vfbCircuitBrowser(UPDATE_CIRCUIT_QUERY, [])
this.setState({ key: Math.random() });
}
+
+ clearGraph () {
+ datasourceConfiguration.query_settings.fq = defaultDatasourceConfiguration.query_settings.fq;
+ this.props.clearGraph()
+ }
+
/**
* Update neuron fields if there's a query preselected.
*/
@@ -479,7 +506,7 @@ class Controls extends Component {
IconButtonProps={{ style: { padding : "0px", margin : "0px" } }}
>
- Configure circuit
+ Configure circuit
@@ -545,7 +572,7 @@ class Controls extends Component {
- Add Neuron
+ Add Neuron
}
@@ -556,7 +583,7 @@ class Controls extends Component {
- # Paths
+ # Paths
- Min Weight
+ Min Weight
@@ -594,7 +621,7 @@ class Controls extends Component {
color="secondary"
classes={{ root : classes.clearButton }}
id="clearCircuitBrowser"
- onClick={() => this.props.clearGraph()}
+ onClick={this.clearGraph.bind(this)}
>Clear
diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js
index 6e2df5988..ff13c9310 100644
--- a/components/interface/VFBCircuitBrowser/QueryParser.js
+++ b/components/interface/VFBCircuitBrowser/QueryParser.js
@@ -21,6 +21,7 @@ export function queryParser (e) {
let maxHops = Math.ceil(data[0]?.row[6] / 2) + 1;
// Read relationship max hops
let relationshipsHops = data[0]?.row[7];
+ const idClassLabels = data[0]?.row[8];
// Keeps track of links
let linksMap = new Map();
@@ -31,7 +32,7 @@ export function queryParser (e) {
let nodesMap = new Map();
// Colors for labels
- let presentColorLabels = new Array();
+ let legendLabels = new Array();
// maps of links with their max hop
let linksMaxHops = {};
// Keeps track of what level nodes belong
@@ -60,24 +61,27 @@ export function queryParser (e) {
// Loop through nodes from query and create nodes for graph
data.forEach(({ graph }) => {
- graph.nodes.forEach(({ id, labels, properties }) => {
+ console.log("Results ", graph.nodes);
+ graph.nodes.forEach(({ id, properties }) => {
let label = properties[e.data.params.configuration.resultsMapping.node.label];
let title = properties[e.data.params.configuration.resultsMapping.node.title];
let color = e.data.params.styling.defaultNodeDescriptionBackgroundColor;
+ let nodeColorLabels = new Array();
+ const labels = properties.uniqueFacets;
// Retrieve list of Label colors from configuration
const colorLabels = Object.entries(e.data.params.styling.nodeColorsByLabel);
-
+ console.log("Labels ", labels);
+ console.log("Color labels, ", colorLabels);
// Loop through color labels
for (var i = 0; i < colorLabels.length ; i++ ) {
let index = labels.indexOf(colorLabels[i][0]);
if ( index > -1 ) {
- color = colorLabels[i][1];
+ nodeColorLabels.push(colorLabels[i][1]);
// Add to array of present colors only if array doesn't have it already
- if ( !presentColorLabels.includes(labels[index]) ) {
- presentColorLabels.push(labels[index]);
+ if ( !legendLabels.includes(labels[index]) ) {
+ legendLabels.push(labels[index]);
}
- break;
}
}
let n = null;
@@ -96,15 +100,20 @@ export function queryParser (e) {
hop = 0;
}
+ const classLabel = idClassLabels[title];
+ console.log("nodeColorLabels ", nodeColorLabels);
+ console.log(" className ", Object.values(classLabel).join());
+
n = {
- path : label,
+ name : label,
id : parseID,
title : title,
level : level,
hop : hop,
width : e.data.params.NODE_WIDTH,
height : e.data.params.NODE_HEIGHT,
- color : color,
+ nodeColorLabels : nodeColorLabels,
+ classLabel : Object.values(classLabel).join()
};
nodesMap.set(id, n);
@@ -143,7 +152,7 @@ export function queryParser (e) {
// Set the X position of each node, this will place them on their corresponding column depending on hops
let positionX = 0;
- let spaceBetween = maxHops > 2 ? 100 : 200;
+ let spaceBetween = maxHops > 2 ? 300 : 400;
if ( sourceNode.level === 0 ){
if ( sourceNode.id == targetNodeID ){
sourceNode.positionX = maxHops > 0 ? maxHops * spaceBetween : spaceBetween;
@@ -244,5 +253,5 @@ export function queryParser (e) {
console.log("Links ", links);
// Worker is done, notify main thread
- this.postMessage({ resultMessage: "OK", params: { results: { nodes, links }, colorLabels : presentColorLabels } });
+ this.postMessage({ resultMessage: "OK", params: { results: { nodes, links }, colorLabels : legendLabels } });
}
\ No newline at end of file
diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js
index 04bd523ba..b13233754 100644
--- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js
+++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js
@@ -39,8 +39,8 @@ const defaultHTTPConfiguration = {
}
const COMPONENT_ID = "VFBCircuitBrowser";
-const NODE_WIDTH = 55;
-const NODE_HEIGHT = 40;
+const NODE_WIDTH = 120;
+const NODE_HEIGHT = 80;
const NODE_BORDER_THICKNESS = 2;
/**
@@ -72,6 +72,7 @@ class VFBCircuitBrowser extends Component {
this.updatePaths = this.updatePaths.bind(this);
this.updateWeight = this.updateWeight.bind(this);
this.resize = this.resize.bind(this);
+ this.nodeRendering = this.nodeRendering.bind(this);
this.highlightNodes = new Set();
this.highlightLinks = new Set();
@@ -261,27 +262,47 @@ class VFBCircuitBrowser extends Component {
self.setState( { loading : false } );
})
}
+
+ getFontSize (context, maxWidth, text, textLength) {
+ let baseSize = 8;
+ let width = context.measureText(text).width;
+ while (width > maxWidth) {
+ baseSize--;
+ context.font = `${baseSize}px sans-serif`
+ width = context.measureText(text).width;
+ }
+
+ return baseSize;
+ }
/**
* Breaks Description texts into lines to fit within a certain width value.
*/
- wrapText (context, text, x, y, maxWidth, lineHeight) {
- var words = text.split(' ');
- var line = '';
-
- for (var n = 0; n < words.length; n++) {
- var testLine = line + words[n] + ' ';
- var metrics = context.measureText(testLine);
- var testWidth = metrics.width;
- if (testWidth > maxWidth && n > 0) {
- context.fillText(line, x, y);
- line = words[n] + ' ';
- y += lineHeight;
+ wrapText (context, text, x, y, maxWidth, maxHeight) {
+ let words = text.split(' ');
+ let lines = [];
+ let line = '';
+
+ let maxTextLength = text.length < 20 ? text.length / 2 : text.length / 3;
+
+ words.forEach( word => {
+ let testLine = line + word + ' ';
+ if ( line.length >= maxTextLength ) {
+ lines.push(line);
+ line = word + ' ';
} else {
line = testLine;
}
- }
- context.fillText(line, x, y);
+ });
+
+ lines.push(line);
+
+ const lineHeight = this.getFontSize(context, maxWidth, lines[0], maxTextLength);
+
+ lines.forEach( line => {
+ context.fillText(line, x, y);
+ y += lineHeight;
+ });
}
// Calculate link middle point
@@ -291,7 +312,58 @@ class VFBCircuitBrowser extends Component {
y: (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cp1y + t * t * ey,
};
}
+
+ nodeRendering (node, ctx, globalScale) {
+ const cardWidth = NODE_WIDTH;
+ const cardHeight = NODE_HEIGHT;
+ const classnameHeight = cardHeight * .45;
+ const idHeight = cardHeight * .45;
+ let borderThickness = this.highlightNodes.has(node) ? NODE_BORDER_THICKNESS : 1;
+
+ // Node border color
+ ctx.fillStyle = self.hoverNode == node ? stylingConfiguration.defaultNodeHoverBoderColor : (this.highlightNodes.has(node) ? stylingConfiguration.defaultNeighborNodesHoverColor : stylingConfiguration.defaultNodeBorderColor) ;
+ // Create Border
+ ctx.fillRect(node.x - cardWidth / 2 - (borderThickness), node.y - cardHeight / 2 - (borderThickness), cardWidth , cardHeight );
+
+ // Assign color to Description Area background in Node
+ ctx.fillStyle = stylingConfiguration.defaultNodeDescriptionBackgroundColor;
+ // Create Description Area in Node
+ ctx.fillRect(node.x - cardWidth / 2,node.y - cardHeight / 2, cardWidth - (borderThickness * 2 ), cardHeight - (borderThickness * 2 ));
+
+ ctx.fillStyle = stylingConfiguration.defaultNodeTitleBackgroundColor;
+ ctx.fillRect(node.x - cardWidth / 2,node.y - cardHeight / 2, cardWidth - (borderThickness * 2 ), cardHeight / 2 );
+
+ const lastIndex = node.nodeColorLabels.length;
+ node.nodeColorLabels.forEach( (color, index) => {
+ // Assign color to Title Bar background in Node
+ ctx.fillStyle = color;
+ const x = (node.x - cardWidth / 2) + (index * (cardWidth / lastIndex));
+ const y = node.y;
+ // Create Title Bar in Node
+ ctx.fillRect(x,y, (cardWidth / lastIndex) - ( index == lastIndex - 1 ? borderThickness * 2 : 0 ) , cardHeight / 10);
+ })
+ // Assign font to text in Node
+ ctx.font = stylingConfiguration.defaultNodeFont;
+ // Assign color to text in Node
+ ctx.fillStyle = stylingConfiguration.defaultNodeFontColor;
+ // Text in font to be centered
+ ctx.textAlign = "center";
+ ctx.textBaseline = 'middle';
+
+ // Create Title in Node
+ this.wrapText(ctx, node.classLabel, node.x, node.y - (cardHeight / 2) + 10, cardWidth * .8 , classnameHeight);
+
+ ctx.font = stylingConfiguration.defaultNodeFont;
+ /*
+ * Add Description text to Nodes
+ * node.name = text to display
+ * node.x = x coordinate of text
+ * node.y + 20 = y coordinate, adds 20 pixels for padding from upper element
+ */
+ this.wrapText(ctx, node.name, node.x, node.y + 20, cardWidth * .8 , classnameHeight);
+ }
+
render () {
let self = this;
@@ -336,7 +408,7 @@ class VFBCircuitBrowser extends Component {
data={this.state.graph}
// Create the Graph as 2 Dimensional
d2={true}
- nodeLabel={node => node.path}
+ nodeLabel={node => node.title}
// Relationship label, placed in Link
linkLabel={link => link.label}
// Width of links, log(weight)
@@ -347,7 +419,7 @@ class VFBCircuitBrowser extends Component {
// Node label, used in tooltip when hovering over Node
linkCanvasObjectMode={() => "after"}
linkCanvasObject={(link, ctx) => {
- const MAX_FONT_SIZE = 8;
+ const MAX_FONT_SIZE = 16;
const LABEL_NODE_MARGIN = 1 * 1.5;
const start = link.source;
@@ -419,46 +491,16 @@ class VFBCircuitBrowser extends Component {
return color;
}}
- nodeCanvasObject={(node, ctx, globalScale) => {
- let cardWidth = NODE_WIDTH;
- let cardHeight = NODE_HEIGHT;
- let borderThickness = self.highlightNodes.has(node) ? NODE_BORDER_THICKNESS : 1;
-
- // Node border color
- ctx.fillStyle = self.hoverNode == node ? stylingConfiguration.defaultNodeHoverBoderColor : (self.highlightNodes.has(node) ? stylingConfiguration.defaultNeighborNodesHoverColor : stylingConfiguration.defaultNodeBorderColor) ;
- // Create Border
- ctx.fillRect(node.x - cardWidth / 2 - (borderThickness), node.y - cardHeight / 2 - (borderThickness), cardWidth , cardHeight );
-
- // Assign color to Description Area background in Node
- ctx.fillStyle = stylingConfiguration.defaultNodeDescriptionBackgroundColor;
- // Create Description Area in Node
- ctx.fillRect(node.x - cardWidth / 2,node.y - cardHeight / 2, cardWidth - (borderThickness * 2 ), cardHeight - ( borderThickness * 2) );
- // Assign color to Title Bar background in Node
- ctx.fillStyle = node.color;
- // Create Title Bar in Node
- ctx.fillRect(node.x - cardWidth / 2 ,node.y - cardHeight / 2, cardWidth - ( borderThickness * 2 ), cardHeight / 3);
-
- // Assign font to text in Node
- ctx.font = stylingConfiguration.defaultNodeFont;
- // Assign color to text in Node
- ctx.fillStyle = stylingConfiguration.defaultNodeFontColor;
- // Text in font to be centered
- ctx.textAlign = "center";
- ctx.textBaseline = 'middle';
- // Create Title in Node
- ctx.fillText(node.title, node.x, node.y - 15);
- // Add Description text to Node
- this.wrapText(ctx, node.path, node.x, node.y, cardWidth - (borderThickness * 2) , 5);
- }}
+ nodeCanvasObject={this.nodeRendering}
// Overwrite Node Canvas Object
nodeCanvasObjectMode={node => 'replace'}
// bu = Bottom Up, creates Graph with root at bottom
dagMode="lr"
nodeVal = { node => {
node.fx = node.positionX;
- node.fy = -100 * node.level
+ node.fy = -150 * node.level
}}
- dagLevelDistance = {25}
+ dagLevelDistance = {75}
onDagError={loopNodeIds => {}}
// Handles clicking event on an individual node
onNodeClick = { (node,event) => this.handleNodeLeftClick(node,event) }
diff --git a/components/interface/VFBDownloadContents/VFBDownloadContents.js b/components/interface/VFBDownloadContents/VFBDownloadContents.js
new file mode 100644
index 000000000..6b86cf888
--- /dev/null
+++ b/components/interface/VFBDownloadContents/VFBDownloadContents.js
@@ -0,0 +1,571 @@
+import React from "react";
+import Button from "@material-ui/core/Button";
+import Grid from "@material-ui/core/Grid";
+import Dialog from "@material-ui/core/Dialog";
+import DialogActions from "@material-ui/core/DialogActions";
+import DialogContent from "@material-ui/core/DialogContent";
+import DialogTitle from "@material-ui/core/DialogTitle";
+import Tooltip from "@material-ui/core/Tooltip";
+import Typography from "@material-ui/core/Typography";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import CircularProgress from '@material-ui/core/CircularProgress';
+import ChevronRightIcon from "@material-ui/icons/ChevronRight";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import { Checkbox, Divider, IconButton } from "@material-ui/core";
+import Box from "@material-ui/core/Box";
+import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
+import { withStyles } from "@material-ui/styles";
+import axios from "axios";
+import TreeView from "@material-ui/lab/TreeView";
+import TreeItem from "@material-ui/lab/TreeItem";
+import NRRDIcon from "../../configuration/VFBDownloadContents/nrrd.png";
+import OBJIcon from "../../configuration/VFBDownloadContents/obj.png";
+import SWCIcon from "../../configuration/VFBDownloadContents/swc.png";
+import ReferenceIcon from "../../configuration/VFBDownloadContents/reference.png";
+import CloseIcon from "@material-ui/icons/Close";
+import { connect } from "react-redux";
+
+const iconsMap = {
+ obj: OBJIcon,
+ swc: SWCIcon,
+ reference: ReferenceIcon,
+ nrrd: NRRDIcon,
+};
+
+const ALL_INSTANCES = { id: "ALL_INSTANCES", name: "All Instances" };
+
+const styles = theme => ({
+ downloadButton: { backgroundColor: "#0AB7FE", color: "white !important" },
+ downloadErrorButton: { backgroundColor: "#FCE7E7", color: "#E53935", border : "1px solid #E53935" },
+ error: { color: "#E53935" },
+ errorMessage: { wordWrap: "break-word" },
+ downloadButtonText: { color: "white !important" },
+ checkedBox: { borderColor: "#0AB7FE" },
+ footer: { backgroundColor: "#EEF9FF" },
+ errorFooter: { backgroundColor: "#FCE7E7" },
+ listItemText: { fontSize: "1em" },
+ customizedButton: {
+ position: "absolute",
+ left: "95%",
+ top: "2%",
+ backgroundColor: "#F5F5F5",
+ color: "gray",
+ },
+ dialog: {
+ overflow: "unset",
+ margin: "0 auto",
+ },
+ dialogContent: { overflow: "hidden" },
+ checked: { "&$checked": { color: "#0AB7FE" } },
+ "@global": {
+ ".MuiTreeItem-root.Mui-selected > .MuiTreeItem-content .MuiTreeItem-label": { backgroundColor: "white" },
+ ".MuiTreeItem-root.Mui-selected > .MuiTreeItem-content .MuiTreeItem-label:hover, .MuiTreeItem-root.Mui-selected:focus > .MuiTreeItem-content .MuiTreeItem-label": { backgroundColor: "white" }
+ },
+});
+
+const theme = createMuiTheme({
+ typography: {
+ h2: {
+ fontSize: 22,
+ fontWeight: 400,
+ fontStyle: "normal",
+ lineHeight: "26.4px",
+ color: "#181818",
+ fontFamily: "Barlow",
+ },
+ h5: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "rgba(0, 0, 0, 0.54)",
+ },
+ subtitle2: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "rgba(0, 0, 0, 0.24)",
+ },
+ error: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "#E53935",
+ },
+ button: {
+ fontSize: 11,
+ fontWeight: 600,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "#0AB7FE",
+ },
+ },
+ Button: {
+ "&:hover": {
+ backgroundColor: "#0AB7FE",
+ boxShadow: "none",
+ },
+ "&:active": {
+ boxShadow: "none",
+ backgroundColor: "#0AB7FE",
+ },
+ },
+});
+
+/**
+ * Component to download files contents
+ */
+class VFBDownloadContents extends React.Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ open: false,
+ typesChecked: [],
+ downloadError: false,
+ downloading: false,
+ selectedVariables: [],
+ allVariablesSelectedFlag: false,
+ errorMessage : ""
+ };
+
+ this.configuration = require("../../configuration/VFBDownloadContents/configuration");
+ this.configurationOptions = this.configuration.options;
+ this.handleCloseDialog = this.handleCloseDialog.bind(this);
+ this.openDialog = this.openDialog.bind(this);
+ this.handleTypeSelection = this.handleTypeSelection.bind(this);
+ this.handleDownload = this.handleDownload.bind(this);
+ this.extractVariableFileMeta = this.extractVariableFileMeta.bind(this);
+ this.getAllLoadedVariables = this.getAllLoadedVariables.bind(this);
+ this.requestZipDownload = this.requestZipDownload.bind(this);
+ this.getVariableById = this.getVariableById.bind(this);
+ this.toggleVariable = this.toggleVariable.bind(this);
+ this.variables = [ALL_INSTANCES];
+ }
+
+ handleCloseDialog () {
+ this.setState({ open: false });
+ }
+
+ openDialog () {
+ this.variables = this.getAllLoadedVariables();
+ this.setState({
+ open: true,
+ downloadError : false,
+ downloading : false,
+ downloadEnabled : this.state.typesChecked.length > 0 && this.state.selectedVariables.length > 0
+ });
+ }
+
+ handleDownload () {
+ if ( this.state.downloading ) {
+ return;
+ }
+
+ let json = { entries: [] };
+
+ this.state.selectedVariables.map( variable => {
+ const filemeta = this.extractVariableFileMeta(variable);
+ json.entries = json.entries.concat(filemeta);
+ });
+
+ json.entries.length > 0 ? this.requestZipDownload(json) : this.setState({ downloadError : true, errorMessage : this.configuration.text.noEntriesFound });
+ }
+
+ /**
+ * Extract filemeta from geppetto model, using variable id to find it
+ */
+ extractVariableFileMeta (variable) {
+ let filemetaText = variable.filemeta?.values[0]?.value?.text;
+ filemetaText = filemetaText?.replace(/'/g, '"');
+
+ const filemetaObject = JSON.parse(filemetaText);
+ let filesArray = [];
+
+ this.state.typesChecked.map( check => {
+ filemetaObject[check]
+ && filesArray.push({
+ Url: filemetaObject[check]?.url,
+ ZipPath: filemetaObject[check]?.local,
+ });
+ });
+
+ return filesArray;
+ }
+
+ /**
+ * Get array of all loaded variables in application
+ */
+ getAllLoadedVariables () {
+ let entities = GEPPETTO.ModelFactory.allPaths;
+ var visuals = [];
+
+ for (var i = 0; i < entities.length; i++) {
+ if ( entities[i].metaType === "VisualType" || entities[i].metaType === "CompositeVisualType" ) {
+ const variable = entities[i]?.path?.split(".")[0];
+ const instance = window.Instances[variable];
+ const filemeta = instance[variable + "_meta"]?.variable?.types[0]?.filemeta;
+ visuals.push({ id: variable, name: instance?.name, filemeta: filemeta });
+ }
+ }
+
+ return visuals;
+ }
+
+ /**
+ * Make axios call to download the zip
+ */
+ requestZipDownload (jsonRequest) {
+ let self = this;
+
+ this.setState({ downloading: true, downloadEnabled : false });
+ // Axios HTTP Post request with post query
+ axios({
+ method: "post",
+ url: this.configuration.postURL,
+ headers: { "content-type": this.configuration.contentType },
+ data: jsonRequest,
+ responseType: "arraybuffer",
+ })
+ .then(function (response) {
+ const url = window.URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", self.configuration.zipName);
+ document.body.appendChild(link);
+ link.click();
+ setTimeout(
+ () =>
+ self.setState({
+ downloading: false,
+ open: false,
+ downloadEnabled : true
+ }),
+ 500
+ );
+ })
+ .catch(function (error) {
+ self.setState({
+ downloadError: true,
+ downloading: false,
+ errorMessage : this.props.classes.errorMessage
+ });
+ });
+ }
+
+ /**
+ * Handle checkbox selection of different types to download
+ */
+ handleTypeSelection (value) {
+ const currentIndex = this.state.typesChecked.indexOf(value);
+ const newTypesChecked = [...this.state.typesChecked];
+
+ if (currentIndex === -1) {
+ newTypesChecked.push(value);
+ } else {
+ newTypesChecked.splice(currentIndex, 1);
+ }
+
+ this.setState({ typesChecked: newTypesChecked, downloadEnabled : newTypesChecked.length > 0 && this.state.selectedVariables.length > 0 });
+ }
+
+ /**
+ * Get variable by id, trigger by checkbox selection of variables
+ */
+ getVariableById (nodes, id) {
+ let variablesMatched = [];
+
+ if (id === ALL_INSTANCES.id) {
+ variablesMatched = nodes;
+ } else {
+ nodes.forEach(node => {
+ if (node.id === id) {
+ variablesMatched.push(node);
+ }
+ });
+ }
+
+ return variablesMatched;
+ }
+
+ /**
+ * Toggle variable selection from checklist
+ */
+ toggleVariable (checked, node) {
+ const allNode = this.getVariableById(this.variables, node.id);
+ let updatedVariables = checked
+ ? [...this.state.selectedVariables, ...allNode]
+ : this.state.selectedVariables.filter(
+ value => !allNode.find( node => node.id === value.id )
+ );
+
+ updatedVariables = updatedVariables.filter((v, i) => updatedVariables.indexOf(v) === i);
+
+ this.setState({
+ selectedVariables: updatedVariables,
+ allVariablesSelectedFlag: updatedVariables.length > 0,
+ downloadEnabled : this.state.typesChecked.length > 0 && updatedVariables.length > 0
+ });
+ }
+
+ render () {
+ let self = this;
+ const { idsMap } = this.props;
+ this.variables = this.getAllLoadedVariables();
+
+ return (
+
+
+
+ );
+ }
+}
+
+function mapStateToProps (state) {
+ return {
+ instanceDeleted : state.generals.ui.canvas.instanceDeleted,
+ instanceOnFocus : state.generals.instanceOnFocus,
+ idsMap : state.generals.idsMap,
+ idsList : state.generals.idsList
+ }
+}
+
+export default connect(mapStateToProps, null, null, { forwardRef : true } )(withStyles(styles)(VFBDownloadContents));
\ No newline at end of file
diff --git a/components/interface/VFBFocusTerm/VFBFocusTerm.js b/components/interface/VFBFocusTerm/VFBFocusTerm.js
index 482347f9b..28d41946b 100644
--- a/components/interface/VFBFocusTerm/VFBFocusTerm.js
+++ b/components/interface/VFBFocusTerm/VFBFocusTerm.js
@@ -568,6 +568,20 @@ class VFBFocusTerm extends React.Component {
:
}
+
+ {
+ this.props.UIUpdateManager("uploaderContentsVisible");
+ }} />
+
+
+ {
+ this.props.UIUpdateManager("downloadContentsVisible");
+ }} />
+
{
+ stylingConfiguration?.dropDownQueries?.map((item, index) => {
if ( parseInt(self.props.graphQueryIndex) === index ) {
self.props.vfbGraph(UPDATE_GRAPH, null, -1, true, false);
self.instanceFocusChange(self.props.instanceOnFocus);
@@ -314,7 +316,7 @@ class VFBGraph extends Component {
selectedNodeLoaded (instance) {
var loadedId = instance.id;
if (instance.getParent() !== null) {
- loadedId = instance.getParent().id;
+ loadedId = instance.getParent()?.id;
}
if ( this.nodeSselected ) {
@@ -347,9 +349,9 @@ class VFBGraph extends Component {
* 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.
*/
- if (this.focusedInstance.getParent() !== null) {
- idToSearch = this.focusedInstance.getParent().id;
- instanceName = this.focusedInstance.getParent().name;
+ if (this.focusedInstance?.getParent() !== null) {
+ idToSearch = this.focusedInstance?.getParent()?.id;
+ instanceName = this.focusedInstance?.getParent()?.name;
}
if (this.__isMounted){
diff --git a/components/interface/VFBTermInfo/ButtonBarComponent.js b/components/interface/VFBTermInfo/ButtonBarComponent.js
index eec3845d2..ee07fd6cd 100644
--- a/components/interface/VFBTermInfo/ButtonBarComponent.js
+++ b/components/interface/VFBTermInfo/ButtonBarComponent.js
@@ -184,7 +184,7 @@ export default class ButtonBarComponent extends React.Component {
return (
- {ctrlButtons.map(function (control, id) {
+ {ctrlButtons?.map(function (control, id) {
// grab attributes to init button attributes
var controlConfig = that.resolveCondition(control, path);
var idVal = path.replace(/\./g, '_').replace(/\[/g, '_').replace(/\]/g, '_') + "_" + controlConfig.id + "_buttonBar_btn";
@@ -233,7 +233,7 @@ export default class ButtonBarComponent extends React.Component {
that.colorPickerActionFn = actionFn;
// set style val to color tint icon
if (entity !== undefined) {
- var colorVal = String(entity.getColor().replace(/0X/i, "#") + "0000").slice(0, 7);
+ var colorVal = String(entity?.getColor()?.replace(/0X/i, "#") + "0000")?.slice(0, 7);
styleVal = { color: colorVal.startsWith('#') ? colorVal : ('#' + colorVal) };
classVal += " color-picker-button";
}
@@ -268,7 +268,7 @@ export default class ButtonBarComponent extends React.Component {
ref={ref => that.colorPickerContainer = ref}
style={{ left: that.state.pickerPosition }}>
{
Instances[path].setColor(color.hex);
that.setState({ displayColorPicker: true });
diff --git a/components/interface/VFBTermInfo/VFBTermInfo.js b/components/interface/VFBTermInfo/VFBTermInfo.js
index e5ff6ea32..a23091531 100644
--- a/components/interface/VFBTermInfo/VFBTermInfo.js
+++ b/components/interface/VFBTermInfo/VFBTermInfo.js
@@ -133,8 +133,12 @@ class VFBTermInfo extends React.Component {
instanceType = anyInstance.getType();
}
+ if ( typeof instanceType?.getVariables !== "function" ) {
+ return;
+ }
+
// If there are no variables, we have an empty composite node, don't add any links
- if ( instanceType.getVariables().length == 0 ){
+ if ( instanceType?.getVariables()?.length == 0 ){
return;
}
diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js
index 19ef9b601..99c33a2f0 100644
--- a/components/interface/VFBTree/VFBTree.js
+++ b/components/interface/VFBTree/VFBTree.js
@@ -90,9 +90,9 @@ class VFBTree extends React.Component {
});
}
- findChildren (parent, key, familyList, label) {
+ findChildren (parent, key, familyList, labels) {
var childrenList = [];
- var childKey = this.searchChildren(familyList, key, parent, label);
+ var childKey = this.searchChildren(familyList, key, parent, labels);
if (childKey !== undefined) {
childrenList.push(childKey);
var i = childKey - 1;
@@ -110,7 +110,8 @@ class VFBTree extends React.Component {
}
insertChildren (nodes, edges, child, imagesMap) {
- var childrenList = this.findChildren({ from: child.id }, "from", edges, "part of");
+ // Extend the array of relationships from here
+ var childrenList = this.findChildren({ from: child.id }, "from", edges, ["part of", "SUBCLASSOF"]);
// child.images = this.findChildren({ from: child.id }, "from", edges, "INSTANCEOF");
var nodesList = [];
for ( var i = 0; i < childrenList.length; i++) {
@@ -190,16 +191,16 @@ class VFBTree extends React.Component {
* this will reflect and move to the node (if it exists) that we have on focus.
*/
var innerInstance = undefined;
- if (instance.getParent() !== null) {
+ if (instance?.getParent() !== null) {
innerInstance = instance.getParent();
} else {
innerInstance = instance;
}
- var idToSearch = innerInstance.getId();
+ var idToSearch = innerInstance?.getId();
if (this.state.nodeSelected !== undefined
- && idToSearch !== this.state.nodeSelected.instanceId
- && idToSearch !== this.state.nodeSelected.classId) {
+ && idToSearch !== this.state.nodeSelected?.instanceId
+ && idToSearch !== this.state.nodeSelected?.classId) {
if (idToSearch === window.templateID) {
this.selectNode(this.state.dataTree[0])
return;
@@ -257,9 +258,9 @@ class VFBTree extends React.Component {
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() }))
+ : (this.props.instance?.getParent() === null
+ ? { subtitle: this.props.instance?.getId() }
+ : { subtitle: this.props.instance?.getParent()?.getId() }))
});
} else {
var treeData = [{
@@ -351,10 +352,10 @@ class VFBTree extends React.Component {
aria-hidden="true"
onClick={ e => {
e.stopPropagation();
- if (Instances[rowInfo.node.instanceId].getParent() !== null) {
- Instances[rowInfo.node.instanceId].getParent().show();
+ if (Instances[rowInfo.node.instanceId]?.getParent() !== null) {
+ Instances[rowInfo.node.instanceId]?.getParent().show();
} else {
- Instances[rowInfo.node.instanceId].show();
+ Instances[rowInfo.node.instanceId]?.show();
}
this.setState({ nodeSelected: rowInfo.node });
}} />);
@@ -365,8 +366,8 @@ class VFBTree extends React.Component {
aria-hidden="true"
onClick={ e => {
e.stopPropagation();
- if (Instances[rowInfo.node.instanceId].getParent() !== null) {
- Instances[rowInfo.node.instanceId].getParent().hide();
+ if (Instances[rowInfo.node.instanceId]?.getParent() !== null) {
+ Instances[rowInfo.node.instanceId]?.getParent().hide();
} else {
Instances[rowInfo.node.instanceId].hide();
}
@@ -438,7 +439,7 @@ class VFBTree extends React.Component {
this.colorPickerContainer = undefined;
let instanceFound = false;
for (let i = 0; i < Instances.length; i++) {
- if (Instances[i].getId() === rowInfo.node.instanceId) {
+ if (Instances[i]?.getId() === rowInfo.node.instanceId) {
instanceFound = true;
break;
}
@@ -548,11 +549,11 @@ class VFBTree extends React.Component {
treeData={treeData}
activateParentsNodeOnClick={true}
handleClick={this.nodeClick}
- style={{ width: this.props.size.width - 10, height: this.props.size.height, float: 'left', clear: 'both' }}
+ style={{ width: this.props.size?.width - 10, 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}
+ searchQuery={this.state.nodeSelected === undefined ? this.props.instance?.getParent()?.getId() : this.state.nodeSelected?.subtitle}
onlyExpandSearchedNodes={false}
/>
}
diff --git a/components/interface/VFBTree/helper.js b/components/interface/VFBTree/helper.js
index 084051717..26adb81e0 100644
--- a/components/interface/VFBTree/helper.js
+++ b/components/interface/VFBTree/helper.js
@@ -48,7 +48,7 @@ const buildDictClassToIndividual = data => {
return dictionaryIndividuals;
};
-const searchChildren = (array, key, target, label) => {
+const searchChildren = (array, key, target, labels) => {
// Define Start and End Index
let startIndex = 0;
let endIndex = array.length - 1;
@@ -60,7 +60,7 @@ const searchChildren = (array, key, target, label) => {
// Compare Middle Index with Target for match
if (isNumber(array[middleIndex][key]) === isNumber(target[key])) {
// check for target relationship (label)
- if (array[middleIndex].label === label){
+ if ((labels === undefined) || labels.includes(array[middleIndex].label)){
return middleIndex;
} else {
// move on if not matching target relationship (label)
diff --git a/components/interface/VFBUploader/VFBUploader.js b/components/interface/VFBUploader/VFBUploader.js
index 020486f0d..146f9f68b 100644
--- a/components/interface/VFBUploader/VFBUploader.js
+++ b/components/interface/VFBUploader/VFBUploader.js
@@ -1,172 +1,359 @@
-import React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button';
-import CircularProgress from '@material-ui/core/CircularProgress';
-import Dialog from '@material-ui/core/Dialog';
-import DialogActions from '@material-ui/core/DialogActions';
-import DialogContent from '@material-ui/core/DialogContent';
-import DialogContentText from '@material-ui/core/DialogContentText';
-import DialogTitle from '@material-ui/core/DialogTitle';
-import FormControl from '@material-ui/core/FormControl';
-import Typography from '@material-ui/core/Typography';
-import FormControlLabel from '@material-ui/core/FormControlLabel';
-import InputLabel from '@material-ui/core/InputLabel';
-import Select from '@material-ui/core/Select';
-import ChevronRightIcon from "@material-ui/icons/ChevronRight";
-import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
-import Checkbox from '@material-ui/core/Checkbox';
-import { withStyles } from '@material-ui/styles';
-import axios from 'axios';
-import { DropzoneArea } from 'material-ui-dropzone'
-
-const PERMISSIONS = "Permissions to store URL in browser history";
-const styles = theme => ({
- listItemText: { fontSize:'1em' },
- templateSelection: {
- width : "30% !important",
- height : "5rem"
- },
- templateContent : { fontSize : "14px" },
- dialogActions : { justifyContent : "space-evenly" }
-});
+import React from "react";
+import {
+ Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel,
+ FormGroup, InputLabel, Select, Typography, IconButton, Divider, Box, TextField,
+ ListItemIcon, ListItemText, Checkbox, MenuItem, Button, LinearProgress, CircularProgress, Grid
+} from "@material-ui/core";
+import FileCopyIcon from "@material-ui/icons/FileCopy";
+import CloseIcon from "@material-ui/icons/Close";
+import DeleteIcon from "@material-ui/icons/Delete";
+import CheckIcon from '@material-ui/icons/Check';
+import InfoIcon from '@material-ui/icons/Info';
+import ReplayIcon from '@material-ui/icons/Replay';
+import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
+import { withStyles } from "@material-ui/styles";
+import axios from "axios";
+import { DropzoneArea } from "material-ui-dropzone";
+import UploadIcon from "../../configuration/VFBUploader/upload-icon.png";
+import { nanoid } from 'nanoid';
+import FileIcon from "../../configuration/VFBUploader/file-icon.png";
+import { CustomStyle, CustomTheme } from "./styles";
+const UNIQUE_ID = "UNIQUE_ID";
class VFBUploader extends React.Component {
constructor (props) {
super(props);
this.state = {
open: false,
- uploading : false,
- nblastEnabled : false,
- files : [],
- permissionsChecked : false,
- templateSelected : ""
- }
-
- this.configuration = require('../../configuration/VFBUploader/configuration');
+ fileNBLASTURL: "",
+ nblastEnabled: false,
+ files: [],
+ templateSelected: "",
+ progress: 100,
+ cookies : false,
+ error : false,
+ uploading : false
+ };
+
+ this.configuration = require("../../configuration/VFBUploader/configuration");
this.handleCloseDialog = this.handleCloseDialog.bind(this);
this.openDialog = this.openDialog.bind(this);
- this.handlePermissionsCheck = this.handlePermissionsCheck.bind(this);
this.handleNBLASTAction = this.handleNBLASTAction.bind(this);
+ this.handleFileDelete = this.handleFileDelete.bind(this);
this.requestUpload = this.requestUpload.bind(this);
+ this.getTitleHead = this.getTitleHead.bind(this);
+ this.getUploaderComponents = this.getUploaderComponents.bind(this);
+ this.getErrorDialog = this.getErrorDialog.bind(this);
+ this.getUploadActions = this.getUploadActions.bind(this);
+ this.handleCookieEvent = this.handleCookieEvent.bind(this);
+ }
+
+ handleCookieEvent (event) {
+ this.setState({ cookies : event.target.checked })
}
handleCloseDialog () {
- this.setState({ open : false });
+ this.setState({ open: false });
}
-
- handleDropZoneChange (files){
+
+ handleDropZoneChange (files) {
this.setState({ files: files });
}
-
+
+ handleFileDelete () {
+ this.setState({ files: [], nblastEnabled: false });
+ }
+
handleTemplateChange (event) {
- this.setState({ templateSelected : event.target.value })
+ this.setState({ templateSelected: event.target.value });
}
-
+
openDialog () {
- this.setState({ open : true });
+ this.setState({ open: true });
}
-
+
handleNBLASTAction () {
- this.requestUpload({});
+ let newId = "VFBu_" + nanoid(8);
+ let url = this.configuration.nblastURL.replace(UNIQUE_ID, this.state.templateSelected + "&" + newId);
+ var formData = new FormData();
+ formData.append("file", this.state.files[0]);
+ formData.append("vfbID", newId);
+ formData.append("templateID", this.state.templateSelected);
+ this.requestUpload(formData, url);
}
-
+
/**
- * Make axios call to download the zip
+ * Make axios call to upload to server
*/
- requestUpload (jsonRequest) {
+ requestUpload (formData, url) {
let self = this;
+ let _id = formData.get("vfbID");
+ let newURL = window.location.origin + window.location.pathname + "&q=" + _id + "," + this.configuration.queryType;
- this.setState( { uploading : true } );
- // Axios HTTP Post request with post query
- axios({
- method: 'post',
- url: this.configuration.nblastURL,
- headers: { 'content-type': this.configuration.contentType },
- data: jsonRequest,
- responseType: "arraybuffer"
- }).then( function (response) {
- const url = window.URL.createObjectURL(new Blob([response.data]));
- setTimeout( () => self.setState( { nblastEnabled : true, uploading : false } ), 500);
- }).catch( function (error) {
- self.downloadErrorMessage = error?.message;
- self.setState( { nblastEnabled : true, uploading : false } );
+ this.setState({ fileNBLASTURL: newURL, uploading : true });
+ window.setCookie(_id, newURL, this.configuration.cookieStorageDays);
+
+ axios.put(url,
+ formData, { headers: { 'Content-Type': this.configuration.contentType } }
+ ).then(function (response) {
+ console.log('SUCCESS!!', response);
+ self.setState({ uploading : false, nblastEnabled: true });
})
+ .catch(function (error) {
+ console.log('FAILURE!!', error);
+ self.setState({ error : true, uploading : false });
+ });
}
- /**
- * Handle checkbox selection of different types to download
- */
- handlePermissionsCheck (event) {
- this.setState({ permissionsChecked : event.target.checked });
+ getTitleHead () {
+ return (
+
+ {this.configuration.text.dialogTitle}
+
+
+ {this.configuration.text.dialogSubtitle}
+
+ );
}
- render () {
- let self = this;
+ getUploaderComponents () {
const { classes } = this.props;
-
+ let self = this;
return (
-
- )
+
+
+
+
+
+
+
+ {this.configuration.text.infoMessage}
+
+
+
+
+ );
+ }
+
+ getErrorDialog () {
+ const { classes } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ {this.configuration.text.errorDialog}
+
+
+
+
+ );
+ }
+
+ getUploadActions () {
+ const { classes } = this.props;
+ let self = this;
+
+ return (
+ <>
+
+
+
+
+ { !this.state.error
+ ? !this.state.nblastEnabled ? (
+
+
+ {this.state.uploading ? : this.configuration.text.blastButtonText}
+
+
+ ) : (
+
+ }
+ onClick={() => self.setState({ fileNBLASTURL : "", nblastEnabled : false, files : [], templateSelected: "" }) }
+ variant="outlined"
+ >
+ {this.configuration.text.restartButtonText}
+
+
+ )
+ :
+ }
+ onClick={() => self.setState({ fileNBLASTURL : "", error : false, nblastEnabled : false, files : [], templateSelected: "" }) }
+ >
+ {this.configuration.text.errorButtonText}
+
+
+ }
+
+ >
+ );
+ }
+
+ render () {
+ let self = this;
+ const { classes } = this.props;
+
+ return (
+
+
+
+ );
}
}
-export default (withStyles(styles)(VFBUploader));
\ No newline at end of file
+export default withStyles(CustomStyle)(VFBUploader);
\ No newline at end of file
diff --git a/components/interface/VFBUploader/styles.js b/components/interface/VFBUploader/styles.js
new file mode 100644
index 000000000..a4a796f76
--- /dev/null
+++ b/components/interface/VFBUploader/styles.js
@@ -0,0 +1,107 @@
+import { createMuiTheme } from "@material-ui/core/styles";
+
+export const CustomStyle = theme => ({
+ dropzoneArea: { minHeight: "20vh !important" },
+ marginTop: { marginTop: "2vh !important" },
+ checked: { "&$checked": { color: "#0AB7FE" } },
+ dialog: {
+ overflowY: 'unset',
+ maxWidth: "60vh",
+ width: "60vh",
+ margin: "0 auto"
+ },
+ customizedButton: {
+ position: 'absolute',
+ left: '90%',
+ top: '2%',
+ backgroundColor: '#F5F5F5',
+ color: 'gray',
+ },
+ errorButton : {
+ backgroundColor : "rgba(252, 231, 231, 1)",
+ color : "red",
+ borderColor : "red",
+ "&:hover": {
+ backgroundColor: "rgba(252, 231, 231, 1)",
+ color: "red"
+ }
+ },
+ vfbColor : { backgroundColor : "#EEF9FF" },
+ cookiesBox : {
+ width : "100%",
+ display : "contents"
+ }
+})
+
+export const CustomTheme = createMuiTheme({
+ typography: {
+ h2: {
+ fontSize: 22,
+ fontWeight: 400,
+ fontStyle: "normal",
+ color : "#181818",
+ lineHeight: "26.4px",
+ fontFamily: "Barlow Condensed",
+ },
+ caption: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ color : "#181818",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow Condensed",
+ },
+ h5: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ color : "rgba(0, 0, 0, 0.4)",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow Condensed",
+ }
+ },
+ palette: { primary: { main: '#0AB7FE' }, secondary : { main : "#fff" }, error : { main : "#ff0000" } },
+ overrides: {
+ MuiButton: {
+ contained: {
+ color: "#f1f1f1",
+ backgroundColor : "#0AB7FE",
+ "&:hover": {
+ backgroundColor: "#0AB7FE",
+ color: "#f1f1f1"
+ },
+ "&:disabled": {
+ backgroundColor: "rgba(10, 183, 254, .4)",
+ color: "#f1f1f1"
+ }
+ },
+ outlined: {
+ color: "#0AB7FE",
+ borderColor : "#0AB7FE",
+ "&:hover": {
+ backgroundColor: "#0AB7FE",
+ color: "#f1f1f1"
+ }
+ },
+ },
+ MuiFilledInput : {
+ root : { backgroundColor : "#EEF9FF" },
+ input : {
+ color : "#0AB7FE !important",
+ borderColor : "#0AB7FE !important"
+ }
+ },
+ MuiSelect : {
+ root : {
+ textAlign : "start",
+ display : "flex"
+ }
+ },
+ MuiListItemIcon : {
+ root : {
+ color : "#0AB7FE",
+ padding : "4px"
+ }
+ }
+ }
+});
diff --git a/model/vfb.xmi b/model/vfb.xmi
index 4590f5d37..987c0c371 100644
--- a/model/vfb.xmi
+++ b/model/vfb.xmi
@@ -186,7 +186,7 @@
name="anatomy_query"
description="fetch Individual instances from Class ID list"
runForCount="false"
- query=""statement": "MATCH (primary:Class) WHERE primary.short_form in $ids WITH primary CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<- [:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]- (channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]-> (template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 5', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'bac066c' AS version, 'anatomy_query' AS query, anatomy_channel_image", "parameters" : { "ids" : $ARRAY_ID_RESULTS }"
+ query=""statement": "MATCH (primary:Class) WHERE primary.short_form in $ids WITH primary CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<- [:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]- (channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]-> (template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 5', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), unique_facets: coalesce(technique.uniqueFacets,[]), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), unique_facets: coalesce(template.uniqueFacets,[]), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'bac066c' AS version, 'anatomy_query' AS query, anatomy_channel_image", "parameters" : { "ids" : $ARRAY_ID_RESULTS }"
countQuery=""statement": "MATCH (n:Class) WHERE n.short_form IN $ids RETURN count(n) AS count", "parameters" : { "ids" : $ARRAY_ID_RESULTS }"/>
+ query=""statement": "MATCH (n:Template {short_form:$id})<-[:depicts]-(:Template)<-[r:in_register_with]-(dc:Individual)-[:depicts]->(di:Individual) OPTIONAL MATCH (di)-[:INSTANCEOF]->(d:Class) RETURN distinct di.short_form as id, di.label as name, coalesce(di.description[0],d.description[0]) as def, COLLECT(DISTINCT d.label) as type, replace(r.folder[0],'http:','https:') + '/thumbnailT.png' as file", "parameters" : { "id" : "$ID" }"
+ countQuery=""statement": "MATCH (n:Template {short_form:$id})<-[:depicts]-(:Template)<-[r:in_register_with]-(dc:Individual)-[:depicts]->(di:Individual) RETURN count(di) as count", "parameters" : { "id" : "$ID" }"/>
@@ -597,7 +597,7 @@
name="Get JSON for Neuron Class"
description="Get JSON for Neuron Class"
runForCount="false"
- query=""statement": "MATCH (primary:Class) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,parents,relationships CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) where rp.typ = 'syn' WITH CASE WHEN p is null THEN [] ELSE collect({ pub: { core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } , synonym: { label: coalesce(rp.value[0], ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.has_synonym_type[0],'') } }) END AS pub_syn,primary,parents,relationships,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WHERE rp.typ = 'def' WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS def_pubs,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(ep:Class)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(primary) WITH CASE WHEN ep IS NULL THEN [] ELSE COLLECT({ short_form: ep.short_form, label: coalesce(ep.label,''), iri: ep.iri, types: labels(ep), symbol: coalesce(ep.symbol[0], '')} ) END AS targeting_splits,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn,def_pubs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Neuron Class' AS query, '4025897' AS version , parents, relationships, xrefs, anatomy_channel_image, pub_syn, def_pubs, targeting_splits", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:Class) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,parents,relationships CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), unique_facets: coalesce(technique.uniqueFacets,[]), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), unique_facets: coalesce(template.uniqueFacets,[]), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) where rp.typ = 'syn' WITH CASE WHEN p is null THEN [] ELSE collect({ pub: { core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } , synonym: { label: coalesce(rp.value[0], ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.has_synonym_type[0],'') } }) END AS pub_syn,primary,parents,relationships,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WHERE rp.typ = 'def' WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS def_pubs,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(ep:Class)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(primary) WITH CASE WHEN ep IS NULL THEN [] ELSE COLLECT({ short_form: ep.short_form, label: coalesce(ep.label,''), iri: ep.iri, types: labels(ep), symbol: coalesce(ep.symbol[0], '')} ) END AS targeting_splits,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn,def_pubs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Neuron Class' AS query, '4025897' AS version , parents, relationships, xrefs, anatomy_channel_image, pub_syn, def_pubs, targeting_splits", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:Class {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -607,7 +607,7 @@
name="Get JSON for Split Class"
description="Get JSON for Split Class"
runForCount="false"
- query=""statement": "MATCH (primary:Class) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,parents,relationships CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) where rp.typ = 'syn' WITH CASE WHEN p is null THEN [] ELSE collect({ pub: { core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } , synonym: { label: coalesce(rp.value[0], ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.has_synonym_type[0],'') } }) END AS pub_syn,primary,parents,relationships,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WHERE rp.typ = 'def' WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS def_pubs,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(primary)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(n:Neuron) WITH CASE WHEN n IS NULL THEN [] ELSE COLLECT({ short_form: n.short_form, label: coalesce(n.label,''), iri: n.iri, types: labels(n), symbol: coalesce(n.symbol[0], '')} ) END AS target_neurons,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn,def_pubs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Split Class' AS query, '4025897' AS version , parents, relationships, xrefs, anatomy_channel_image, pub_syn, def_pubs, target_neurons", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:Class) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,parents,relationships CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), unique_facets: coalesce(technique.uniqueFacets,[]), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), unique_facets: coalesce(template.uniqueFacets,[]), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) where rp.typ = 'syn' WITH CASE WHEN p is null THEN [] ELSE collect({ pub: { core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } , synonym: { label: coalesce(rp.value[0], ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.has_synonym_type[0],'') } }) END AS pub_syn,primary,parents,relationships,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WHERE rp.typ = 'def' WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS def_pubs,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(primary)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(n:Neuron) WITH CASE WHEN n IS NULL THEN [] ELSE COLLECT({ short_form: n.short_form, label: coalesce(n.label,''), iri: n.iri, types: labels(n), symbol: coalesce(n.symbol[0], '')} ) END AS target_neurons,primary,parents,relationships,xrefs,anatomy_channel_image,pub_syn,def_pubs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Split Class' AS query, '4025897' AS version , parents, relationships, xrefs, anatomy_channel_image, pub_syn, def_pubs, target_neurons", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:Class {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -617,7 +617,7 @@
name="Get JSON for Individual"
description="Get JSON for Individual:Anatomy"
runForCount="false"
- query=""statement": "MATCH (primary:Individual) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (primary)-[:has_source]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary,dataset_license OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,dataset_license,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, dataset_license, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,dataset_license,parents,relationships OPTIONAL MATCH (primary)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, dataset_license, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE collect ({ channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }}) END AS channel_image,primary,dataset_license,parents,relationships,xrefs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Individual:Anatomy' AS query, '4025897' AS version , dataset_license, parents, relationships, xrefs, channel_image", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:Individual) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (primary)-[:has_source]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), unique_facets: coalesce(l.uniqueFacets,[]), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary,dataset_license OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,dataset_license,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, dataset_license, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,dataset_license,parents,relationships OPTIONAL MATCH (primary)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, dataset_license, parents, relationships, xrefs OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE collect ({ channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), unique_facets: coalesce(technique.uniqueFacets,[]), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), unique_facets: coalesce(template.uniqueFacets,[]), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }}) END AS channel_image,primary,dataset_license,parents,relationships,xrefs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Individual:Anatomy' AS query, '4025897' AS version , dataset_license, parents, relationships, xrefs, channel_image", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:Individual {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -637,7 +637,7 @@
name="Get JSON for Template"
description="Get JSON for Template"
runForCount="false"
- query=""statement": "MATCH (primary:Template) WHERE primary.short_form in [$id] WITH primary MATCH (channel:Individual)<-[irw:in_register_with]-(channel:Individual)-[:depicts]->(primary) WITH { index: coalesce(apoc.convert.toInteger(irw.index), []) + [], extent: irw.extent[0], center: irw.center[0], voxel: irw.voxel[0], orientation: coalesce(irw.orientation[0], ''), image_folder: coalesce(irw.folder[0],''), channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} } as template_channel,primary OPTIONAL MATCH (technique:Class)<-[:is_specified_output_of]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(primary) WHERE technique.short_form IN ['FBbi_00000224','FBbi_00000251'] AND exists(irw.index) WITH primary, template_channel, collect ({ channel: channel, irw: irw}) AS painted_domains UNWIND painted_domains AS pd OPTIONAL MATCH (channel:Individual { short_form: pd.channel.short_form})-[:depicts]-(ai:Individual)-[:INSTANCEOF]->(c:Class) WITH collect({ anatomical_type: { short_form: c.short_form, label: coalesce(c.label,''), iri: c.iri, types: labels(c), symbol: coalesce(c.symbol[0], '')} , anatomical_individual: { short_form: ai.short_form, label: coalesce(ai.label,''), iri: ai.iri, types: labels(ai), symbol: coalesce(ai.symbol[0], '')} , folder: pd.irw.folder[0], center: coalesce (pd.irw.center, []), index: [] + coalesce (pd.irw.index, []) }) AS template_domains,primary,template_channel OPTIONAL MATCH (primary)-[:has_source]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary,template_channel,template_domains OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary,template_channel,template_domains,dataset_license OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,template_channel,template_domains,dataset_license,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, template_channel, template_domains, dataset_license, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,template_channel,template_domains,dataset_license,parents,relationships OPTIONAL MATCH (o:Individual)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), symbol: coalesce(o.symbol[0], '')} }) END AS related_individuals ,primary,template_channel,template_domains,dataset_license,parents,relationships,xrefs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Template with expanded domain techniques' AS query, 'm20211015' AS version , template_channel, template_domains, dataset_license, parents, relationships, xrefs, related_individuals", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:Template) WHERE primary.short_form in [$id] WITH primary MATCH (channel:Individual)<-[irw:in_register_with]-(channel:Individual)-[:depicts]->(primary) WITH { index: coalesce(apoc.convert.toInteger(irw.index), []) + [], extent: irw.extent[0], center: irw.center[0], voxel: irw.voxel[0], orientation: coalesce(irw.orientation[0], ''), image_folder: coalesce(irw.folder[0],''), channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} } as template_channel,primary OPTIONAL MATCH (technique:Class)<-[:is_specified_output_of]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(primary) WHERE technique.short_form IN ['FBbi_00000224','FBbi_00000251'] AND exists(irw.index) WITH primary, template_channel, collect ({ channel: channel, irw: irw}) AS painted_domains UNWIND painted_domains AS pd OPTIONAL MATCH (channel:Individual { short_form: pd.channel.short_form})-[:depicts]-(ai:Individual)-[:INSTANCEOF]->(c:Class) WITH collect({ anatomical_type: { short_form: c.short_form, label: coalesce(c.label,''), iri: c.iri, types: labels(c), symbol: coalesce(c.symbol[0], '')} , anatomical_individual: { short_form: ai.short_form, label: coalesce(ai.label,''), iri: ai.iri, types: labels(ai), symbol: coalesce(ai.symbol[0], '')} , folder: pd.irw.folder[0], center: coalesce (pd.irw.center, []), index: [] + coalesce (pd.irw.index, []) }) AS template_domains,primary,template_channel OPTIONAL MATCH (primary)-[:has_source]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), unique_facets: coalesce(l.uniqueFacets,[]), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary,template_channel,template_domains OPTIONAL MATCH (o:Class)<-[r:SUBCLASSOF|INSTANCEOF]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} ) END AS parents ,primary,template_channel,template_domains,dataset_license OPTIONAL MATCH (o:Class)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} }) END AS relationships ,primary,template_channel,template_domains,dataset_license,parents OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, template_channel, template_domains, dataset_license, parents, relationships OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,template_channel,template_domains,dataset_license,parents,relationships OPTIONAL MATCH (o:Individual)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.iri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o), unique_facets: coalesce(o.uniqueFacets,[]), symbol: coalesce(o.symbol[0], '')} }) END AS related_individuals ,primary,template_channel,template_domains,dataset_license,parents,relationships,xrefs RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for Template with expanded domain techniques' AS query, 'm20211015' AS version , template_channel, template_domains, dataset_license, parents, relationships, xrefs, related_individuals", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:Template {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -647,7 +647,7 @@
name="Get JSON for pub"
description="Fetches JSON for pub."
runForCount="false"
- query=""statement": "MATCH (primary:Individual:pub) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (primary)-[:has_reference]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'bac066c' AS version , dataset_license, {title: coalesce(primary.title[0], '') ,PubMed: coalesce(primary.PMID[0], ''), FlyBase: coalesce(primary.FlyBase[0], ''), DOI: coalesce(primary.DOI[0], '') }AS pub_specific_content", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:Individual:pub) WHERE primary.short_form in [$id] WITH primary OPTIONAL MATCH (primary)-[:has_reference]-(ds:DataSet)-[:has_license|license]->(l:License) WITH COLLECT ({ dataset: { link : coalesce(ds.dataset_link[0], ''), core : { short_form: ds.short_form, label: coalesce(ds.label,''), iri: ds.iri, types: labels(ds), symbol: coalesce(ds.symbol[0], '')} }, license: { icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), unique_facets: coalesce(l.uniqueFacets,[]), symbol: coalesce(l.symbol[0], '')} }}) AS dataset_license,primary RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'bac066c' AS version , dataset_license, {title: coalesce(primary.title[0], '') ,PubMed: coalesce(primary.PMID[0], ''), FlyBase: coalesce(primary.FlyBase[0], ''), DOI: coalesce(primary.DOI[0], '') }AS pub_specific_content", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:pub:Individual {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -657,7 +657,7 @@
name="Get JSON for DataSet"
description="Get JSON for DataSet"
runForCount="false"
- query=""statement": "MATCH (primary:DataSet) WHERE primary.short_form in [$id] WITH primary CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license|license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), symbol: coalesce(l.symbol[0], '')} }) as license,primary,anatomy_channel_image,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS pubs,primary,anatomy_channel_image,xrefs,license OPTIONAL MATCH (primary)<-[:has_source]-(i:Individual) WITH i, primary, anatomy_channel_image, xrefs, license, pubs OPTIONAL MATCH (i)-[:INSTANCEOF]-(c:Class) WITH DISTINCT { images: count(distinct i),types: count(distinct c) } as dataset_counts,primary,anatomy_channel_image,xrefs,license,pubs RETURN { link : coalesce(primary.dataset_link[0], ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for DataSet' AS query, 'bac066c' AS version , anatomy_channel_image, xrefs, license, pubs, dataset_counts", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:DataSet) WHERE primary.short_form in [$id] WITH primary CALL apoc.cypher.run('WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with] ->(template:Individual)-[:depicts]->(template_anat:Individual) RETURN template, channel, template_anat, i, irw limit 10', {primary:primary}) yield value with value.template as template, value.channel as channel,value.template_anat as template_anat, value.i as i, value.irw as irw, primary OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE COLLECT({ anatomy: { short_form: i.short_form, label: coalesce(i.label,''), iri: i.iri, types: labels(i), symbol: coalesce(i.symbol[0], '')} , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), unique_facets: coalesce(channel.uniqueFacets,[]), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), unique_facets: coalesce(technique.uniqueFacets,[]), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), unique_facets: coalesce(template.uniqueFacets,[]), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: apoc.convert.toList(primary.self_xref)[0]}) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:database_cross_reference]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: coalesce(s.link_base[0], ''), accession: coalesce(dbx.accession[0], ''), link_text: primary.label + ' on ' + s.label, homepage: coalesce(s.homepage[0], ''), site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s), unique_facets: coalesce(s.uniqueFacets,[]), symbol: coalesce(s.symbol[0], '')} , icon: coalesce(s.link_icon_url[0], ''), link_postfix: coalesce(s.link_postfix[0], '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license|license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo[0], ''), link : coalesce(l.license_url[0], ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l), unique_facets: coalesce(l.uniqueFacets,[]), symbol: coalesce(l.symbol[0], '')} }) as license,primary,anatomy_channel_image,xrefs OPTIONAL MATCH (primary)-[rp:has_reference]->(p:pub) WITH CASE WHEN p is null THEN [] ELSE collect({ core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p), symbol: coalesce(p.symbol[0], '')} , PubMed: coalesce(p.PMID[0], ''), FlyBase: coalesce(p.FlyBase[0], ''), DOI: coalesce(p.DOI[0], '') } ) END AS pubs,primary,anatomy_channel_image,xrefs,license OPTIONAL MATCH (primary)<-[:has_source]-(i:Individual) WITH i, primary, anatomy_channel_image, xrefs, license, pubs OPTIONAL MATCH (i)-[:INSTANCEOF]-(c:Class) WITH DISTINCT { images: count(distinct i),types: count(distinct c) } as dataset_counts,primary,anatomy_channel_image,xrefs,license,pubs RETURN { link : coalesce(primary.dataset_link[0], ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for DataSet' AS query, 'bac066c' AS version , anatomy_channel_image, xrefs, license, pubs, dataset_counts", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:DataSet {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
@@ -667,7 +667,7 @@
name="Get JSON for License"
description="Get JSON for License"
runForCount="false"
- query=""statement": "MATCH (primary:License) WHERE primary.short_form in [$id] WITH primary RETURN { icon : coalesce(primary.license_logo[0], ''), link : coalesce(primary.license_url[0], ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for License' AS query, 'bac066c' AS version", "parameters" : { "id" : "$ID" }"
+ query=""statement": "MATCH (primary:License) WHERE primary.short_form in [$id] WITH primary RETURN { icon : coalesce(primary.license_logo[0], ''), link : coalesce(primary.license_url[0], ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), unique_facets: coalesce(primary.uniqueFacets,[]), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, 'Get JSON for License' AS query, 'bac066c' AS version", "parameters" : { "id" : "$ID" }"
countQuery=""statement": "MATCH (primary:License {short_form: $id} ) RETURN count(primary) as count", "parameters" : { "id" : "$ID" }">
diff --git a/package.json b/package.json
index eb74472e6..3d7a8389a 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"license": "MIT",
"scripts": {
"test": "jest --verbose",
+ "update-tests-snapshots": "jest --updateSnapshot",
"prebuild": "eslint . --color",
"build": "webpack -p --progress",
"prebuild-dev": "eslint . --color",
@@ -25,7 +26,7 @@
"@babel/runtime": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.4.5",
"@geppettoengine/geppetto-client": "file:./geppetto-client",
- "@material-ui/icons": "3.0.1",
+ "@material-ui/icons": "^4.0.0",
"@material-ui/lab": "^4.0.0-alpha.57",
"@types/react-rnd": "^8.0.0",
"axios": "^0.19.2",
@@ -45,7 +46,7 @@
"puppeteer": "^1.17.0",
"react-collapsible": "^2.3.1",
"react-color": "^2.17.3",
- "react-tabs": "3.0.0",
+ "react-tabs": "^3.2.3",
"react-redux": "^7.0.3",
"redux": "^4.0.1",
"style-loader": "^0.13.2",
diff --git a/tests/jest/resources/volume.nrrd b/tests/jest/resources/volume.nrrd
new file mode 100644
index 000000000..2191e6b26
Binary files /dev/null and b/tests/jest/resources/volume.nrrd differ
diff --git a/tests/jest/vfb/batch1/focus-term-tests.js b/tests/jest/vfb/batch1/focus-term-tests.js
index 3c103529e..04e8a8ff8 100644
--- a/tests/jest/vfb/batch1/focus-term-tests.js
+++ b/tests/jest/vfb/batch1/focus-term-tests.js
@@ -50,7 +50,7 @@ describe('VFB Focus Term Tests', () => {
await page.evaluate(async () => {
let tabs = document.getElementsByClassName('MuiListItem-root ');
for ( var i = 0; i < tabs.length ; i ++ ) {
- if ( tabs[i].innerText === "medulla (FBbt_00003748)" ) {
+ if ( tabs[i].innerText.split('\n')[0] === "medulla (FBbt_00003748)" ) {
tabs[i].click();
}
}
diff --git a/tests/jest/vfb/batch1/spotlight-tests.js b/tests/jest/vfb/batch1/spotlight-tests.js
index dd13c25a6..a4e81778a 100644
--- a/tests/jest/vfb/batch1/spotlight-tests.js
+++ b/tests/jest/vfb/batch1/spotlight-tests.js
@@ -81,10 +81,10 @@ describe('VFB Spotlight Tests', () => {
await page.evaluate(async () => {
let tabs = document.getElementsByClassName('MuiListItem-root ');
for ( var i = 0; i < tabs.length ; i ++ ) {
- if ( tabs[i].innerText === "fru-M-200266 (VFB_00000001)" ) {
+ if ( tabs[i].innerText.split('\n')[0] === "fru-M-200266 (VFB_00000001)" ) {
tabs[i].click();
}
- }
+ }
});
await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { hidden: true, timeout : 50000 });
})
diff --git a/tests/jest/vfb/batch2/tree-browser-tests.js b/tests/jest/vfb/batch2/tree-browser-tests.js
index 51fe67ea5..8fb9d3556 100644
--- a/tests/jest/vfb/batch2/tree-browser-tests.js
+++ b/tests/jest/vfb/batch2/tree-browser-tests.js
@@ -184,7 +184,7 @@ describe('VFB Tree Browser Component Tests', () => {
await page.evaluate(async () => {
let tabs = document.getElementsByClassName('MuiListItem-root ');
for ( var i = 0; i < tabs.length ; i ++ ) {
- if ( tabs[i].innerText === "medulla (FBbt_00003748)" ) {
+ if ( tabs[i].innerText.split('\n')[0] === "medulla (FBbt_00003748)" ) {
tabs[i].click();
}
}
@@ -192,8 +192,15 @@ describe('VFB Tree Browser Component Tests', () => {
await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { hidden: true, timeout : 50000 });
})
+ // Check Medulla is focus term
+ it('Medulla loaded as the focus term', async () => {
+ await page.waitFor(5000);
+ // Check Medulla actually loaded
+ let element = await findElementByText(page, "medulla");
+ expect(element).toBe("medulla");
+ })
+
it('Open Tree Browser', async () => {
- await page.waitFor(2000);
await selectTab(page, "Template ROI Browser");
// Check that the Tree Browser is visible
diff --git a/tests/jest/vfb/batch3/term-context-tests.js b/tests/jest/vfb/batch3/term-context-tests.js
index c1bd76d25..5ef780ec7 100644
--- a/tests/jest/vfb/batch3/term-context-tests.js
+++ b/tests/jest/vfb/batch3/term-context-tests.js
@@ -13,7 +13,7 @@ const SNAPSHOT_OPTIONS = {
customSnapshotsDir : "./tests/jest/vfb/snapshots",
comparisonMethod: 'ssim',
failureThresholdType: 'percent',
- failureThreshold: 0.05 // This means a 5% difference is allowed between compared snapshots during tests
+ failureThreshold: 0.20 // This means a 20% difference is allowed between compared snapshots during tests
};
//Import snapshot module
@@ -73,7 +73,7 @@ describe('VFB Term Context Component Tests', () => {
await page.evaluate(async () => {
let tabs = document.getElementsByClassName('MuiListItem-root ');
for ( var i = 0; i < tabs.length ; i ++ ) {
- if ( tabs[i].innerText === "medulla (FBbt_00003748)" ) {
+ if ( tabs[i].innerText.split('\n')[0] === "medulla (FBbt_00003748)" ) {
tabs[i].click();
}
}
@@ -123,6 +123,7 @@ describe('VFB Term Context Component Tests', () => {
// Take screenshot, and compared to stored image of page.
const image = await page.screenshot();
// This will fail if Medulla didn't load in Term Context, since snapshot comparison will show differences
+ SNAPSHOT_OPTIONS.failureThreshold = 0.20 // allowing for minor graph layout changes
expect(image).toMatchImageSnapshot( { ...SNAPSHOT_OPTIONS, customSnapshotsDir : "./tests/jest/vfb/snapshots/term-context/medulla" });
})
})
diff --git a/tests/jest/vfb/review/downloader-tests.js b/tests/jest/vfb/review/downloader-tests.js
new file mode 100644
index 000000000..d9b35ea05
--- /dev/null
+++ b/tests/jest/vfb/review/downloader-tests.js
@@ -0,0 +1,82 @@
+const puppeteer = require('puppeteer');
+const { TimeoutError } = require('puppeteer/Errors');
+
+import { getUrlFromProjectId } from '../cmdline.js';
+import { wait4selector, click, closeModalWindow, findElementByText } from '../utils';
+import * as ST from '../selectors';
+
+const baseURL = process.env.url || 'http://localhost:8081/org.geppetto.frontend';
+const PROJECT_URL = baseURL + "/geppetto?id=VFB_00102107&i=VFB_00101567,VFB_00102271,VFB_00102107";
+
+/**
+ * Query Builder component tests
+ */
+describe('VFB Downloader Tests', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1800000);
+ await page.goto(PROJECT_URL);
+ });
+
+ describe('Test landing page', () => {
+ it('Loading spinner goes away', async () => {
+ await wait4selector(page, ST.SPINNER_SELECTOR, { hidden: true, timeout : 120000 })
+ // Close tutorial window
+ closeModalWindow(page);
+ })
+
+ it('VFB Title shows up', async () => {
+ const title = await page.title();
+ expect(title).toMatch("Virtual Fly Brain");
+ })
+
+ it('Zoom button for VFB_00102107 appears in button bar inside the term info component', async () => {
+ await wait4selector(page, 'button[id=VFB_00102107_zoom_buttonBar_btn]', { visible: true , timeout : 120000 })
+ })
+
+ it('Term info component created after load', async () => {
+ await wait4selector(page, 'div#bar-div-vfbterminfowidget', { visible: true })
+ })
+
+ //Tests canvas has 5 meshes rendered
+ it('Canvas container component has 3 meshes rendered', async () => {
+ expect(
+ await page.evaluate(async () => Object.keys(CanvasContainer.engine.meshes).length)
+ ).toBe(3)
+ })
+ })
+
+ describe('Tests Download Contents', () => {
+ it('Open Download Component Files', async () => {
+ await page.click('i.fa-download');
+
+ await page.waitForSelector('#downloadContents');
+ })
+
+ it('Download disabled by default', async () => {
+ expect(
+ await page.evaluate(async () => document.querySelector('#downloadContentsButton').disabled )
+ ).toBe(true)
+ })
+
+ it('Download all files for all instances', async () => {
+ await page.evaluate(async selector => {
+ let inputs = document.querySelectorAll(".MuiDialogContent-root input");
+ inputs = Array.prototype.slice.call(inputs).filter(input => input.id != "ALL_INSTANCES");
+ inputs.forEach( input => {
+ input.click();
+ });
+ });
+
+ expect(
+ await page.evaluate(async () => document.querySelector('#downloadContentsButton').disabled )
+ ).toBe(false)
+ })
+
+
+ it('Download Contents Dialog goes away', async () => {
+ await page.click('#downloadContentsButton');
+ await wait4selector(page, '#downloadContents', { hidden: true , timeout : 120000 })
+ })
+
+ })
+})
diff --git a/tests/jest/vfb/review/nblast-uploader-tests.js b/tests/jest/vfb/review/nblast-uploader-tests.js
new file mode 100644
index 000000000..3a81c62a7
--- /dev/null
+++ b/tests/jest/vfb/review/nblast-uploader-tests.js
@@ -0,0 +1,75 @@
+const puppeteer = require('puppeteer');
+const { TimeoutError } = require('puppeteer/Errors');
+
+import { getUrlFromProjectId } from '../cmdline.js';
+import { wait4selector, click, closeModalWindow, findElementByText } from '../utils';
+import * as ST from '../selectors';
+
+const baseURL = process.env.url || 'http://localhost:8081/org.geppetto.frontend';
+const PROJECT_URL = baseURL + "/geppetto?id=VFB_00017894";
+
+/**
+ * Query Builder component tests
+ */
+describe('VFB Uploader Tests', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1800000);
+ await page.goto(PROJECT_URL);
+ });
+
+ describe('Test landing page', () => {
+ it('Loading spinner goes away', async () => {
+ await wait4selector(page, ST.SPINNER_SELECTOR, { hidden: true, timeout : 120000 })
+ // Close tutorial window
+ closeModalWindow(page);
+ })
+
+ it('VFB Title shows up', async () => {
+ const title = await page.title();
+ expect(title).toMatch("Virtual Fly Brain");
+ })
+
+ it('Zoom button for VFB_00017894 appears in button bar inside the term info component', async () => {
+ await wait4selector(page, 'button[id=VFB_00017894_zoom_buttonBar_btn]', { visible: true , timeout : 120000 })
+ })
+
+ it('Term info component created after load', async () => {
+ await wait4selector(page, 'div#bar-div-vfbterminfowidget', { visible: true })
+ })
+ })
+
+ describe('Tests NBLAST Uploader', () => {
+ it('Open Uploader', async () => {
+ await page.click('#fa-upload');
+
+ await page.waitForSelector('div.MuiDialog-root');
+ })
+
+ it('Templates Populated', async () => {
+ await page.evaluate(async () => {
+ var dropdown = document.getElementById('mui-component-select-template');
+ var event = document.createEvent('MouseEvents');
+ event.initMouseEvent('mousedown', true, true, window);
+ dropdown.dispatchEvent(event);
+ });
+
+ await page.waitForSelector('li.MuiListItem-root');
+
+ const list = await page.evaluate(async () => {
+ document.querySelectorAll('.MuiListItem-root')[1].click();
+ return document.querySelectorAll('.MuiListItem-root').length;
+ });
+
+ expect(list).toBe(3);
+ })
+
+ it('Template Selected', async () => {
+ const selection = await page.evaluate(async () => {
+ return document.getElementById("template-selection").value;
+ });
+
+ expect(selection).toBe("JRC2018Unisex");
+ })
+
+ })
+})
diff --git a/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-medulla-loaded-graph-remains-the-same-1-snap.png b/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-medulla-loaded-graph-remains-the-same-1-snap.png
index cab033cce..3a5593dc3 100644
Binary files a/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-medulla-loaded-graph-remains-the-same-1-snap.png and b/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-medulla-loaded-graph-remains-the-same-1-snap.png differ
diff --git a/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-test-term-context-component-snapshot-comparison-of-term-context-1-snap.png b/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-test-term-context-component-snapshot-comparison-of-term-context-1-snap.png
index 682c069a8..9291d978d 100644
Binary files a/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-test-term-context-component-snapshot-comparison-of-term-context-1-snap.png and b/tests/jest/vfb/snapshots/term-context/adult-brain/term-context-tests-js-vfb-term-context-component-tests-test-term-context-component-snapshot-comparison-of-term-context-1-snap.png differ
diff --git a/tests/jest/vfb/snapshots/term-context/medulla/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-sync-trigger-graph-displays-medulla-1-snap.png b/tests/jest/vfb/snapshots/term-context/medulla/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-sync-trigger-graph-displays-medulla-1-snap.png
index 41a04136b..d475d20e9 100644
Binary files a/tests/jest/vfb/snapshots/term-context/medulla/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-sync-trigger-graph-displays-medulla-1-snap.png and b/tests/jest/vfb/snapshots/term-context/medulla/term-context-tests-js-vfb-term-context-component-tests-add-medulla-snapshot-comparison-of-term-context-after-sync-trigger-graph-displays-medulla-1-snap.png differ