From 90a11acab502fba8d7103a199225396a0bbfb0fe Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 19 Nov 2019 13:06:15 +0000 Subject: [PATCH 01/25] updating Get JSON for anat_2_ep query (with pubs) --- model/vfb.xmi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 90c64d59a..4671b535e 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -342,8 +342,8 @@ + name="Query for anatomy from expression " + description="Get JSON for anat_2_ep query"> Date: Wed, 20 Nov 2019 14:30:44 +0000 Subject: [PATCH 02/25] updating tips --- components/configuration/buttonBarConfiguration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/configuration/buttonBarConfiguration.js b/components/configuration/buttonBarConfiguration.js index 2fdeef005..b834aa903 100644 --- a/components/configuration/buttonBarConfiguration.js +++ b/components/configuration/buttonBarConfiguration.js @@ -7,7 +7,7 @@ var buttonBarConfig = { "queryBuilderVisible": { "icon": "fa fa-quora", "label": "", - "tooltip": "Open Query" + "tooltip": "Query Results" }, "controlPanelVisible": { "icon": "fa fa-list", From ea871442e63dc84df1c06e64d50583ef0b3031aa Mon Sep 17 00:00:00 2001 From: Rob Court Date: Thu, 21 Nov 2019 17:17:32 +0000 Subject: [PATCH 03/25] updating queries --- model/vfb.xmi | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 4671b535e..1dc07005a 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -307,9 +307,9 @@ + query=""statement": "MATCH (ep:Expression_pattern:Class)<-[ar:overlaps|part_of]-(anoni:Individual)-[:INSTANCEOF]->(anat:Class) WHERE ep.short_form in [{ID}] WITH anoni, anat, ar OPTIONAL MATCH (p:pub { short_form: ar.pub}) WITH anat, anoni, { core: { short_form: p.short_form, label: coalesce(p.label,''), iri: p.iri, types: labels(p) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } AS pub OPTIONAL MATCH (anoni)-[r:Related]->(o:FBdv) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.uri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o) } }) END AS stages ,anoni,anat,pub OPTIONAL MATCH (anat:Synaptic_neuropil)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, anoni, anat, pub, stages , i 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,anoni,anat,pub,stages RETURN { short_form: anat.short_form, label: coalesce(anat.label,''), iri: anat.iri, types: labels(anat) } AS anatomy, 'Get JSON for ep_2_anat query' AS query, 'ca9ab19' AS version , pub, stages, anatomy_channel_image", "parameters" : { "ID" : "$ID" }" + countQuery=""statement": "MATCH (ep:Class:Expression_pattern)<-[ar:overlaps|part_of]-(anoni:Individual)-[:INSTANCEOF]->(anat:Class) WHERE ep.short_form in [{ID}] RETURN count(anat) as count", "parameters" : { "ID" : "$ID" }"/> Date: Thu, 21 Nov 2019 17:19:17 +0000 Subject: [PATCH 04/25] fixing tooltips --- components/interface/FocusTerm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/interface/FocusTerm.js b/components/interface/FocusTerm.js index df1a78606..465600785 100644 --- a/components/interface/FocusTerm.js +++ b/components/interface/FocusTerm.js @@ -498,14 +498,14 @@ export default class FocusTerm extends React.Component { }} /> + title="Query results"> { this.props.UIUpdateManager("queryBuilderVisible"); }} /> + title="Layers"> { this.props.UIUpdateManager("controlPanelVisible"); From 1be5c3e39d3d39633af4d2d1c3bae2f530a57fb8 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Thu, 21 Nov 2019 18:00:41 +0000 Subject: [PATCH 05/25] typo fix --- model/vfb.xmi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 1dc07005a..a922a4c15 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -309,7 +309,7 @@ name="Test Query for Exp from Anatomy" description="Get JSON for anat_2_ep query" returnType="//@libraries.3/@types.1" - query=""statement": "MATCH (ep:Class:Expression_pattern)<-[ar:overlaps|part_of]-(:Individual)-[:INSTANCEOF]->(anat:Class) WHERE anat.short_form in [{ID}] WITH DISTINCT collect(DISTINCT ar.pub) as pubs, anat, ep UNWIND pubs as p MATCH (pub:pub { short_form: p}) WITH anat, ep, collect({ core: { short_form: pub.short_form, label: coalesce(pub.label,''), iri: pub.iri, types: labels(pub) } , PubMed: coalesce(pub.PMID, ''), FlyBase: coalesce(pub.FlyBase, ''), DOI: coalesce(pub.DOI, '') }) as pubs OPTIONAL MATCH (ep)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, anat, ep, pubs , i 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,anat,ep,pubs {short_form: anat.short_form, label: coalesce(anat.label,''), iri: anat.iri, types: labels(anat) } as anatomy, { short_form: ep.short_form, label: coalesce(ep.label,''), iri: ep.iri, types: labels(ep) } AS expression_pattern, 'Get JSON for anat_2_ep query' AS query, 'ca9ab19' AS version , pubs, anatomy_channel_image", "parameters" : { "ID" : "$ID" }" + query=""statement": "MATCH (ep:Class:Expression_pattern)<-[ar:overlaps|part_of]-(:Individual)-[:INSTANCEOF]->(anat:Class) WHERE anat.short_form in [{ID}] WITH DISTINCT collect(DISTINCT ar.pub) as pubs, anat, ep UNWIND pubs as p MATCH (pub:pub { short_form: p}) WITH anat, ep, collect({ core: { short_form: pub.short_form, label: coalesce(pub.label,''), iri: pub.iri, types: labels(pub) } , PubMed: coalesce(pub.PMID, ''), FlyBase: coalesce(pub.FlyBase, ''), DOI: coalesce(pub.DOI, '') }) as pubs OPTIONAL MATCH (ep)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, anat, ep, pubs , i 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,anat,ep,pubs RETURN {short_form: anat.short_form, label: coalesce(anat.label,''), iri: anat.iri, types: labels(anat) } as anatomy, { short_form: ep.short_form, label: coalesce(ep.label,''), iri: ep.iri, types: labels(ep) } AS expression_pattern, 'Get JSON for anat_2_ep query' AS query, 'ca9ab19' AS version , pubs, anatomy_channel_image", "parameters" : { "ID" : "$ID" }" countQuery=""statement": "MATCH (anat:Class) WHERE anat.short_form IN {ARRAY_ID_RESULTS} OPTIONAL MATCH (ep:Class)<-[ar:overlaps|part_of]-(:Individual)-[:INSTANCEOF]->(anat) RETURN count(ep) as count", "parameters" : { "ARRAY_ID_RESULTS" : $ARRAY_ID_RESULTS}"/> Date: Thu, 21 Nov 2019 19:08:20 +0000 Subject: [PATCH 06/25] updating term queries --- model/vfb.xmi | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index a922a4c15..16a85f06e 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -404,9 +404,9 @@ @@ -414,9 +414,9 @@ @@ -424,9 +424,9 @@ @@ -444,9 +444,9 @@ @@ -454,9 +454,9 @@ From ed2c87ee5efdc4fc515973b478e22f46b2ce576f Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 24 Nov 2019 11:09:40 +0000 Subject: [PATCH 07/25] adding template_2_datasets_query --- model/vfb.xmi | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/model/vfb.xmi b/model/vfb.xmi index 16a85f06e..1815bf2c9 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -380,6 +380,26 @@ returnType="//@libraries.3/@types.1" queryProcessorId="neo4jQueryProcessor"/> + + + + + + + From 041324915b93289e647766e69eaf7ca1249cbeab Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 25 Nov 2019 11:43:37 +0000 Subject: [PATCH 08/25] typo fix --- model/vfb.xmi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 1815bf2c9..866cd176c 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -906,7 +906,7 @@ name="Show all datasets aligned to template" description="List all datasets aligned to $NAME" returnType="//@libraries.3/@types.24" - queryChain="//@queries.22"> + queryChain="//@dataSources.0/@queries.12"> From 598c8385cc661e6a971468038c912043df29c036 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 25 Nov 2019 15:47:11 +0000 Subject: [PATCH 09/25] adding extra columns --- .../queryBuilderConfiguration.js | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/components/configuration/queryBuilderConfiguration.js b/components/configuration/queryBuilderConfiguration.js index 96b2f3853..4206f6ab3 100644 --- a/components/configuration/queryBuilderConfiguration.js +++ b/components/configuration/queryBuilderConfiguration.js @@ -76,12 +76,22 @@ var queryResultsColMeta = [ "locked": false, "visible": true, "displayName": "Stage", - "cssClassName": "query-results-stage-column" + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] }, { - "columnName": "controls", + "columnName": "license", "order": 8, "locked": false, + "visible": true, + "displayName": "License", + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "controls", + "order": 9, + "locked": false, "visible": false, "customComponent": QueryResultsControlsComponent, "displayName": "Controls", @@ -91,7 +101,7 @@ var queryResultsColMeta = [ }, { "columnName": "images", - "order": 9, + "order": 10, "locked": false, "visible": true, "customComponent": SlideshowImageComponent, @@ -102,17 +112,26 @@ var queryResultsColMeta = [ }, { "columnName": "score", - "order": 10, + "order": 11, "locked": false, "visible": true, "displayName": "Score", "cssClassName": "query-results-score-column", "sortDirectionCycle": ['desc', 'asc', null] + }, + { + "columnName": "image_count", + "order": 12, + "locked": false, + "visible": true, + "displayName": "Image_count", + "cssClassName": "query-results-score-column", + "sortDirectionCycle": ['desc', 'asc', null] } ]; // which columns to display in the results -var queryResultsColumns = ['name', 'expressed_in', 'description', 'reference', 'type', 'stage', 'images', 'score']; +var queryResultsColumns = ['name', 'expressed_in', 'description', 'reference', 'type', 'stage', 'license', 'images', 'score','image_count']; var queryResultsControlConfig = { "Common": { @@ -140,7 +159,7 @@ var queryResultsControlConfig = { var queryBuilderDatasourceConfig = { VFB: { - url: "https://solr.virtualflybrain.org/solr/ontology/select?fl=short_form,label,synonym,id,type,has_narrow_synonym_annotation,has_broad_synonym_annotation&start=0&fq=ontology_name:(vfb)&fq=shortform_autosuggest:VFB*%20OR%20shortform_autosuggest:FB*%20OR%20is_defining_ontology:true&rows=100&bq=is_obsolete:false%5E100.0%20shortform_autosuggest:VFB*%5E110.0%20shortform_autosuggest:FBbt*%5E100.0%20is_defining_ontology:true%5E100.0%20label_s:%22%22%5E2%20synonym_s:%22%22%20in_subset_annotation:BRAINNAME%5E3%20short_form:FBbt_00003982%5E2&q=$SEARCH_TERM$%20OR%20$SEARCH_TERM$*%20OR%20*$SEARCH_TERM$*&defType=edismax&qf=label%20synonym%20label_autosuggest_ws%20label_autosuggest_e%20label_autosuggest%20synonym_autosuggest_ws%20synonym_autosuggest_e%20synonym_autosuggest%20shortform_autosuggest%20has_narrow_synonym_annotation%20has_broad_synonym_annotation&wt=json&indent=true", + url: "https://solr.virtualflybrain.org/solr/ontology/select?fl=short_form,label,synonym,id,type,has_narrow_synonym_annotation,has_broad_synonym_annotation&start=0&fq=ontology_name:(vfb)&fq=shortform_autosuggest:VFB*%20OR%20shortform_autosuggest:FB*%20OR%20is_defining_ontology:true&rows=100&bq=is_obsolete:false%5E100.0%20shortform_autosuggest:VFB*%5E110.0%20shortform_autosuggest:FBbt*%5E100.0%20is_defining_ontology:true%5E100.0%20label_s:%22%22%5E2%20synonym_s:%22%22%20in_subset_annotation:BRAINNAME%5E3%20short_form:FBbt_00003982%5E2&q=$SEARCH_TERM$%20OR%20$SEARCH_TERM$*%20OR%20*$SEARCH_TERM$*&defType=edismax&qf=label%20synonym%20label_autosuggest_ws%20label_autosuggest_e%20label_autosuggest%20synonym_autosuggest_ws%20synonym_autosuggest_e%20synonym_autosuggest%20shortform_autosuggest%20has_narrow_synonym_annotation%20has_broad_synonym_annotation&wt=json&indent=true", crossDomain: true, id: "short_form", label: { field: "label", formatting: "$VALUE$" }, From bd93421950b002cc293706710d09ad8f4fc272b5 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 25 Nov 2019 15:48:21 +0000 Subject: [PATCH 10/25] sorting by number of images --- components/configuration/queryBuilderConfiguration.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/configuration/queryBuilderConfiguration.js b/components/configuration/queryBuilderConfiguration.js index 4206f6ab3..f74d243b9 100644 --- a/components/configuration/queryBuilderConfiguration.js +++ b/components/configuration/queryBuilderConfiguration.js @@ -299,6 +299,10 @@ var sorterColumns = [ column: "score", order: "DESC" }, + { + column: "image_count", + order: "DESC" + }, { column: "images", order: "DESC" From 4b5751324a1db8ed29d51ac8c17a247591bf67b8 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 25 Nov 2019 16:37:15 +0000 Subject: [PATCH 11/25] reducing log size --- components/interface/ErrorCatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interface/ErrorCatcher.js b/components/interface/ErrorCatcher.js index cf4f5d7b5..89cc74d6c 100644 --- a/components/interface/ErrorCatcher.js +++ b/components/interface/ErrorCatcher.js @@ -47,7 +47,7 @@ class ErrorCatcher extends React.Component { handleClose = () => { 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 url = "https://github.com/VirtualFlyBrain/VFB2/issues/new?body=" + customMessage + this.state.error.message + "\n\n" + this.state.error.stack.replace("#",escape("#")) + "\n\n```diff\n" + window.console.logs.slice(-10).join('\n').replace("#",escape("#")) + "\n```\n"; + var url = "https://github.com/VirtualFlyBrain/VFB2/issues/new?body=" + customMessage + this.state.error.message + "\n\n" + this.state.error.stack.replace("#",escape("#")) + "\n\n```diff\n" + window.console.logs.slice(-8).join('\n').replace("#",escape("#")) + "\n```\n"; var win = window.open(encodeURI(url), '_blank'); win.focus(); }; From 262f963778e8efea96bd4b6aa9b03fb1df1a842a Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 25 Nov 2019 18:45:57 +0000 Subject: [PATCH 12/25] removing count sorting --- components/configuration/queryBuilderConfiguration.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/components/configuration/queryBuilderConfiguration.js b/components/configuration/queryBuilderConfiguration.js index f74d243b9..5074eb848 100644 --- a/components/configuration/queryBuilderConfiguration.js +++ b/components/configuration/queryBuilderConfiguration.js @@ -85,7 +85,7 @@ var queryResultsColMeta = [ "locked": false, "visible": true, "displayName": "License", - "cssClassName": "query-results-stage-column", + "cssClassName": "query-results-license-column", "sortDirectionCycle": ['asc', 'desc', null] }, { @@ -125,7 +125,7 @@ var queryResultsColMeta = [ "locked": false, "visible": true, "displayName": "Image_count", - "cssClassName": "query-results-score-column", + "cssClassName": "query-results-image_count-column", "sortDirectionCycle": ['desc', 'asc', null] } ]; @@ -299,10 +299,6 @@ var sorterColumns = [ column: "score", order: "DESC" }, - { - column: "image_count", - order: "DESC" - }, { column: "images", order: "DESC" From 70849a6f6534fc59be5efd0674ada9fb454b8397 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 12:08:47 +0000 Subject: [PATCH 13/25] taking from neuron_split_links --- model/vfb.xmi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 866cd176c..b3909d878 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -466,7 +466,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 OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo, ''), link : coalesce(l.license_url, ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l) } }) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,anatomy_channel_image,xrefs,license RETURN { link : coalesce(primary.dataset_link, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for DataSet' AS query, 'ca9ab19' AS version , anatomy_channel_image, xrefs, license, def_pubs", "parameters" : { "ID" : "$ID" }" + query=""statement": "MATCH (primary:DataSet) WHERE primary.short_form in [{ID}] WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo, ''), link : coalesce(l.license_url, ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l) } }) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,anatomy_channel_image,xrefs,license RETURN { link : coalesce(primary.dataset_link, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation - comment`, []) } AS term, 'Get JSON for DataSet' AS query, '2e501bd' AS version , anatomy_channel_image, xrefs, license, def_pubs", "parameters" : { "ID" : "$ID" }" countQuery=""statement": "MATCH (primary:DataSet {short_form: {ID}} ) RETURN count(primary) as count", "parameters" : { "ID" : "$ID" }"> From ca7b9b6dd11b8d651c9be603099eeac8a29fe3b7 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 12:32:31 +0000 Subject: [PATCH 14/25] adding split and neuron class term info handling --- model/vfb.xmi | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index b3909d878..5453c3ebd 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -129,6 +129,9 @@ + + + + + + + From 7f3c9ae06cd8c400a607a7524de33d3d406f1a33 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 12:35:00 +0000 Subject: [PATCH 15/25] comment space typo fix --- model/vfb.xmi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 5453c3ebd..90d6c5e86 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -489,7 +489,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 OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo, ''), link : coalesce(l.license_url, ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l) } }) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,anatomy_channel_image,xrefs,license RETURN { link : coalesce(primary.dataset_link, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation - comment`, []) } AS term, 'Get JSON for DataSet' AS query, '2e501bd' AS version , anatomy_channel_image, xrefs, license, def_pubs", "parameters" : { "ID" : "$ID" }" + query=""statement": "MATCH (primary:DataSet) WHERE primary.short_form in [{ID}] WITH primary OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, anatomy_channel_image OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,anatomy_channel_image OPTIONAL MATCH (primary)-[:has_license]->(l:License) WITH collect ({ icon : coalesce(l.license_logo, ''), link : coalesce(l.license_url, ''), core : { short_form: l.short_form, label: coalesce(l.label,''), iri: l.iri, types: labels(l) } }) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,anatomy_channel_image,xrefs,license RETURN { link : coalesce(primary.dataset_link, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for DataSet' AS query, '2e501bd' AS version , anatomy_channel_image, xrefs, license, def_pubs", "parameters" : { "ID" : "$ID" }" countQuery=""statement": "MATCH (primary:DataSet {short_form: {ID}} ) RETURN count(primary) as count", "parameters" : { "ID" : "$ID" }"> @@ -499,7 +499,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, ''), link : coalesce(primary.license_url, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation - comment`, []) } AS term, 'Get JSON for License' AS query, '2e501bd' AS version", "parameters" : { "ID" : "$ID" }" + query=""statement": "MATCH (primary:License) WHERE primary.short_form in [{ID}] WITH primary RETURN { icon : coalesce(primary.license_logo, ''), link : coalesce(primary.license_url, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for License' AS query, '2e501bd' AS version", "parameters" : { "ID" : "$ID" }" countQuery=""statement": "MATCH (primary:License {short_form: {ID}} ) RETURN count(primary) as count", "parameters" : { "ID" : "$ID" }"> From 6568b6eacc8735814ee67308fa0d7d34582e423b Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 12:54:17 +0000 Subject: [PATCH 16/25] adding all dataset query --- model/vfb.xmi | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 90d6c5e86..a169bb145 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -403,6 +403,26 @@ returnType="//@libraries.3/@types.1" queryProcessorId="neo4jQueryProcessor"/> + + + + @@ -499,7 +519,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, ''), link : coalesce(primary.license_url, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for License' AS query, '2e501bd' AS version", "parameters" : { "ID" : "$ID" }" + query=""statement": "MATCH (primary:License) WHERE primary.short_form in [{ID}] WITH primary RETURN { icon : coalesce(primary.license_logo, ''), link : coalesce(primary.license_url, ''), core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation - comment`, []) } AS term, 'Get JSON for License' AS query, '2e501bd' AS version", "parameters" : { "ID" : "$ID" }" countQuery=""statement": "MATCH (primary:License {short_form: {ID}} ) RETURN count(primary) as count", "parameters" : { "ID" : "$ID" }"> @@ -933,4 +953,13 @@ + + + From 9e5ccd6aaec9f32a18634f673e27f5a2770617fa Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 13:03:23 +0000 Subject: [PATCH 17/25] adding license link --- components/configuration/queryBuilderConfiguration.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/configuration/queryBuilderConfiguration.js b/components/configuration/queryBuilderConfiguration.js index 5074eb848..9ff03de3b 100644 --- a/components/configuration/queryBuilderConfiguration.js +++ b/components/configuration/queryBuilderConfiguration.js @@ -84,6 +84,10 @@ var queryResultsColMeta = [ "order": 8, "locked": false, "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 1, + "entityDelimiter": "----", "displayName": "License", "cssClassName": "query-results-license-column", "sortDirectionCycle": ['asc', 'desc', null] From 670dec74c1d8a52f2df8fbaae24e5b51ac954408 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 13:08:44 +0000 Subject: [PATCH 18/25] sorting by number of images in ds --- components/configuration/queryBuilderConfiguration.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/configuration/queryBuilderConfiguration.js b/components/configuration/queryBuilderConfiguration.js index 9ff03de3b..77e0653eb 100644 --- a/components/configuration/queryBuilderConfiguration.js +++ b/components/configuration/queryBuilderConfiguration.js @@ -303,6 +303,10 @@ var sorterColumns = [ column: "score", order: "DESC" }, + { + column: "image_count", + order: "DESC" + }, { column: "images", order: "DESC" From 3b7bf9218c69665cf1991c6f941d6454af460899 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 26 Nov 2019 14:56:12 +0000 Subject: [PATCH 19/25] typo fix --- model/vfb.xmi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index a169bb145..de6d449ef 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -469,7 +469,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) } ) 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.uri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o) } }) END AS relationships ,primary,parents OPTIONAL MATCH (o:Individual)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.uri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o) } }) END AS related_individuals ,primary,parents,relationships OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, parents, relationships, related_individuals OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,parents,relationships,related_individuals OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, parents, relationships, related_individuals, xrefs , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,related_individuals,xrefs OPTIONAL MATCH (primary)-[rp:has_reference { typ: 'syn'}]->(p:pub) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } , synonym: { label: coalesce(rp.synonym, ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.cat,'') } }) END AS pub_syn,primary,parents,relationships,related_individuals,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference { typ: 'def'}]->(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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,parents,relationships,related_individuals,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(primary)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(n:Neuron) WITH COLLECT({ short_form: n.short_form, label: coalesce(n.label,''), iri: n.iri, types: labels(n) } ) as target_neurons,primary,parents,relationships,related_individuals,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) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for Split Class' AS query, '2e501bd' AS version , parents, relationships, related_individuals, 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) } ) 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.uri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o) } }) END AS relationships ,primary,parents OPTIONAL MATCH (o:Individual)<-[r {type:'Related'}]-(primary) WITH CASE WHEN o IS NULL THEN [] ELSE COLLECT ({ relation: { label: r.label, iri: r.uri, type: type(r) } , object: { short_form: o.short_form, label: coalesce(o.label,''), iri: o.iri, types: labels(o) } }) END AS related_individuals ,primary,parents,relationships OPTIONAL MATCH (s:Site { short_form: primary.self_xref }) WITH CASE WHEN s IS NULL THEN [] ELSE COLLECT({ link_base: s.link_base, accession: coalesce(primary.short_form, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) END AS self_xref, primary, parents, relationships, related_individuals OPTIONAL MATCH (s:Site)<-[dbx:hasDbXref]-(primary) WITH CASE WHEN s IS NULL THEN self_xref ELSE COLLECT({ link_base: s.link_base, accession: coalesce(dbx.accession, ''), link_text: primary.label + ' on ' + s.label, site: { short_form: s.short_form, label: coalesce(s.label,''), iri: s.iri, types: labels(s) } , icon: coalesce(s.link_icon_url, ''), link_postfix: coalesce(s.link_postfix, '')}) + self_xref END AS xrefs,primary,parents,relationships,related_individuals OPTIONAL MATCH (primary)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, parents, relationships, related_individuals, xrefs , i limit 5 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) } , channel_image: { channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel) } , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique) } ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template) } , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat) } ,image_folder: irw.folder, index: coalesce(irw.index, []) + [] }} }) END AS anatomy_channel_image ,primary,parents,relationships,related_individuals,xrefs OPTIONAL MATCH (primary)-[rp:has_reference { typ: 'syn'}]->(p:pub) 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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } , synonym: { label: coalesce(rp.synonym, ''), scope: coalesce(rp.scope, ''), type: coalesce(rp.cat,'') } }) END AS pub_syn,primary,parents,relationships,related_individuals,xrefs,anatomy_channel_image OPTIONAL MATCH (primary)-[rp:has_reference { typ: 'def'}]->(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) } , PubMed: coalesce(p.PMID, ''), FlyBase: coalesce(p.FlyBase, ''), DOI: coalesce(p.DOI, '') } ) END AS def_pubs,primary,parents,relationships,related_individuals,xrefs,anatomy_channel_image,pub_syn OPTIONAL MATCH (:Class { label: 'intersectional expression pattern'})<-[:SUBCLASSOF]-(primary)<-[ar:part_of]-(anoni:Individual)-[:INSTANCEOF]->(n:Neuron) WITH COLLECT({ short_form: n.short_form, label: coalesce(n.label,''), iri: n.iri, types: labels(n) } ) as target_neurons,primary,parents,relationships,related_individuals,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) } , description : coalesce(primary.description, []), comment : coalesce(primary.`annotation-comment`, []) } AS term, 'Get JSON for Split Class' AS query, '2e501bd' AS version , parents, relationships, related_individuals, 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" }"> From 87aa14f781ecd0f2f33ef9f116a6e57e9e06a890 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Mon, 2 Dec 2019 14:00:00 +0000 Subject: [PATCH 20/25] fixed issues with delete and select of instances --- components/VFBMain.js | 2 +- components/interface/TreeWidget.js | 117 ++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/components/VFBMain.js b/components/VFBMain.js index 09abb64ed..8c8a2458b 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -788,7 +788,7 @@ export default class VFBMain extends React.Component { instance={this.instanceOnFocus} size={{ height: _height, width: _width }} ref={ref => this.treeBrowserReference = ref} - selectionHandler={this.addVfbId}/> + selectionHandler={this.addVfbId} /> ); } } diff --git a/components/interface/TreeWidget.js b/components/interface/TreeWidget.js index 7e1eb7068..7beb9cf2e 100644 --- a/components/interface/TreeWidget.js +++ b/components/interface/TreeWidget.js @@ -25,6 +25,7 @@ export default class TreeWidget extends React.Component { dataTree: undefined, root: undefined, loading: false, + edges: undefined, nodes: undefined, nodeSelected: undefined, displayColorPicker: false, @@ -44,6 +45,7 @@ export default class TreeWidget extends React.Component { this.findChildren = this.findChildren.bind(this); this.searchChildren = this.searchChildren.bind(this); this.insertChildren = this.insertChildren.bind(this); + this.updateSubtitle = this.updateSubtitle.bind(this); this.monitorMouseClick = this.monitorMouseClick.bind(this); this.defaultComparator = this.defaultComparator.bind(this); this.convertDataForTree = this.convertDataForTree.bind(this); @@ -288,8 +290,6 @@ export default class TreeWidget extends React.Component { var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; if (node.instanceId.indexOf("VFB_") > -1) { child.instanceId = node.instanceId; - node.subtitle = child.subtitle; - // child.subtitle = child.subtitle + " " + node.instanceId; uniqNodes.splice(j, 1); } } @@ -298,13 +298,13 @@ export default class TreeWidget extends React.Component { var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; if (node.instanceId.indexOf("VFB_") > -1) { child.instanceId = node.instanceId; - node.subtitle = child.subtitle; } else { child.children.push({ title: node.title, subtitle: node.instanceId, description: node.info, instanceId: node.instanceId, + classId: node.instanceId, id: node.id, showColorPicker: false, children: [] @@ -324,6 +324,7 @@ export default class TreeWidget extends React.Component { subtitle: nodes[i].instanceId, description: nodes[i].info, instanceId: nodes[i].instanceId, + classId: nodes[i].instanceId, id: nodes[i].id, showColorPicker: false, children: [] @@ -395,10 +396,11 @@ export default class TreeWidget extends React.Component { var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); var treeData = this.convertDataForTree(nodes, edges, vertix); this.setState({ + loading: false, instance: { id: instance }, dataTree: treeData, root: vertix, - loading: false, + edges: edges, nodes: nodes, nodeSelected: (this.props.instance === undefined ? treeData[0] @@ -437,24 +439,65 @@ export default class TreeWidget extends React.Component { } monitorMouseClick (e) { - // event handler to monitor when we click outside the color picker and close it. - if (!(this.colorPickerContainer !== undefined && this.colorPickerContainer !== null && this.colorPickerContainer.contains(e.target))) { + const clickCoord = { + INSIDE: 'inside', + OUTSIDE: 'outside', + PICKER_PRESENT: 'picker_present', + NODE_PRESENT: 'node_present' + }; + + let clickCondition = undefined; + if (this.colorPickerContainer !== undefined && this.colorPickerContainer !== null) { + clickCondition = clickCoord.PICKER_PRESENT; + if (!this.colorPickerContainer.contains(e.target)) { + clickCondition = clickCoord.OUTSIDE; + } + } + + switch (clickCondition) { + case clickCoord.OUTSIDE: if (this.nodeWithColorPicker !== undefined) { this.nodeWithColorPicker.showColorPicker = false; this.nodeWithColorPicker = undefined; } this.colorPickerContainer = undefined; this.setState({ displayColorPicker: false }); + break; + default: + console.log('mouse click function in the tree, just clicked this if you need'); + console.log(clickCondition); } + + /* + * event handler to monitor when we click outside the color picker and close it. + * if (!(this.colorPickerContainer !== undefined && this.colorPickerContainer !== null && this.colorPickerContainer.contains(e.target))) { + * if (this.nodeWithColorPicker !== undefined) { + * this.nodeWithColorPicker.showColorPicker = false; + * this.nodeWithColorPicker = undefined; + * } + */ + + /* + * this.colorPickerContainer = undefined; + * this.setState({ displayColorPicker: false }); + * } + */ } getButtons (rowInfo) { // As per name, provided by the react-sortable-tree api, we use this to attach to each node custom buttons var buttons = []; var fillCondition = "unknown"; + var instanceLoaded = false; if (rowInfo.node.instanceId.indexOf("VFB_") > -1) { fillCondition = "3dAvailable"; - if (Instances[rowInfo.node.instanceId] === undefined) { + for (var i = 1; i < Instances.length; i++) { + if (Instances[i].id !== undefined && Instances[i].id === rowInfo.node.instanceId) { + instanceLoaded = true; + break; + } + } + if (!instanceLoaded) { fillCondition = "3dToLoad"; } else { if ((typeof Instances[rowInfo.node.instanceId].isVisible !== "undefined") && (Instances[rowInfo.node.instanceId].isVisible())) { @@ -471,7 +514,7 @@ export default class TreeWidget extends React.Component { aria-hidden="true" onClick={ e => { e.stopPropagation(); - rowInfo.node.subtitle = rowInfo.node.instanceId; + // rowInfo.node.subtitle = rowInfo.node.instanceId; this.props.selectionHandler(rowInfo.node.instanceId); this.setState({ nodeSelected: rowInfo.node }); }} />); @@ -560,13 +603,24 @@ export default class TreeWidget extends React.Component {
{rowInfo.node.description}
)}>
{ e.stopPropagation(); this.colorPickerContainer = undefined; - this.props.selectionHandler(rowInfo.node.subtitle); + let instanceFound = false; + for (let i = 0; i < Instances.length; i++) { + if (Instances[i].getId() === rowInfo.node.instanceId) { + instanceFound = true; + break; + } + } + if (instanceFound && typeof Instances[rowInfo.node.instanceId].isVisible === "function") { + this.props.selectionHandler(rowInfo.node.instanceId); + } else { + this.props.selectionHandler(rowInfo.node.classId); + } this.setState({ nodeSelected: rowInfo.node }); }}> {rowInfo.node.title} @@ -577,6 +631,21 @@ export default class TreeWidget extends React.Component { return title; } + updateSubtitle (tree, idSelected) { + var node = undefined; + if (tree.length !== undefined) { + node = tree[0]; + } else { + node = tree; + } + if (node.instanceId === idSelected || node.classId === idSelected) { + node.subtitle = idSelected; + } + for (let i = 0; i < node.children.length; i++) { + this.updateSubtitle(node.children[i], idSelected); + } + } + componentWillMount () { if (window.templateID !== undefined) { this.initTree(window.templateID); @@ -591,7 +660,33 @@ export default class TreeWidget extends React.Component { var that = this; document.addEventListener('mousedown', this.monitorMouseClick, false); GEPPETTO.on(GEPPETTO.Events.Select, function (instance) { - that.setState({ displayColorPicker: false }); + var innerInstance = undefined; + if (instance.getParent() !== null) { + innerInstance = instance.getParent(); + } else { + innerInstance = instance; + } + var newId = innerInstance.getId(); + var newInstance = { + instanceId: { newId }, + subtitle: { newId }, + classId: { newId } + }; + var treeData = that.state.dataTree; + that.updateSubtitle(treeData, newId); + that.setState({ + dataTree: treeData, + displayColorPicker: false, + instance: { newInstance } + }); + }); + + GEPPETTO.on(GEPPETTO.Events.Instance_deleted, function (parameters) { + console.log('paramerters are:'); + console.log(parameters); + if (Instances[parameters] !== undefined ) { + that.setState({ instance: undefined }); + } }); } From 08b444eaee8b8b46f4358a6b99f46534cd0d24b0 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 3 Dec 2019 13:47:26 +0000 Subject: [PATCH 21/25] adding split label colour --- css/VFBTermInfo.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/VFBTermInfo.less b/css/VFBTermInfo.less index 979414556..ccfaa0493 100644 --- a/css/VFBTermInfo.less +++ b/css/VFBTermInfo.less @@ -380,6 +380,10 @@ background-color:#534700 !important; } +.label.types>.label.label-Split { + background-color:#e012e3 !important; +} + .button-bar-vfbHistoryLinks-back { position:absolute; left:10px; From 5b0c9219eac06d30ac50d78332063e04c4e62271 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 3 Dec 2019 16:36:08 +0000 Subject: [PATCH 22/25] ensuring log passed is less than 1000 characters --- components/interface/VFBToolBar.js | 44 ++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/components/interface/VFBToolBar.js b/components/interface/VFBToolBar.js index 2196eb0e7..6038f5860 100644 --- a/components/interface/VFBToolBar.js +++ b/components/interface/VFBToolBar.js @@ -117,19 +117,19 @@ export default class VFBToolBar extends React.Component { } else if ((verOffset = nAgt.indexOf("MSIE")) != -1) { // In MSIE, the true version is after "MSIE" in userAgent browserName = "Microsoft Internet Explorer"; fullVersion = nAgt.substring(verOffset + 5); - } else if ((verOffset = nAgt.indexOf("Chrome")) != -1) { // In Chrome, the true version is after "Chrome" + } else if ((verOffset = nAgt.indexOf("Chrome")) != -1) { // In Chrome, the true version is after "Chrome" browserName = "Chrome"; fullVersion = nAgt.substring(verOffset + 7); - } else if ((verOffset = nAgt.indexOf("Safari")) != -1) { // In Safari, the true version is after "Safari" or after "Version" + } else if ((verOffset = nAgt.indexOf("Safari")) != -1) { // In Safari, the true version is after "Safari" or after "Version" browserName = "Safari"; fullVersion = nAgt.substring(verOffset + 7); if ((verOffset = nAgt.indexOf("Version")) != -1) { fullVersion = nAgt.substring(verOffset + 8); } - } else if ((verOffset = nAgt.indexOf("Firefox")) != -1) { // In Firefox, the true version is after "Firefox" + } else if ((verOffset = nAgt.indexOf("Firefox")) != -1) { // In Firefox, the true version is after "Firefox" browserName = "Firefox"; fullVersion = nAgt.substring(verOffset + 8); - } else if ( (nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/')) ) { // In most other browsers, "name/version" is at the end of userAgent + } else if ( (nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/')) ) { // In most other browsers, "name/version" is at the end of userAgent browserName = nAgt.substring(nameOffset,verOffset); fullVersion = nAgt.substring(verOffset + 1); if (browserName.toLowerCase() == browserName.toUpperCase()) { @@ -145,10 +145,32 @@ export default class VFBToolBar extends React.Component { } majorVersion = parseInt('' + fullVersion,10); if (isNaN(majorVersion)) { - fullVersion = '' + parseFloat(navigator.appVersion); + fullVersion = '' + parseFloat(navigator.appVersion); majorVersion = parseInt(navigator.appVersion,10); } - + // return as much of the log up to the last 10 events < 1000 characters: + logLength = -10; + 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 < 0) { + logLength += 1; + limitedLog = window.console.logs.slice(logLength).join('%0A').replace( + /\&/g,escape('&') + ).replace( + /\#/g,escape('#') + ).replace( + /\-/g,'%2D' + ).replace( + /\+/g,'%2B' + ); + } this.props.htmlOutputHandler( htmlContent.replace( /\$URL\$/g,window.location.href.replace( @@ -165,15 +187,7 @@ export default class VFBToolBar extends React.Component { ).replace( /\$SCREEN\$/g, window.innerWidth + ',' + window.innerHeight ).replace( - /\$LOG\$/g, window.console.logs.slice(-10).join('%0A').replace( - /\&/g,escape('&') - ).replace( - /\#/g,escape('#') - ).replace( - /\-/g,'%2D' - ).replace( - /\+/g,'%2B' - ) + /\$LOG\$/g, limitedLog ).replace( /\$COLOURLOG\$/g, window.console.logs.join('
').replace( /\&/g,escape('&') From 8de9f715d5d69bfc880e07ea546b2806cb28d794 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Tue, 3 Dec 2019 18:24:25 +0000 Subject: [PATCH 23/25] syntax fix --- components/interface/VFBToolBar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/interface/VFBToolBar.js b/components/interface/VFBToolBar.js index 6038f5860..cf236e62c 100644 --- a/components/interface/VFBToolBar.js +++ b/components/interface/VFBToolBar.js @@ -149,8 +149,8 @@ export default class VFBToolBar extends React.Component { majorVersion = parseInt(navigator.appVersion,10); } // return as much of the log up to the last 10 events < 1000 characters: - logLength = -10; - limitedLog = window.console.logs.slice(logLength).join('%0A').replace( + var logLength = -10; + var limitedLog = window.console.logs.slice(logLength).join('%0A').replace( /\&/g,escape('&') ).replace( /\#/g,escape('#') From f63e0d95dc6865445335a889febdb945e1090741 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Wed, 4 Dec 2019 16:28:42 +0000 Subject: [PATCH 24/25] re-work of the tree component to adopt the new query provided --- .../configuration/treeWidgetConfiguration.js | 22 +- components/interface/TreeWidget.js | 357 ++++-------------- components/interface/VFBTree/helper.js | 206 ++++++++++ 3 files changed, 295 insertions(+), 290 deletions(-) create mode 100644 components/interface/VFBTree/helper.js diff --git a/components/configuration/treeWidgetConfiguration.js b/components/configuration/treeWidgetConfiguration.js index 37a299831..f088829c2 100644 --- a/components/configuration/treeWidgetConfiguration.js +++ b/components/configuration/treeWidgetConfiguration.js @@ -3,14 +3,30 @@ var restPostConfig = { contentType: "application/json" }; +/* + * var treeCypherQuery = instance => ({ + * "statements": [ + * { + * "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'" + instance + "'})" + * + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + * + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" + * + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat:Class)) RETURN p,r,image", + * "resultDataContents": ["graph"] + * } + * ] + * }); + */ + var treeCypherQuery = instance => ({ "statements": [ { "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'" + instance + "'})" + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" - + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat:Class)) RETURN p,r,image", - "resultDataContents": ["graph"] + + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat)) " + + "RETURN collect(distinct { node_id: id(anat), short_form: anat.short_form, image: image.short_form })" + + " AS image_nodes, id(root) AS root, collect(p)", + "resultDataContents": ["row", "graph"] } ] }); @@ -19,4 +35,4 @@ var treeCypherQuery = instance => ({ module.exports = { restPostConfig, treeCypherQuery -}; \ No newline at end of file +}; diff --git a/components/interface/TreeWidget.js b/components/interface/TreeWidget.js index 7beb9cf2e..b1a4eb3ee 100644 --- a/components/interface/TreeWidget.js +++ b/components/interface/TreeWidget.js @@ -21,7 +21,7 @@ export default class TreeWidget extends React.Component { super(props); this.state = { - instance: undefined, + errors: undefined, dataTree: undefined, root: undefined, loading: false, @@ -34,22 +34,26 @@ export default class TreeWidget extends React.Component { this.initTree = this.initTree.bind(this); this.getNodes = this.getNodes.bind(this); - this.sortData = this.sortData.bind(this); this.restPost = this.restPost.bind(this); this.nodeClick = this.nodeClick.bind(this); this.updateTree = this.updateTree.bind(this); this.getButtons = this.getButtons.bind(this); this.selectNode = this.selectNode.bind(this); - this.convertEdges = this.convertEdges.bind(this); - this.convertNodes = this.convertNodes.bind(this); this.findChildren = this.findChildren.bind(this); - this.searchChildren = this.searchChildren.bind(this); this.insertChildren = this.insertChildren.bind(this); this.updateSubtitle = this.updateSubtitle.bind(this); this.monitorMouseClick = this.monitorMouseClick.bind(this); - this.defaultComparator = this.defaultComparator.bind(this); this.convertDataForTree = this.convertDataForTree.bind(this); - this.parseGraphResultData = this.parseGraphResultData.bind(this); + + this.isNumber = require('./VFBTree/helper').isNumber; + this.sortData = require('./VFBTree/helper').sortData; + this.findRoot = require('./VFBTree/helper').findRoot; + this.convertEdges = require('./VFBTree/helper').convertEdges; + this.convertNodes = require('./VFBTree/helper').convertNodes; + this.searchChildren = require('./VFBTree/helper').searchChildren; + this.defaultComparator = require('./VFBTree/helper').defaultComparator; + this.parseGraphResultData = require('./VFBTree/helper').parseGraphResultData; + this.buildDictClassToIndividual = require('./VFBTree/helper').buildDictClassToIndividual; this.theme = createMuiTheme({ overrides: { MuiTooltip: { tooltip: { fontSize: "12px" } } } }); this.AUTHORIZATION = "Basic " + btoa("neo4j:vfb"); @@ -67,13 +71,6 @@ export default class TreeWidget extends React.Component { this.nodeWithColorPicker = undefined; } - isNumber (variable) { - if (isNaN(variable)) { - return variable; - } else { - return parseFloat(variable); - } - } restPost (data) { var strData = JSON.stringify(data); return $.ajax({ @@ -89,175 +86,6 @@ export default class TreeWidget extends React.Component { }); } - defaultComparator (a, b, key) { - if (this.isNumber(a[key]) < this.isNumber(b[key])) { - return -1; - } - if (this.isNumber(a[key]) > this.isNumber(b[key])) { - return 1; - } - return 0; - } - - sortData (unsortedArray, key, comparator) { - // Create a sortable array to return. - const sortedArray = [ ...unsortedArray ]; - - // Recursively sort sub-arrays. - const recursiveSort = (start, end) => { - - // If this sub-array is empty, it's sorted. - if (end - start < 1) { - return; - } - - const pivotValue = sortedArray[end]; - let splitIndex = start; - for (let i = start; i < end; i++) { - const sort = comparator(sortedArray[i], pivotValue, key); - - // This value is less than the pivot value. - if (sort === -1) { - - /* - * If the element just to the right of the split index, - * isn't this element, swap them. - */ - if (splitIndex !== i) { - const temp = sortedArray[splitIndex]; - sortedArray[splitIndex] = sortedArray[i]; - sortedArray[i] = temp; - } - - /* - * Move the split index to the right by one, - * denoting an increase in the less-than sub-array size. - */ - splitIndex++; - } - - /* - * Leave values that are greater than or equal to - * the pivot value where they are. - */ - } - - // Move the pivot value to between the split. - sortedArray[end] = sortedArray[splitIndex]; - sortedArray[splitIndex] = pivotValue; - - // Recursively sort the less-than and greater-than arrays. - recursiveSort(start, splitIndex - 1); - recursiveSort(splitIndex + 1, end); - }; - - // Sort the entire array. - recursiveSort(0, unsortedArray.length - 1); - return sortedArray; - } - - convertEdges (edges) { - var convertedEdges = []; - edges.forEach(function (edge) { - var relatType = edge.type.replace("_"," "); - if (relatType.indexOf("Related") > -1){ - relatType = edge.properties['label'].replace("_"," "); - } - if (convertedEdges.length > 0) { - - } else { - convertedEdges.push({ - from: edge.endNode, - to: edge.startNode, - label: relatType - }); - } - convertedEdges.push({ - from: edge.endNode, - to: edge.startNode, - label: relatType - }); - }); - return convertedEdges; - } - - convertNodes (nodes) { - var convertedNodes = []; - nodes.forEach(function (node) { - var nodeLabel = node.properties['short_form']; - var displayedLabel = node.properties['label']; - var description = node.properties['description'] - convertedNodes.push({ - title: displayedLabel, - subtitle: nodeLabel, - instanceId: nodeLabel, - info: description, - id: node.id, - }) - }); - return convertedNodes; - } - - parseGraphResultData (data) { - var nodes = {}, edges = {}; - data.results[0].data.forEach(function (row) { - row.graph.nodes.forEach(function (n) { - if (!nodes.hasOwnProperty(n.id)) { - nodes[n.id] = n; - } - }); - row.graph.relationships.forEach(function (r) { - if (!edges.hasOwnProperty(r.id)) { - edges[r.id] = r; - } - }); - }); - var nodesArray = [], edgesArray = []; - for (var p in nodes) { - if (nodes.hasOwnProperty(p)) { - nodesArray.push(nodes[p]); - } - } - for (var q in edges) { - if (edges.hasOwnProperty(q)) { - edgesArray.push(edges[q]) - } - } - return { nodes: nodesArray, edges: edgesArray }; - } - - searchChildren (array, key, target, label){ - // Define Start and End Index - let startIndex = 0; - let endIndex = array.length - 1; - - // While Start Index is less than or equal to End Index - while (startIndex <= endIndex) { - // Define Middle Index (This will change when comparing ) - let middleIndex = Math.floor((startIndex + endIndex) / 2); - // Compare Middle Index with Target for match - if (this.isNumber(array[middleIndex][key]) === this.isNumber(target[key])) { - // check for target relationship (label) - if (array[middleIndex].label === label){ - return middleIndex; - } else { - // move on if not matching target relationship (label) - startIndex = middleIndex + 1; - } - } - // Search Right Side Of Array - if (this.isNumber(target[key]) > this.isNumber(array[middleIndex][key])) { - startIndex = middleIndex + 1; - } - // Search Left Side Of Array - if (this.isNumber(target[key]) < this.isNumber(array[middleIndex][key])) { - endIndex = middleIndex - 1; - } - } - // If Target Is Not Found - return undefined; - } - findChildren (parent, key, familyList, label) { var childrenList = []; var childKey = this.searchChildren(familyList, key, parent, label); @@ -277,7 +105,7 @@ export default class TreeWidget extends React.Component { return childrenList; } - insertChildren (nodes, edges, child) { + insertChildren (nodes, edges, child, imagesMap) { var childrenList = this.findChildren({ from: child.id }, "from", edges, "part of"); // child.images = this.findChildren({ from: child.id }, "from", edges, "INSTANCEOF"); var nodesList = []; @@ -286,45 +114,34 @@ export default class TreeWidget extends React.Component { } var uniqNodes = [...new Set(nodesList)]; - for (var j = uniqNodes.length - 1; j >= 0 ; j--) { - var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; - if (node.instanceId.indexOf("VFB_") > -1) { - child.instanceId = node.instanceId; - uniqNodes.splice(j, 1); - } - } - for ( var j = 0; j < uniqNodes.length; j++) { var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; - if (node.instanceId.indexOf("VFB_") > -1) { - child.instanceId = node.instanceId; - } else { - child.children.push({ - title: node.title, - subtitle: node.instanceId, - description: node.info, - instanceId: node.instanceId, - classId: node.instanceId, - id: node.id, - showColorPicker: false, - children: [] - }); - this.insertChildren(nodes, edges, child.children[j]) - } + let imageId = node.instanceId; + child.children.push({ + title: node.title, + subtitle: node.classId, + description: node.info, + classId: node.classId, + instanceId: node.instanceId, + id: node.id, + showColorPicker: false, + children: [] + }); + this.insertChildren(nodes, edges, child.children[j], imagesMap) } } - convertDataForTree (nodes, edges, vertix) { + convertDataForTree (nodes, edges, vertix, imagesMap) { // This will create the data structure for the react-sortable-tree library, starting from the vertix node. var refinedDataTree = []; for ( var i = 0; i < nodes.length; i++ ) { if (vertix === nodes[i].id) { refinedDataTree.push({ title: nodes[i].title, - subtitle: nodes[i].instanceId, + subtitle: nodes[i].classId, description: nodes[i].info, + classId: nodes[i].classId, instanceId: nodes[i].instanceId, - classId: nodes[i].instanceId, id: nodes[i].id, showColorPicker: false, children: [] @@ -334,12 +151,31 @@ export default class TreeWidget extends React.Component { } var child = refinedDataTree[0]; // Once the vertix has been established we call insertChildren recursively on each child. - this.insertChildren(nodes, edges, child); + this.insertChildren(nodes, edges, child, imagesMap); return refinedDataTree; } + updateSubtitle (tree, idSelected) { + var node = undefined; + if (tree.length !== undefined) { + node = tree[0]; + } else { + node = tree; + } + if (node.instanceId === idSelected || node.classId === idSelected) { + node.subtitle = idSelected; + } + for (let i = 0; i < node.children.length; i++) { + this.updateSubtitle(node.children[i], idSelected); + } + } + selectNode (instance) { if (this.state.nodeSelected !== undefined && this.state.nodeSelected.instanceId !== instance.instanceId) { + /* + * var treeData = this.state.dataTree; + * this.updateSubtitle(treeData, instance.instanceId); + */ this.setState({ nodeSelected: instance }); } } @@ -355,9 +191,12 @@ export default class TreeWidget extends React.Component { } else { innerInstance = instance; } + var idToSearch = innerInstance.getId(); - if (this.state.instance !== undefined && innerInstance.id !== this.state.instance.id) { - if (innerInstance.id === window.templateID) { + if (this.state.nodeSelected !== undefined + && idToSearch !== this.state.nodeSelected.instanceId + && idToSearch !== this.state.nodeSelected.classId) { + if (idToSearch === window.templateID) { this.selectNode(this.state.dataTree[0]) return; } @@ -368,8 +207,7 @@ export default class TreeWidget extends React.Component { * in the searchQuery in the render to move the tree focus on this node */ while (this.state.nodes.length > i) { - var idToSearch = innerInstance.getId(); - if (idToSearch === this.state.nodes[i]["instanceId"]) { + if (idToSearch === this.state.nodes[i]["instanceId"] || idToSearch === this.state.nodes[i]["classId"]) { node.push(i); break; } @@ -383,21 +221,28 @@ export default class TreeWidget extends React.Component { initTree (instance) { // This function is the core and starting point of the component itself + var that = this; this.setState({ loading: true }); this.restPost(treeCypherQuery(instance)).done(data => { /* * we take the data provided by the cypher query and consume the until we obtain the treeData that can be given * to the react-sortable-tree since it understands this data structure */ + if (data.errors.length > 0) { + console.log("-- ERROR TREE COMPONENT --"); + console.log(data.errors); + this.setState({ errors: "Error retrieving the data - check the console for additional information" }); + } + if (data.results[0].data.length > 0) { var dataTree = this.parseGraphResultData(data); - var vertix = this.findRoot(data.results[0].data[0].graph.nodes); - var nodes = this.sortData(this.convertNodes(dataTree.nodes), "id", this.defaultComparator); + var vertix = this.findRoot(data); + var imagesMap = this.buildDictClassToIndividual(data); + var nodes = this.sortData(this.convertNodes(dataTree.nodes, imagesMap), "id", this.defaultComparator); var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); - var treeData = this.convertDataForTree(nodes, edges, vertix); + var treeData = this.convertDataForTree(nodes, edges, vertix, imagesMap); this.setState({ loading: false, - instance: { id: instance }, dataTree: treeData, root: vertix, edges: edges, @@ -415,7 +260,6 @@ export default class TreeWidget extends React.Component { children: [] }]; this.setState({ - instance: { id: instance }, dataTree: treeData, root: undefined, loading: false @@ -424,18 +268,10 @@ export default class TreeWidget extends React.Component { }); } - findRoot (nodes) { - let min = nodes[0].id; - for ( let i = 1; i < nodes.length; i++) { - if (nodes[i].id < min) { - min = nodes[i].id; - } - } - return min; - } - nodeClick (event, rowInfo) { - this.selectNode(rowInfo.node); + if (event.target.getAttribute("type") !== "button" && (event.target.getAttribute("aria-label") !== "Collapse" || event.target.getAttribute("aria-label") !== "Expand")) { + this.selectNode(rowInfo.node); + } } monitorMouseClick (e) { @@ -463,25 +299,7 @@ export default class TreeWidget extends React.Component { this.colorPickerContainer = undefined; this.setState({ displayColorPicker: false }); break; - default: - console.log('mouse click function in the tree, just clicked this if you need'); - console.log(clickCondition); } - - /* - * event handler to monitor when we click outside the color picker and close it. - * if (!(this.colorPickerContainer !== undefined && this.colorPickerContainer !== null && this.colorPickerContainer.contains(e.target))) { - * if (this.nodeWithColorPicker !== undefined) { - * this.nodeWithColorPicker.showColorPicker = false; - * this.nodeWithColorPicker = undefined; - * } - */ - - /* - * this.colorPickerContainer = undefined; - * this.setState({ displayColorPicker: false }); - * } - */ } getButtons (rowInfo) { @@ -603,7 +421,7 @@ export default class TreeWidget extends React.Component {
{rowInfo.node.description}
)}>
{ @@ -631,21 +449,6 @@ export default class TreeWidget extends React.Component { return title; } - updateSubtitle (tree, idSelected) { - var node = undefined; - if (tree.length !== undefined) { - node = tree[0]; - } else { - node = tree; - } - if (node.instanceId === idSelected || node.classId === idSelected) { - node.subtitle = idSelected; - } - for (let i = 0; i < node.children.length; i++) { - this.updateSubtitle(node.children[i], idSelected); - } - } - componentWillMount () { if (window.templateID !== undefined) { this.initTree(window.templateID); @@ -660,32 +463,12 @@ export default class TreeWidget extends React.Component { var that = this; document.addEventListener('mousedown', this.monitorMouseClick, false); GEPPETTO.on(GEPPETTO.Events.Select, function (instance) { - var innerInstance = undefined; - if (instance.getParent() !== null) { - innerInstance = instance.getParent(); - } else { - innerInstance = instance; - } - var newId = innerInstance.getId(); - var newInstance = { - instanceId: { newId }, - subtitle: { newId }, - classId: { newId } - }; - var treeData = that.state.dataTree; - that.updateSubtitle(treeData, newId); - that.setState({ - dataTree: treeData, - displayColorPicker: false, - instance: { newInstance } - }); + that.updateTree(instance); }); GEPPETTO.on(GEPPETTO.Events.Instance_deleted, function (parameters) { - console.log('paramerters are:'); - console.log(parameters); if (Instances[parameters] !== undefined ) { - that.setState({ instance: undefined }); + that.setState({ nodeSelected: undefined }); } }); } diff --git a/components/interface/VFBTree/helper.js b/components/interface/VFBTree/helper.js new file mode 100644 index 000000000..084051717 --- /dev/null +++ b/components/interface/VFBTree/helper.js @@ -0,0 +1,206 @@ +/* eslint-disable no-prototype-builtins */ + +const parseGraphResultData = data => { + var nodes = {}, edges = {}; + data.results[0].data.forEach(function (row) { + row.graph.nodes.forEach(function (n) { + if (!nodes.hasOwnProperty(n.id)) { + nodes[n.id] = n; + } + }); + row.graph.relationships.forEach(function (r) { + if (!edges.hasOwnProperty(r.id)) { + edges[r.id] = r; + } + }); + }); + var nodesArray = [], edgesArray = []; + for (var p in nodes) { + if (nodes.hasOwnProperty(p)) { + nodesArray.push(nodes[p]); + } + } + for (var q in edges) { + if (edges.hasOwnProperty(q)) { + edgesArray.push(edges[q]) + } + } + return { nodes: nodesArray, edges: edgesArray }; +} + +const findRoot = data => { + let columns = data.results[0].columns; + let rootIndex = columns.indexOf("root"); + return data.results[0].data[0].row[rootIndex].toString(); +} + +const buildDictClassToIndividual = data => { + var dictionaryIndividuals = {}; + let columns = data.results[0].columns; + let imagesIndex = columns.indexOf("image_nodes"); + let nodes = data.results[0].data[0].row[imagesIndex]; + for (let i = 0; i < nodes.length; i++) { + dictionaryIndividuals[nodes[i].short_form] = { + id: nodes[i].node_id, + image: nodes[i].image + } + } + return dictionaryIndividuals; +}; + +const searchChildren = (array, key, target, label) => { + // Define Start and End Index + let startIndex = 0; + let endIndex = array.length - 1; + + // While Start Index is less than or equal to End Index + while (startIndex <= endIndex) { + // Define Middle Index (This will change when comparing ) + let middleIndex = Math.floor((startIndex + endIndex) / 2); + // 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){ + return middleIndex; + } else { + // move on if not matching target relationship (label) + startIndex = middleIndex + 1; + } + } + // Search Right Side Of Array + if (isNumber(target[key]) > isNumber(array[middleIndex][key])) { + startIndex = middleIndex + 1; + } + // Search Left Side Of Array + if (isNumber(target[key]) < isNumber(array[middleIndex][key])) { + endIndex = middleIndex - 1; + } + } + // If Target Is Not Found + return undefined; +} + +const sortData = (unsortedArray, key, comparator) => { + // Create a sortable array to return. + const sortedArray = [ ...unsortedArray ]; + // Recursively sort sub-arrays. + const recursiveSort = (start, end) => { + // If this sub-array is empty, it's sorted. + if (end - start < 1) { + return; + } + const pivotValue = sortedArray[end]; + let splitIndex = start; + for (let i = start; i < end; i++) { + const sort = comparator(sortedArray[i], pivotValue, key); + // This value is less than the pivot value. + if (sort === -1) { + /* + * If the element just to the right of the split index, + * isn't this element, swap them. + */ + if (splitIndex !== i) { + const temp = sortedArray[splitIndex]; + sortedArray[splitIndex] = sortedArray[i]; + sortedArray[i] = temp; + } + /* + * Move the split index to the right by one, + * denoting an increase in the less-than sub-array size. + */ + splitIndex++; + } + /* + * Leave values that are greater than or equal to + * the pivot value where they are. + */ + } + // Move the pivot value to between the split. + sortedArray[end] = sortedArray[splitIndex]; + sortedArray[splitIndex] = pivotValue; + // Recursively sort the less-than and greater-than arrays. + recursiveSort(start, splitIndex - 1); + recursiveSort(splitIndex + 1, end); + }; + // Sort the entire array. + recursiveSort(0, unsortedArray.length - 1); + return sortedArray; +} + + +const convertEdges = edges => { + var convertedEdges = []; + edges.forEach(function (edge) { + var relatType = edge.type.replace("_"," "); + if (relatType.indexOf("Related") > -1){ + relatType = edge.properties['label'].replace("_"," "); + } + if (convertedEdges.length > 0) { + + } else { + convertedEdges.push({ + from: edge.endNode, + to: edge.startNode, + label: relatType + }); + } + convertedEdges.push({ + from: edge.endNode, + to: edge.startNode, + label: relatType + }); + }); + return convertedEdges; +} + +const convertNodes = (nodes, imagesMap) => { + var convertedNodes = []; + nodes.forEach(function (node) { + var nodeLabel = node.properties['short_form']; + var nodeImage = nodeLabel; + if (imagesMap[nodeLabel] !== undefined && imagesMap[nodeLabel].id.toString() === node.id.toString()) { + nodeImage = imagesMap[nodeLabel].image; + } + var displayedLabel = node.properties['label']; + var description = node.properties['description'] + convertedNodes.push({ + title: displayedLabel, + subtitle: nodeLabel, + instanceId: nodeImage, + classId: nodeLabel, + info: description, + id: node.id, + }) + }); + return convertedNodes; +} + +const defaultComparator = (a, b, key) => { + if (isNumber(a[key]) < isNumber(b[key])) { + return -1; + } + if (isNumber(a[key]) > isNumber(b[key])) { + return 1; + } + return 0; +} + +const isNumber = variable => { + if (isNaN(variable)) { + return variable; + } else { + return parseFloat(variable); + } +} + +module.exports = { + isNumber, + findRoot, + sortData, + convertNodes, + convertEdges, + searchChildren, + defaultComparator, + parseGraphResultData, + buildDictClassToIndividual +}; From a8bbba449afb70d82f7795f188c82df00e3b80e3 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 6 Dec 2019 14:09:00 +0000 Subject: [PATCH 25/25] fix eye icon on load instance --- components/interface/TreeWidget.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/interface/TreeWidget.js b/components/interface/TreeWidget.js index b1a4eb3ee..814abbbac 100644 --- a/components/interface/TreeWidget.js +++ b/components/interface/TreeWidget.js @@ -421,7 +421,7 @@ export default class TreeWidget extends React.Component {
{rowInfo.node.description}
)}>
{ @@ -462,6 +462,7 @@ export default class TreeWidget extends React.Component { componentDidMount () { var that = this; document.addEventListener('mousedown', this.monitorMouseClick, false); + GEPPETTO.on(GEPPETTO.Events.Select, function (instance) { that.updateTree(instance); }); @@ -471,6 +472,10 @@ export default class TreeWidget extends React.Component { that.setState({ nodeSelected: undefined }); } }); + + GEPPETTO.on(GEPPETTO.Events.Instances_created, function () { + that.setState({ displayColorPicker: false }); + }); } render () {