diff --git a/Dockerfile b/Dockerfile index a82f3095b..d74eb171c 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.0.7-feature/1238 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..56ebdda25 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; @@ -1752,7 +1763,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..75d1d2952 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 = { @@ -120,5 +129,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..4ce0ff102 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", @@ -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" : "facets_annotation" }, + "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..e2206036f 100644 --- a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js +++ b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js @@ -253,6 +253,14 @@ var toolbarMenu = { parameters: ["circuitBrowserVisible"] } }, + { + label: "Download Contents", + icon: "fa fa-download", + action: { + handlerAction: "downloadContentsVisible", + parameters: [] + } + }, { label: "NBLAST Uploader", icon: "fa fa-upload", 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..20d778f5a 100644 --- a/components/interface/VFBCircuitBrowser/Controls.js +++ b/components/interface/VFBCircuitBrowser/Controls.js @@ -120,9 +120,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 +163,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,8 +185,10 @@ class AutocompleteResults extends Component { fullWidth freeSolo disableClearable + clearOnEscape disablePortal autoHighlight + clearOnBlur value={this.fieldLabel} id={this.props.index.toString()} ListboxProps={{ style: { maxHeight: "10rem" } }} @@ -267,6 +275,12 @@ 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(); } @@ -360,6 +374,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 +389,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); 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 ( + + + + {this.configuration.text.title} + + + { !this.state.downloadError ? ( + + + + {this.configuration.text.typesSubtitle} + + + + {Object.keys(this.configurationOptions).map(key => { + const option = this.configurationOptions[key]; + const labelId = `checkbox-list-secondary-label-${key}`; + return ( + + {`${option.tooltip}`} } classes={ { popper: "light" } } placement="top-start" arrow> + + + + {`${option.label}`} + + self.handleTypeSelection(key)} + checked={this.state.typesChecked.indexOf(key) !== -1} + inputProps={{ "aria-labelledby": labelId }} + disabled={this.state.downloading} + disableRipple + className={self.props.classes.checked} + id={option.id} + /> + + + + ); + })} + + {this.variables.length > 0 ? ( + <> + + + {this.configuration.text.variablesSubtitle} + + + + } + defaultExpanded={[ALL_INSTANCES.id]} + defaultExpandIcon={} + > + e.stopPropagation()} + checked={self.state.allVariablesSelectedFlag} + onChange={event => + self.toggleVariable( + event.currentTarget.checked, + ALL_INSTANCES + ) + } + disableRipple + className={self.props.classes.checked} + id={ALL_INSTANCES.id} + /> + } + label={ + + {ALL_INSTANCES.name} + + } + key={ALL_INSTANCES.id} + /> + } + > + {this.variables.map(node => ( + e.stopPropagation()} + checked={self.state.selectedVariables.some( + item => item.id === node.id + )} + onChange={event => + self.toggleVariable( + event.currentTarget.checked, + node + ) + } + className={self.props.classes.checked} + id={"Download_" + node.id} + /> + } + label={ + + {node.name} + + } + key={node.id} + /> + } + /> + ))} + + + + + ) : ( + + {this.configuration.text.noVariablesSubtitle} + + )} + + + ) + : ( + + + + + + + {this.state.errorMessage} + + + + ) + } + + + + + + { !this.state.downloadError ? ( + + + + + + + + + ) + : ( + + + + + + ) + } + + + + ); + } +} + +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"); + }} /> + ({ - 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 ( - -

NBLAST Uploader

- - - Choose Template: + + + {self.configuration.text.selectTemplate} + - - - -
- --- NBLAST Text Updates --- - self.handlePermissionsCheck(event)} - /> - } - label={{PERMISSIONS}} + + + {self.configuration.text.addYourFile} + {self.state.files.length > 0 ? ( + + + + + + + + + {self.state.files[0].name} + + + + + + + + + + ) + : upload} + showAlerts={["error"]} + fullWidth + showPreviewsInDropzone={false} + dropzoneClass={classes.dropzoneArea} /> -
- { - self.state.uploading ? : null } - -
-
- ) + + + + + + + + {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 ? ( + + + + ) : ( + + + + ) + : + + + } + + + ); + } + + render () { + let self = this; + const { classes } = this.props; + + return ( + + + + {this.getTitleHead()} + + + { !self.state.error + ? !self.state.nblastEnabled ? this.getUploaderComponents() : this.getSuccessComponent() + : this.getErrorDialog() + } + + + + {self.getUploadActions()} + + + + ); } } -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/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..6754d8d7b 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(); } } diff --git a/tests/jest/vfb/batch3/term-context-tests.js b/tests/jest/vfb/batch3/term-context-tests.js index c1bd76d25..b0e3ef109 100644 --- a/tests/jest/vfb/batch3/term-context-tests.js +++ b/tests/jest/vfb/batch3/term-context-tests.js @@ -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(); } } 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..8ebbb926b 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