├── .gitignore ├── .jshintrc ├── .settings └── .gitignore ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── README.md ├── application.properties ├── doc └── CONTRIBUTING.md ├── grails-app ├── conf │ ├── BootStrap.groovy │ ├── BuildConfig.groovy │ ├── Config.groovy │ ├── DataSource.groovy │ ├── SmartRResources.groovy │ ├── UrlMappings.groovy │ └── spring │ │ └── resources.groovy ├── controllers │ └── smartR │ │ └── plugin │ │ ├── IpaConnectorController.groovy │ │ ├── SmartRController.groovy │ │ └── rest │ │ ├── RSessionController.groovy │ │ ├── ScriptExecutionController.groovy │ │ └── SmartRTestController.groovy ├── i18n │ └── messages.properties └── views │ ├── error.gsp │ ├── layouts │ ├── _boxplot.gsp │ ├── _correlation.gsp │ ├── _heatmap.gsp │ ├── _ipaconnector.gsp │ ├── _linegraph.gsp │ ├── _patientmapper.gsp │ ├── _volcanoplot.gsp │ └── smartR.gsp │ └── smartR │ └── index.gsp ├── h2_init.sql ├── karma.conf.js ├── package.json ├── scripts ├── _Events.groovy ├── _Install.groovy ├── _Uninstall.groovy └── _Upgrade.groovy ├── smartRGrailsPlugin.groovy ├── src └── groovy │ └── heim │ ├── SmartRExecutorService.groovy │ ├── SmartRRuntimeConstants.groovy │ ├── jobs │ ├── JobInstance.groovy │ └── JobInstanceImpl.groovy │ ├── rserve │ ├── GenericJavaObjectAsJsonRFunctionArg.groovy │ ├── RConnectionProvider.groovy │ ├── RFunctionArg.groovy │ ├── RScriptOutputManager.groovy │ ├── RScriptsSynchronizer.groovy │ ├── RServeSession.groovy │ └── RUtil.java │ ├── session │ ├── SessionContext.groovy │ ├── SessionFiles.groovy │ ├── SessionService.groovy │ ├── SmartRSessionScope.java │ ├── SmartRSessionScopeInterfaced.java │ └── SmartRSessionSpringScope.groovy │ └── tasks │ ├── AbstractTask.groovy │ ├── DataFetchTask.groovy │ ├── DataFetchTaskFactory.groovy │ ├── JobTasksService.groovy │ ├── RScriptExecutionTask.groovy │ ├── RScriptExecutionTaskFactory.groovy │ ├── Task.groovy │ ├── TaskFactory.groovy │ ├── TaskResult.groovy │ ├── TaskState.groovy │ └── TestTaskFactory.groovy ├── test ├── functional │ └── heim │ │ ├── BaseAPITestCase.groovy │ │ ├── DataFetchingTaskTests.groovy │ │ ├── RSessionControllerTests.groovy │ │ └── ScriptExecutionControllerTests.groovy ├── integration │ └── heim │ │ └── session │ │ └── SessionServiceTests.groovy ├── runit │ ├── run_tests.R │ └── tests │ │ ├── boxplot_summary.R │ │ ├── core_input.R │ │ ├── heatmap_downloadData.R │ │ ├── heatmap_preprocess.R │ │ ├── heatmap_run.R │ │ ├── heatmap_summary.R │ │ └── volcanoplot_run.R └── unit │ ├── heim │ └── RScriptOutputManagerSpec.groovy │ └── javascript │ ├── JavaScriptUnitTestKarmaSuite.groovy │ ├── ajaxServicesTests.js │ ├── fetchButtonDirectiveTests.js │ ├── runButtonDirectiveTests.js │ ├── smartRUtilsServiceTests.js │ └── testSetup.js └── web-app ├── HeimScripts ├── _core │ ├── index.R │ └── input.R ├── _func_test │ ├── autosourcing.R │ ├── sample.R │ ├── sourced.R │ └── sourcing.R ├── _shared_functions │ ├── Clustering │ │ └── heatmapUtils.R │ ├── GEX │ │ ├── DataFrameAndGEXmatrixUtils.R │ │ └── limmaUtils.R │ ├── Generic │ │ └── utils.R │ └── SummaryStatistics │ │ └── summaryStatistics.R ├── boxplot │ ├── run.R │ └── summary.R ├── correlation │ └── run.R ├── heatmap │ ├── downloadData.R │ ├── preprocess.R │ ├── run.R │ └── summary.R ├── ipaconnector │ └── run.R ├── linegraph │ ├── corrStats.R │ ├── lineStats.R │ └── run.R ├── patientmapper │ └── run.R └── volcanoplot │ └── run.R ├── WEB-INF ├── applicationContext.xml ├── sitemesh.xml └── tld │ ├── c.tld │ ├── fmt.tld │ ├── grails.tld │ └── spring.tld ├── css ├── boxplot.css ├── correlation.css ├── heatmap.css ├── ipaconnector.css ├── linegraph.css ├── ng-table.css ├── patientmappper.css ├── smartR.css ├── tooltip.css └── volcanoplot.css └── js ├── resource ├── angular-css.js ├── angular-mocks.js ├── angular-route.js ├── angular.js ├── crossfilter.js ├── d3-tip.js ├── d3.min.js ├── jquery-2.1.4.min.js ├── jquery-ui-1.11.4.min.js ├── jsrender.js ├── ng-table.js ├── plotly-latest.min.js └── sha256.js └── smartR ├── _angular ├── controllers │ ├── boxplot.js │ ├── correlation.js │ ├── heatmap.js │ ├── ipaconnector.js │ ├── linegraph.js │ ├── patientmapper.js │ └── volcanoplot.js ├── directives │ ├── biomarkerSelection.js │ ├── capturePlotButton.js │ ├── cohortSummaryInfo.js │ ├── conceptBox.js │ ├── downloadResultsButton.js │ ├── fetchButton.js │ ├── ipaApi.js │ ├── ngTranscludeReplace.js │ ├── preprocessButton.js │ ├── runButton.js │ ├── sortingCriteria.js │ ├── summaryStatistics.js │ ├── tabContainer.js │ ├── workflowControls.js │ ├── workflowTab.js │ └── workflowWarnings.js ├── services │ ├── commonWorkflowService.js │ ├── rServeService.js │ └── smartRUtils.js ├── smartRApp.js ├── templates │ ├── biomarkerSelection.html │ ├── boxplot.html │ ├── conceptBox.html │ ├── fetchButton.html │ ├── heatmap.html │ ├── ipaApi.html │ ├── linegraph.html │ ├── preprocessButton.html │ ├── runButton.html │ ├── sortingCriteria.html │ ├── summaryStatistics.html │ ├── tabContainer.html │ ├── workflowControls.html │ └── workflowWarnings.html └── viz │ ├── d3Correlation.js │ ├── d3Heatmap.js │ ├── d3Linegraph.js │ ├── d3Volcanoplot.js │ └── plotlyBoxplot.js └── smartR.js /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for Grails 1.2 and 1.3 2 | 3 | # web application files 4 | /web-app/WEB-INF/classes 5 | 6 | # default HSQL database files for production mode 7 | /prodDb.* 8 | 9 | # general HSQL database files 10 | *Db.properties 11 | *Db.script 12 | 13 | # R generated files 14 | .RData 15 | .Rapp.history 16 | .Rhistory 17 | 18 | # InteliJ generated files 19 | *.iml 20 | 21 | # logs 22 | /stacktrace.log 23 | /test/reports 24 | /logs 25 | 26 | # project release file 27 | /*.war 28 | 29 | # plugin release files 30 | /*.zip 31 | /*.zip.sha1 32 | /plugin.xml 33 | 34 | # older plugin install locations 35 | /plugins 36 | /web-app/plugins 37 | 38 | # "temporary" build files 39 | /target 40 | 41 | .DS_Store 42 | .idea 43 | /production 44 | *.DS_Store 45 | /SmartR.todo 46 | .tern-port 47 | /node_modules 48 | -------------------------------------------------------------------------------- /.settings/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php # to have .travis installing the php version we indicate 2 | sudo: false 3 | php: 4 | - 5.6 5 | addons: 6 | apt: 7 | packages: 8 | - libreadline-dev 9 | - clang 10 | - gfortran 11 | - libcairo2-dev 12 | cache: 13 | directories: 14 | - $HOME/.m2 15 | - $HOME/rserve 16 | - node_modules 17 | 18 | jdk: 19 | - oraclejdk7 20 | 21 | before_install: 22 | - git clone --depth 1 git://github.com/transmart/transmart-travis.git ~/ts-travis 23 | - git clone --depth 100 git://github.com/transmart/transmart-data.git ~/transmart-data 24 | - source ~/ts-travis/init.sh 25 | - source ~/ts-travis/grails_inline.sh 26 | - source ~/ts-travis/maven_dep.sh 27 | - source ~/ts-travis/rserve_install.sh 28 | 29 | install: 30 | - maybe_build_maven_dep $(travis_get_owner)/transmart-core-api core-api 31 | - maybe_make_inline_grails_dep $(travis_get_owner)/transmart-core-db core-db . transmart-core-db-tests 32 | - make -C ~/transmart-data/env ../vars 33 | - install_rserve ~/transmart-data 34 | - npm install 35 | 36 | before_script: 37 | - start_rserve 38 | 39 | script: 40 | - grails refresh-dependencies --non-interactive 41 | - grails test-app --non-interactive --stacktrace 42 | - "\"$RSERVE_LOCATION\"/bin/Rscript test/runit/run_tests.R" 43 | 44 | notifications: 45 | hipchat: 46 | rooms: 47 | secure: UKvpmZGc7nf/OGOY4e+mmvgaax/ezCTUNehuxhPqXSYwkec7OTY64znG6QN1rAP1YuWky1bdvTXiTXbr6STss4NlgMfmtK85GtxGWTFx6Xrq3L13YP9iG4ZaYQHGAzWat6CfKUmmkwhFgZCcg6n4msPW+YBpeGcFEglcOHRKcAxqx9S6vLzzIa+lsC2030bOL994IJrDgE48tyZtu8XhTpds737DsTgR0UW2WwH4PmyfdaxCvi4OmM/ZBrXBQgdK48R6RXmcP6WyvtLbxBVZCiZaf7TF06DLZffAJgxMhjJgczUzu/5kQpikodvCGxzYaKZ2cnRCKEVbF1YfFhMdjkUFalTdSmNhMUZapYb0Yt32wCR7inQDAVShJa0RGm3TEhtTWuFMlKCTFHgOV7jYc3wxQjDEFMdWWVvf9T0lXA5ULaBd0fbmBrP2Nj+9gtGOkVeu4X0ukXe10+COUj2oBzMR295+856LXHz9jp220VnCVU1VOxWnlyUvVO8qLnYlMyX5rukJ3XoR3dsff0h4qKKsPgDzkZDVucQreGci9a4sLpVuvH+o6P14oweZGU6gX5DSRUWvGOm1asZBSuwvmDIfIIrqbK63zfFQI0ClM6OaJl9K6qTL27BXUHOnx6jvchZET6ph9B+3v1eSAbLvEnNrfexfgNTwc5W3XX+AWdU= 48 | 49 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | 3 | Current Version 0.3 4 | - **Fix: Big datasets lead no longer to an unpextect error** 5 | - Fix: Feature ranking works correctly now 6 | - Fix: Added log2 to Heatmap tooltip 7 | - Fix: Cutoff in Heatmap will not work anymore with 2 rows or less 8 | - **Fix: Heatmap can now handle datasets containing duplicated probeset ids** 9 | - Improved Heatmap script performance 10 | - Fix: Correlation visualization behaves correctly now when resizing 11 | - Fix: The "Run Analysis" button will now enable again even if JS rendering fails 12 | 13 | Version 0.2 14 | - Fix: Session management should be more stable now 15 | - Fix: Large datasets should not cause SmartR to crash anymore 16 | - Fix: Heatmap - switched red and green color 17 | - Fix: Heatmap - use log2 values instead of raw values for ranking 18 | - Fix: Heatmap - improved contrast 19 | 20 | Version 0.1 21 | - Introduced versioning. No changelog available behind this point. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## If you use SmartR in your publication please cite it: 2 | https://doi.org/10.1093/bioinformatics/btx137 3 | 4 | ###SmartR is a grails plugin seeking to improve the visual analytics of the [tranSMART platform](https://github.com/transmart/transmartApp) by using recent web technologies such as [d3](http://d3js.org/). 5 | 6 | [![Heatmap Example](https://i.imgur.com/WGFV2kD.png)](https://youtu.be/Gg0AdYt77Cs) 7 | 8 | #### Installation 9 | ##### tranSMART 16.2, eTRIKS v3 and younger builds 10 | SmartR will be included by default. No modification necessary. 11 | 12 | ##### tranSMART 1.2.5 and 16.1 13 | 1. Add ```runtime ':smart-r:1.0-STABLE-SNAPSHOT'``` to **BuildConfig.groovy** in the **transmartApp** source code. 14 | 2. Compile a WAR file via ```grails war``` for deployment. 15 | 16 | ##### tranSMART 1.2.4 and older 17 | 1. Add ```runtime ':smart-r:1.0-STABLE-SNAPSHOT'``` to **BuildConfig.groovy** in the **transmartApp** source code. 18 | 2. Add the following code to **transmartApp/web-app/js/datasetExplorer/datasetExplorer.js** near [>>this location<< :](https://github.com/transmart/transmartApp/blob/release-1.2.4/web-app/js/datasetExplorer/datasetExplorer.js#L782) 19 | ```loadPlugin('smartR', "/SmartR/loadScripts", function () { 20 | resultsTabPanel.insert(4, smartRPanel); 21 | })``` 22 | 3. Compile a WAR file via ```grails war``` for deployment. 23 | 24 | #### Requirements 25 | - SmartR requires the following R packages: 26 | - [jsonlite](https://cran.r-project.org/web/packages/jsonlite/index.html) 27 | - [gplots](https://cran.r-project.org/web/packages/gplots/index.html) 28 | - [reshape2](https://cran.r-project.org/web/packages/reshape2/index.html) 29 | - [WGCNA](https://cran.r-project.org/web/packages/WGCNA/index.html) 30 | - [limma](https://bioconductor.org/packages/release/bioc/html/limma.html) 31 | - [zoo](https://cran.r-project.org/web/packages/zoo/index.html) 32 | 33 | #### Releases 34 | - https://github.com/transmart/SmartR/releases 35 | 36 | #### Wiki 37 | - https://wiki.transmartfoundation.org/display/transmartwiki/SmartR 38 | 39 | -------------------------------------------------------------------------------- /application.properties: -------------------------------------------------------------------------------- 1 | #Grails Metadata file 2 | #Tue Mar 03 14:09:30 CET 2015 3 | app.grails.version=2.3.11 4 | app.name=smart-r 5 | app.servlet.version=2.5 6 | -------------------------------------------------------------------------------- /grails-app/conf/BootStrap.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Janssen Research & Development, LLC. 3 | * 4 | * This file is part of REST API: transMART's plugin exposing tranSMART's 5 | * data via an HTTP-accessible RESTful API. 6 | * 7 | * This program is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or (at your 10 | * option) any later version, along with the following terms: 11 | * 12 | * 1. You may convey a work based on this program in accordance with 13 | * section 5, provided that you retain the above notices. 14 | * 2. You may convey verbatim copies of this program code as you receive 15 | * it, in any medium, provided that you retain the above notices. 16 | * 17 | * This program is distributed in the hope that it will be useful, but 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 20 | * Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License along 23 | * with this program. If not, see . 24 | */ 25 | 26 | import com.google.common.collect.ImmutableList 27 | import grails.util.Environment 28 | import grails.util.Holders 29 | import groovy.util.logging.Log4j 30 | 31 | @Log4j 32 | class BootStrap { 33 | 34 | final static String TEST_PHASE_CONFIGURER_CLASS_NAME = 35 | 'org.codehaus.groovy.grails.test.runner.phase.IntegrationTestPhaseConfigurer' 36 | 37 | def init = { servletContext -> 38 | if (Environment.currentEnvironment == Environment.TEST) { 39 | if (Class.forName(TEST_PHASE_CONFIGURER_CLASS_NAME).currentApplicationContext) { 40 | /* don't load the test data bundle for integration tests */ 41 | return 42 | } 43 | def testData = createTestData() 44 | log.info 'About to save test data' 45 | testData.saveAll() 46 | log.info 'Saved test data' 47 | 48 | def queryResults = testData.mrnaData.patients.collect { patient -> 49 | Class.forName('org.transmartproject.db.querytool.QueryResultData') 50 | .getMethod('createQueryResult', List) 51 | .invoke(null, [patient]) 52 | } 53 | 54 | // hibernate is not saving QtQueryMaster before QtQueryInstance if 55 | // the id of QtQueryMaster is explicitly assigned, hence we add 56 | // an endpoint to retrieve these result instance ids 57 | // (see SmartRTestController) 58 | 59 | Holders.applicationContext.registerSingleton('mrnaPatientSetIds', ArrayList) 60 | Holders.applicationContext.getBean('mrnaPatientSetIds').addAll( 61 | ImmutableList.copyOf(queryResults*.save()*.id)) 62 | log.info 'Created extra patient sets for testing' 63 | } 64 | } 65 | 66 | def createTestData() { 67 | Class clazz = Class.forName('org.transmartproject.db.TestData') 68 | clazz.getMethod('createDefault').invoke(null) //static method 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /grails-app/conf/BuildConfig.groovy: -------------------------------------------------------------------------------- 1 | def forkSettingsRun = [ 2 | minMemory: 1536, 3 | maxMemory: 4096, 4 | maxPerm : 384, 5 | debug : false, 6 | ] 7 | def forkSettingsOther = [ 8 | minMemory: 256, 9 | maxMemory: 1024, 10 | maxPerm : 384, 11 | debug : false, 12 | ] 13 | 14 | grails.project.fork = [ 15 | test : false, 16 | run : forkSettingsRun, 17 | war : false, 18 | console: forkSettingsOther] 19 | 20 | grails.project.class.dir = "target/classes" 21 | grails.project.test.class.dir = "target/test-classes" 22 | grails.project.test.reports.dir = "target/test-reports" 23 | 24 | def dm, dmClass 25 | try { 26 | dmClass = new GroovyClassLoader().parseClass( 27 | new File('../transmart-dev/SmartRDependencyManagement.groovy')) 28 | } catch (Exception e) { 29 | } 30 | if (dmClass) { 31 | dm = dmClass.newInstance() 32 | } 33 | 34 | grails.project.dependency.resolver = 'maven' 35 | grails.project.dependency.resolution = { 36 | log "warn" 37 | legacyResolve false 38 | inherits('global') {} 39 | if (!dm) { 40 | repositories { 41 | grailsCentral() 42 | mavenLocal() 43 | mavenCentral() 44 | mavenRepo 'https://repo.transmartfoundation.org/content/repositories/public/' 45 | mavenRepo 'https://repo.thehyve.nl/content/repositories/public/' 46 | } 47 | } else { 48 | dm.configureRepositories delegate 49 | } 50 | dependencies { 51 | // compile 'org.apache.ant:ant:1.9.6' 52 | compile 'net.sf.opencsv:opencsv:2.3' 53 | compile 'org.rosuda:Rserve:1.7.3' 54 | compile 'org.mapdb:mapdb:0.9.10' 55 | compile 'org.apache.commons:commons-lang3:3.4' 56 | 57 | /* serializable ImmutableMap only on guava 16 */ 58 | compile group: 'com.google.guava', name: 'guava', version: '16.0-dev-20140115-68c8348' 59 | compile 'org.transmartproject:transmart-core-api:16.2-SNAPSHOT' 60 | //test 'com.jayway.restassured:rest-assured:2.4.1' 61 | 62 | runtime 'org.javassist:javassist:3.16.1-GA' 63 | runtime 'com.ittm_solutions.ipacore:IpaApi:1.0.0' 64 | 65 | test('org.gmock:gmock:0.9.0-r435-hyve2') { 66 | transitive = false /* don't bring groovy-all */ 67 | export = false 68 | } 69 | } 70 | plugins { 71 | // for release support (e.g. to 'publish' plugin to nexus) 72 | build "org.grails.plugins:release:3.1.2" 73 | 74 | // FIXME: Advanced workflows gets buggy when updating resources plugin to 1.2.14 75 | runtime ':resources:1.2.1' 76 | 77 | compile ":sendfile:0.2" 78 | build ':tomcat:7.0.47', { 79 | export = false 80 | } 81 | test ':functional-test:2.0.0' 82 | test ':karma-test-runner:0.2.4' 83 | 84 | // core-db doesn't export hibernate as dep as it was builtin in 2.2.4 85 | //runtime ':hibernate:3.6.10.16' 86 | 87 | if (!dm) { 88 | runtime ':transmart-core:16.2-SNAPSHOT' 89 | 90 | test ':transmart-core:16.2-SNAPSHOT' 91 | test ':transmart-core-db-tests:16.2-SNAPSHOT' 92 | } else { 93 | dm.internalDependencies delegate 94 | } 95 | 96 | } 97 | } 98 | 99 | dm?.with { 100 | configureInternalPlugin 'runtime', 'transmart-core' 101 | configureInternalPlugin 'test', 'transmart-core' 102 | configureInternalPlugin 'test', 'transmart-core-db-tests' 103 | } 104 | 105 | dm?.inlineInternalDependencies grails, grailsSettings 106 | -------------------------------------------------------------------------------- /grails-app/conf/Config.groovy: -------------------------------------------------------------------------------- 1 | // configuration for plugin testing - will not be included in the plugin zip 2 | log4j = { 3 | 4 | error 'org.codehaus.groovy.grails.web.servlet', // controllers 5 | 'org.codehaus.groovy.grails.web.pages', // GSP 6 | 'org.codehaus.groovy.grails.web.sitemesh', // layouts 7 | 'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping 8 | 'org.codehaus.groovy.grails.web.mapping', // URL mapping 9 | 'org.codehaus.groovy.grails.commons', // core / classloading 10 | 'org.codehaus.groovy.grails.plugins', // plugins 11 | 'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration 12 | 'org.springframework', 13 | 'org.hibernate', 14 | 'net.sf.ehcache.hibernate' 15 | info 'org.transmartproject' 16 | debug 'heim' 17 | 18 | // uncomment to debug queries 19 | //trace 'org.hibernate.type' 20 | //debug 'org.hibernate.SQL' 21 | } 22 | 23 | grails.views.default.codec="none" // none, html, base64 24 | grails.views.gsp.encoding="UTF-8" 25 | 26 | grails.databinding.convertEmptyStringsToNull = false 27 | grails.databinding.trimStrings = false 28 | -------------------------------------------------------------------------------- /grails-app/conf/DataSource.groovy: -------------------------------------------------------------------------------- 1 | dataSource { 2 | pooled = true 3 | driverClassName = "org.h2.Driver" 4 | username = "sa" 5 | password = "" 6 | } 7 | hibernate { 8 | cache.use_second_level_cache = true 9 | cache.use_query_cache = false 10 | cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3 11 | // cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4 12 | } 13 | // environment specific settings 14 | environments { 15 | development { 16 | dataSource { 17 | dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', '' 18 | url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM './h2_init.sql'" 19 | } 20 | } 21 | test { 22 | dataSource { 23 | dbCreate = "update" 24 | url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM './h2_init.sql'" 25 | } 26 | } 27 | production { 28 | dataSource { 29 | dbCreate = "update" 30 | url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM './h2_init.sql'" 31 | properties { 32 | maxActive = -1 33 | minEvictableIdleTimeMillis = 1800000 34 | timeBetweenEvictionRunsMillis = 1800000 35 | numTestsPerEvictionRun = 3 36 | testOnBorrow = true 37 | testWhileIdle = true 38 | testOnReturn = false 39 | validationQuery = "SELECT 1" 40 | jdbcInterceptors = "ConnectionState" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /grails-app/conf/SmartRResources.groovy: -------------------------------------------------------------------------------- 1 | modules = { 2 | smartR_core { 3 | resource url: [plugin: 'smart-r', dir: 'css', file: 'smartR.css'] 4 | resource url: [plugin: 'smart-r', dir: 'css', file: 'tooltip.css'] 5 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'jquery-2.1.4.min.js'] 6 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'jquery-ui-1.11.4.min.js'] 7 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'd3.min.js'] 8 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'd3-tip.js'] 9 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'crossfilter.js'] 10 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'plotly-latest.min.js'] 11 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'jsrender.js'] 12 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'angular.js'] 13 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'angular-route.js'] 14 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'angular-css.js'] 15 | // IPA connector 16 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'sha256.js' ] 17 | resource url: [plugin: 'smart-r', dir: 'js/resource', file: 'ng-table.js'] 18 | resource url: [plugin: 'smart-r', dir: 'css', file: 'ng-table.css'] 19 | } 20 | 21 | smartR_angular_components { 22 | dependsOn 'smartR_core' 23 | // app initializer 24 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular', file: 'smartRApp.js'] 25 | // directives 26 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'conceptBox.js'] 27 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'tabContainer.js'] 28 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'workflowTab.js'] 29 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'fetchButton.js'] 30 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'preprocessButton.js'] 31 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'runButton.js'] 32 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'capturePlotButton.js'] 33 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'downloadResultsButton.js'] 34 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'ngTranscludeReplace.js'] 35 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'summaryStatistics.js'] 36 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'biomarkerSelection.js'] 37 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'sortingCriteria.js'] 38 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'cohortSummaryInfo.js'] 39 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'workflowWarnings.js'] 40 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'workflowControls.js'] 41 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/directives', file: 'ipaApi.js'] 42 | // services 43 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/services', file: 'rServeService.js'] 44 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/services', file: 'smartRUtils.js'] 45 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/services', file: 'commonWorkflowService.js'] 46 | } 47 | 48 | smartR_all { 49 | dependsOn 'smartR_angular_components' 50 | // heatmap 51 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'heatmap.js'] 52 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/viz', file: 'd3Heatmap.js'] 53 | // correlation 54 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'correlation.js'] 55 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/viz', file: 'd3Correlation.js'] 56 | // boxplot 57 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'boxplot.js'] 58 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/viz', file: 'plotlyBoxplot.js'] 59 | // volcano 60 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'volcanoplot.js'] 61 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/viz', file: 'd3Volcanoplot.js'] 62 | // linegraph 63 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'linegraph.js'] 64 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/viz', file: 'd3Linegraph.js'] 65 | // patientmapper 66 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'patientmapper.js'] 67 | // IPA connector 68 | resource url: [plugin: 'smart-r', dir: 'js/smartR/_angular/controllers', file: 'ipaconnector.js'] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /grails-app/conf/UrlMappings.groovy: -------------------------------------------------------------------------------- 1 | class UrlMappings { 2 | /** 3 | * This mappings file seems to be required when inlining core-db plugins 4 | * (as stated here https://github.com/transmart/transmart-rest-api/blob/v1.2.4/grails-app/conf/UrlMappings.groovy). 5 | * Without it, we get "Class not found loading Grails application: UrlMappings" 6 | * (https://travis-ci.org/transmart/SmartR/builds/163942859) 7 | */ 8 | 9 | static mappings = { 10 | // Following block seems to be required by functional tests (BaseAPITestCase.groovy) 11 | "/$controller/$action?/$id?(.${format})?"{ 12 | constraints { 13 | // apply constraints here 14 | } 15 | } 16 | 17 | // Removed the two following lines in order to not interfere with transmartApp url mappings - TRANSREL-134 18 | //"/"(view:"/index") 19 | //"500"(view:'/error') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /grails-app/conf/spring/resources.groovy: -------------------------------------------------------------------------------- 1 | import heim.tasks.TestTaskFactory 2 | 3 | // only relevant for testing/running standalone 4 | beans = { 5 | testTaskFactory TestTaskFactory 6 | } 7 | -------------------------------------------------------------------------------- /grails-app/controllers/smartR/plugin/rest/RSessionController.groovy: -------------------------------------------------------------------------------- 1 | package smartR.plugin.rest 2 | 3 | import heim.session.SessionService 4 | import org.transmartproject.core.exceptions.InvalidArgumentsException 5 | 6 | class RSessionController { 7 | 8 | static scope = 'prototype' 9 | 10 | static allowedMethods = [ 11 | create: 'POST', 12 | delete: 'POST', 13 | ] 14 | 15 | SessionService sessionService 16 | 17 | /** 18 | * Creates a new R session 19 | */ 20 | def create() { 21 | def json = request.JSON 22 | if (!json.workflow) { 23 | throw new InvalidArgumentsException('Workflow not given') 24 | } 25 | 26 | UUID sessionId = sessionService.createSession( 27 | null /* user not used now */, 28 | json.workflow) 29 | 30 | response.status = 201 31 | render(contentType: 'text/json') { 32 | [sessionId: sessionId.toString()] 33 | } 34 | } 35 | 36 | def touch() { 37 | sessionService.touchSession(sessionId) 38 | render status: 204 // no content 39 | } 40 | 41 | /** 42 | * Deletes an R session 43 | */ 44 | def delete() { 45 | sessionService.destroySession(sessionId) 46 | 47 | response.status = 202 /* session shutdown runs asynchronously */ 48 | render '' 49 | } 50 | 51 | /** 52 | * Delete files assoiated with this session 53 | */ 54 | def deleteFiles() { 55 | sessionService.removeAllFiles(sessionId) 56 | 57 | response.status = 204 58 | render '' 59 | } 60 | 61 | private UUID getSessionId() { 62 | def json = request.JSON 63 | if (!json.sessionId || !(json.sessionId instanceof String)) { 64 | throw new InvalidArgumentsException( 65 | "No session id provided or not string: ${json.sessionId}") 66 | } 67 | try { 68 | UUID.fromString(json.sessionId) 69 | } catch (IllegalArgumentException iae) { 70 | throw new InvalidArgumentsException( 71 | "Invalid session id (not a UUID): ${json.sessionId}") 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /grails-app/controllers/smartR/plugin/rest/ScriptExecutionController.groovy: -------------------------------------------------------------------------------- 1 | package smartR.plugin.rest 2 | 3 | import grails.validation.Validateable 4 | import heim.session.SessionService 5 | import heim.tasks.TaskResult 6 | import org.transmartproject.core.exceptions.InvalidArgumentsException 7 | 8 | class ScriptExecutionController { 9 | 10 | static scope = 'request' 11 | 12 | SessionService sessionService 13 | def sendFileService 14 | 15 | static allowedMethods = [ 16 | init : 'POST', 17 | run : 'POST', 18 | status: 'GET', 19 | downloadFile: 'GET', 20 | ] 21 | 22 | def run(RunCommand runCommand) { 23 | throwIfInvalid runCommand 24 | 25 | UUID executionId = 26 | sessionService.createTask( 27 | runCommand.arguments, 28 | runCommand.sessionId, 29 | runCommand.taskType,) 30 | 31 | render(contentType: 'text/json') { 32 | [executionId: executionId.toString()] 33 | } 34 | } 35 | 36 | def status(StatusCommand statusCommand) { 37 | throwIfInvalid statusCommand 38 | 39 | Map status = sessionService.getTaskData( 40 | statusCommand.sessionId, 41 | statusCommand.executionId, 42 | statusCommand.waitForCompletion) 43 | 44 | TaskResult res = status.result 45 | def resultValue = null 46 | if (res != null) { 47 | String exceptionMessage 48 | if (res.exception) { 49 | exceptionMessage = res.exception.getClass().toString() + 50 | ': ' + res.exception.message 51 | } 52 | resultValue = [ 53 | successful: res.successful, 54 | exception: exceptionMessage, 55 | artifacts: res.artifacts, 56 | ] 57 | } 58 | 59 | render(contentType: 'text/json') { 60 | [ 61 | state : status.state.toString(), 62 | result: resultValue 63 | ] 64 | } 65 | } 66 | 67 | def downloadFile(DownloadCommand downloadCommand) { 68 | throwIfInvalid downloadCommand 69 | 70 | File selectedFile = sessionService.getFile( 71 | downloadCommand.sessionId, 72 | downloadCommand.executionId, 73 | downloadCommand.filename) 74 | 75 | sendFileService.sendFile servletContext, request, response, selectedFile 76 | } 77 | 78 | private void throwIfInvalid(command) { 79 | if (command.hasErrors()) { 80 | List errorStrings = command.errors.allErrors.collect { 81 | g.message(error:it, encodeAs: 'raw') 82 | } 83 | throw new InvalidArgumentsException("Invalid input: $errorStrings") 84 | } 85 | } 86 | } 87 | 88 | @Validateable 89 | class StatusCommand { 90 | UUID sessionId 91 | UUID executionId 92 | boolean waitForCompletion 93 | 94 | static constraints = { 95 | sessionId nullable: false 96 | executionId nullable: false 97 | } 98 | } 99 | 100 | @Validateable 101 | class RunCommand { 102 | UUID sessionId 103 | Map arguments = [:] 104 | String taskType 105 | 106 | static constraints = { 107 | sessionId nullable: false 108 | taskType blank: false 109 | } 110 | } 111 | 112 | @Validateable 113 | class DownloadCommand { 114 | UUID sessionId 115 | UUID executionId 116 | String filename 117 | 118 | static constraints = { 119 | sessionId nullable: false 120 | executionId nullable: false 121 | filename blank: false 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /grails-app/controllers/smartR/plugin/rest/SmartRTestController.groovy: -------------------------------------------------------------------------------- 1 | package smartR.plugin.rest 2 | 3 | import grails.converters.JSON 4 | import grails.util.Environment 5 | import grails.util.Holders 6 | 7 | /** 8 | * It's not possible to create result instances with well-known ids, so we 9 | * expose these to the client with this controller (see BootStrap). 10 | */ 11 | class SmartRTestController { 12 | 13 | def resultInstanceIds() { 14 | if (Environment.currentEnvironment == Environment.TEST) { 15 | render([values: Holders.applicationContext.getBean('mrnaPatientSetIds')] as JSON) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /grails-app/i18n/messages.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transmart/SmartR/6ce267606ff9b99bb63a5a2db0e1b5303d50d206/grails-app/i18n/messages.properties -------------------------------------------------------------------------------- /grails-app/views/error.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else> 5 | 6 | 7 | 8 | 9 | SmartR An error has occurred 10 | exception '${exception}' 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /grails-app/views/layouts/_boxplot.gsp: -------------------------------------------------------------------------------- 1 | 2 | 86 | -------------------------------------------------------------------------------- /grails-app/views/layouts/_correlation.gsp: -------------------------------------------------------------------------------- 1 | 2 | 94 | -------------------------------------------------------------------------------- /grails-app/views/layouts/_linegraph.gsp: -------------------------------------------------------------------------------- 1 | 2 | 65 | -------------------------------------------------------------------------------- /grails-app/views/layouts/_patientmapper.gsp: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /grails-app/views/layouts/_volcanoplot.gsp: -------------------------------------------------------------------------------- 1 | 2 | 46 | -------------------------------------------------------------------------------- /grails-app/views/layouts/smartR.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /grails-app/views/smartR/index.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Loading...
4 |
5 |

SmartR - Dynamic Data Visualization and Interaction

6 |
7 | 8 |
9 | 15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /h2_init.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS I2B2METADATA; 2 | CREATE SCHEMA IF NOT EXISTS I2B2DEMODATA; 3 | CREATE SCHEMA IF NOT EXISTS DEAPP; 4 | CREATE SCHEMA IF NOT EXISTS BIOMART; 5 | CREATE SCHEMA IF NOT EXISTS SEARCHAPP; 6 | SET SCHEMA_SEARCH_PATH PUBLIC,I2B2METADATA,I2B2DEMODATA,BIOMART; 7 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ['jasmine'], 12 | 13 | plugins: [ 14 | 'karma-jasmine', 15 | 'karma-phantomjs-launcher', 16 | 'karma-ng-html2js-preprocessor', 17 | 'karma-remote-reporter' 18 | ], 19 | 20 | // list of files / patterns to load in the browser 21 | files: [ 22 | // libraries 23 | 'web-app/js/resource/jquery-2.1.4.min.js', 24 | 'web-app/js/resource/angular.js', 25 | 'web-app/js/resource/angular-route.js', 26 | 'web-app/js/resource/angular-css.js', 27 | 'web-app/js/resource/d3.min.js', 28 | 29 | // stuff for testing only 30 | 'web-app/js/resource/angular-mocks.js', 31 | 'test/unit/javascript/testSetup.js', 32 | // 'node_modules/karma-ng-html2js-preprocessor/lib/html2js.js', 33 | 34 | // application code 35 | 'web-app/js/smartR/_angular/services/smartRUtils.js', 36 | 'web-app/js/smartR/_angular/services/rServeService.js', 37 | 'web-app/js/smartR/_angular/directives/fetchButton.js', 38 | 'web-app/js/smartR/_angular/directives/runButton.js', 39 | 40 | // templates 41 | 'web-app/js/smartR/_angular/templates/*.html', 42 | 43 | // test files 44 | 'test/unit/javascript/**/*.js' 45 | // 'test/functional/javascript/**/*.js' 46 | ], 47 | 48 | // list of files to exclude 49 | exclude: [ 50 | ], 51 | 52 | // preprocess matching files before serving them to the browser 53 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 54 | preprocessors: { 55 | 'web-app/js/smartR/_angular/templates/*.html': 'ng-html2js' 56 | }, 57 | 58 | ngHtml2JsPreprocessor: { 59 | stripPrefix: 'web-app', 60 | moduleName: 'smartRTemplates' 61 | }, 62 | 63 | // test results reporter to use 64 | // possible values: 'dots', 'progress' 65 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 66 | reporters: ['progress', 'remote'], 67 | 68 | remoteReporter: { 69 | host: 'localhost', 70 | port: '9889' 71 | }, 72 | 73 | 74 | // web server port 75 | port: 9876, 76 | 77 | 78 | // enable / disable colors in the output (reporters and logs) 79 | colors: true, 80 | 81 | 82 | // level of logging 83 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 84 | logLevel: config.LOG_INFO, 85 | 86 | 87 | // enable / disable watching file and executing tests whenever any file changes 88 | autoWatch: true, 89 | 90 | 91 | // start these browsers 92 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 93 | browsers: ['PhantomJS'], 94 | 95 | 96 | // Continuous Integration mode 97 | // if true, Karma captures browsers, runs the tests and exits 98 | singleRun: true, 99 | 100 | // Concurrency level 101 | // how many browser should be started simultaneous 102 | concurrency: Infinity 103 | }); 104 | }; 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-r", 3 | "version": "1.0.0", 4 | "description": "![alt tag](http://i.imgur.com/8qltmqs.png)", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start karma.conf.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "repository": "https://github.com/sherzinger/SmartR", 15 | "devDependencies": { 16 | "jasmine-ajax": "^3.2.0", 17 | "jasmine-core": "^2.4.1", 18 | "jasmine-def": "^0.1.0", 19 | "jasmine-expect": "^2.0.0-beta2", 20 | "karma": "^0.13.19", 21 | "karma-cli": "^0.1.2", 22 | "karma-jasmine": "^0.3.6", 23 | "karma-phantomjs-launcher": "^0.2.3", 24 | "karma-remote-reporter": "^0.2.0", 25 | "phantomjs": "^2.0.0", 26 | "karma-ng-html2js-preprocessor": "^0.2.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scripts/_Events.groovy: -------------------------------------------------------------------------------- 1 | eventCreatePluginArchiveStart = { stagingDir -> 2 | event("BuildInfoAddPropertiesStart", [stagingDir]) 3 | writeProperties(getEnvProperties(), "${stagingDir}/application.properties") 4 | event("BuildInfoAddPropertiesEnd", [stagingDir]) 5 | } 6 | 7 | private void writeProperties(Map properties, String propertyFile) { 8 | Ant.propertyfile(file: propertyFile) { 9 | properties.each { k,v-> 10 | entry(key: k, value: v) 11 | } 12 | } 13 | } 14 | 15 | def getEnvProperties() { 16 | def environment = [:] 17 | Ant.antProject.properties.findAll({k,v-> k.startsWith('environment')}).each { k,v-> 18 | environment[k] = v 19 | } 20 | 21 | def properties = [:] 22 | properties['scm.version'] = getRevision()?.trim() ?: '-' 23 | properties['build.date'] = new Date().format('dd/MMM/yyyy; kk:mm:ss') 24 | properties['build.timezone'] = Calendar.getInstance().getTimeZone().getID() 25 | properties['build.java'] = System.getProperty('java.version') 26 | properties['build.groovy'] = GroovySystem.version 27 | 28 | //Add a selection of useful environment variables 29 | properties['env.os'] = environment['environment.OS'] ?: environment['environment.OSTYPE'] ?: '-' 30 | properties['env.username'] = environment['environment.USERNAME'] ?: environment['environment.USER'] ?: '-' 31 | properties['env.computer'] = environment['environment.COMPUTERNAME'] ?: environment['environment.HOSTNAME'] ?: '-' 32 | properties['env.proc.type'] = environment['environment.PROCESSOR_ARCHITECTURE'] ?: environment['environment.HOSTTYPE'] ?: '-' 33 | properties['env.proc.cores'] = environment['environment.NUMBER_OF_PROCESSORS'] ?: '-' 34 | 35 | return properties 36 | } 37 | 38 | def getRevision() { 39 | // client provided closure to determine revision 40 | def determineRevisionClosure = buildConfig.buildinfo.determineRevision 41 | if (determineRevisionClosure instanceof Closure) { 42 | return determineRevisionClosure() 43 | } 44 | 45 | // try to get revision from Hudson 46 | def scmVersion = Ant.antProject.properties."environment.SVN_REVISION" 47 | 48 | if (!scmVersion) { 49 | scmVersion = Ant.antProject.properties."environment.GIT_COMMIT" 50 | } 51 | 52 | // maybe a local git? 53 | if (!scmVersion) { 54 | try { 55 | def command = """git rev-parse HEAD""" 56 | def proc = command.execute() 57 | proc.waitFor() 58 | if (proc.exitValue() == 0) { 59 | scmVersion = proc.in.text 60 | } 61 | } catch (IOException e) { 62 | // oh well 63 | } 64 | } 65 | 66 | if (!scmVersion) { 67 | scmVersion = getEstimateRevisionFromGitFolder() 68 | } 69 | 70 | if (!scmVersion) { 71 | scmVersion = getRevisionFromSvnCli() 72 | } 73 | 74 | // if Hudson/Jenkins env variable not found, try file system (for SVN) 75 | if (!scmVersion) { 76 | File entries = new File(basedir, '.svn/entries') 77 | if (entries.exists() && entries.text.split('\n').length>3) { 78 | scmVersion = entries.text.split('\n')[3].trim() 79 | } 80 | } 81 | 82 | return scmVersion ?: '-' 83 | } 84 | 85 | private String getRevisionFromSvnCli() { 86 | try { 87 | def command = 'svn info --xml' 88 | def proc = command.execute() 89 | def out = new ByteArrayOutputStream() 90 | proc.consumeProcessOutput(out, null) //prevent blocking in Windows due to a full output buffer 91 | int exitVal = proc.waitFor() 92 | if (exitVal == 0) { 93 | def slurper = new XmlSlurper().parseText(out.toString()) 94 | return slurper.entry.@revision 95 | } 96 | } catch (ignore) { 97 | return null 98 | } 99 | } 100 | 101 | def getEstimateRevisionFromGitFolder() { 102 | try { 103 | //on system which do not have git in the PATH, try the file system 104 | //the head this might not always provide accurate commit hash 105 | def headFile = new File(".git/HEAD") 106 | def refsHeadPath = '' 107 | if (headFile.exists()) { 108 | def headContents = headFile.text.trim() 109 | refsHeadPath = headContents.split(':')[1].trim() 110 | def refsHeadFile = new File(".git/${refsHeadPath}") 111 | if (refsHeadFile.isFile()) { 112 | return refsHeadFile.text.trim() 113 | } 114 | } 115 | } catch (ignore) { 116 | return null 117 | } 118 | } -------------------------------------------------------------------------------- /scripts/_Install.groovy: -------------------------------------------------------------------------------- 1 | // 2 | // This script is executed by Grails after plugin was installed to project. 3 | // This script is a Gant script so you can use all special variables provided 4 | // by Gant (such as 'baseDir' which points on project base dir). You can 5 | // use 'ant' to access a global instance of AntBuilder 6 | // 7 | // For example you can create directory under project tree: 8 | // 9 | // ant.mkdir(dir:"${basedir}/grails-app/jobs") 10 | // 11 | -------------------------------------------------------------------------------- /scripts/_Uninstall.groovy: -------------------------------------------------------------------------------- 1 | // 2 | // This script is executed by Grails when the plugin is uninstalled from project. 3 | // Use this script if you intend to do any additional clean-up on uninstall, but 4 | // beware of messing up SVN directories! 5 | // 6 | -------------------------------------------------------------------------------- /scripts/_Upgrade.groovy: -------------------------------------------------------------------------------- 1 | // 2 | // This script is executed by Grails during application upgrade ('grails upgrade' 3 | // command). This script is a Gant script so you can use all special variables 4 | // provided by Gant (such as 'baseDir' which points on project base dir). You can 5 | // use 'ant' to access a global instance of AntBuilder 6 | // 7 | // For example you can create directory under project tree: 8 | // 9 | // ant.mkdir(dir:"${basedir}/grails-app/jobs") 10 | // 11 | -------------------------------------------------------------------------------- /smartRGrailsPlugin.groovy: -------------------------------------------------------------------------------- 1 | import grails.util.Environment 2 | import heim.rserve.RScriptsSynchronizer 3 | import org.codehaus.groovy.grails.plugins.GrailsPluginUtils 4 | import org.springframework.stereotype.Component 5 | import heim.SmartRRuntimeConstants 6 | 7 | class smartRGrailsPlugin { 8 | 9 | public static final String DEFAULT_REMOTE_RSCRIPTS_DIRECTORY = '/tmp/smart_r_scripts' 10 | public static final String TRANSMART_EXTENSIONS_REGISTRY_BEAN_NAME = 'transmartExtensionsRegistry' 11 | 12 | // the plugin version 13 | def version = "1.2-STABLE-SNAPSHOT" 14 | // the version or versions of Grails the plugin is designed for 15 | def grailsVersion = "2.3 > *" 16 | // resources that are excluded from plugin packaging 17 | def pluginExcludes = [ 18 | "grails-app/views/error.gsp" 19 | ] 20 | 21 | // TODO Fill in these fields 22 | def title = "SmartR Plugin" // Headline display name of the plugin 23 | def author = "" 24 | def authorEmail = "" 25 | def description = 26 | ''' 27 | Brief summary/description of the plugin. 28 | ''' 29 | 30 | // URL to the plugin's documentation 31 | def documentation = "" 32 | 33 | // Extra (optional) plugin metadata 34 | 35 | // License: one of 'APACHE', 'GPL2', 'GPL3' 36 | // def license = "APACHE" 37 | 38 | // Details of company behind the plugin (if there is one) 39 | // def organization = [ name: "My Company", url: "http://www.my-company.com/" ] 40 | 41 | 42 | // Any additional developers beyond the author specified above. 43 | // def developers = [ [ name: "Joe Bloggs", email: "joe@bloggs.net" ]] 44 | 45 | // Location of the plugin's issue tracker. 46 | // def issueManagement = [ system: "JIRA", url: "http://jira.grails.org/browse/GPMYPLUGIN" ] 47 | 48 | // Online location of the plugin's browseable source code. 49 | // def scm = [ url: "http://svn.codehaus.org/grails-plugins/" ] 50 | 51 | def doWithWebDescriptor = { xml -> 52 | // TODO Implement additions to web.xml (optional), this event occurs before 53 | } 54 | 55 | def doWithSpring = { 56 | xmlns context:"http://www.springframework.org/schema/context" 57 | 58 | context.'component-scan'('base-package': 'heim') { 59 | context.'include-filter'( 60 | type: 'annotation', 61 | expression: Component.canonicalName) 62 | } 63 | } 64 | 65 | def doWithApplicationContext = { ctx -> 66 | def config = application.config 67 | SmartRRuntimeConstants constants = ctx.getBean(SmartRRuntimeConstants) 68 | 69 | File smartRDir = GrailsPluginUtils.getPluginDirForName('smart-r')?.file 70 | if (!smartRDir) { 71 | String pluginPath = ctx.pluginManager.allPlugins.find { 72 | it.name == 'smartR' 73 | }.pluginPath 74 | 75 | smartRDir = ctx.getResource(pluginPath).file 76 | } else { 77 | smartRDir = new File(smartRDir, 'web-app') 78 | } 79 | if (!smartRDir) { 80 | throw new RuntimeException('Could not determine directory for ' + 81 | 'smart-r plugin') 82 | } 83 | 84 | constants.pluginScriptDirectory = new File(smartRDir.canonicalPath, 'HeimScripts') 85 | log.info("Directory for heim scripts is ${constants.pluginScriptDirectory}") 86 | 87 | if (!skipRScriptsTransfer(config)) { 88 | def remoteScriptDirectory = config.smartR.remoteScriptDirectory 89 | if (!remoteScriptDirectory) { 90 | remoteScriptDirectory = DEFAULT_REMOTE_RSCRIPTS_DIRECTORY 91 | } 92 | constants.remoteScriptDirectoryDir = remoteScriptDirectory 93 | log.info("Location for R scripts in the Rserve server is ${constants.remoteScriptDirectoryDir}") 94 | 95 | ctx.getBean(RScriptsSynchronizer).start() 96 | } else { 97 | log.info('Skipping copying of R script in development mode with local Rserve') 98 | constants.remoteScriptDirectoryDir = constants.pluginScriptDirectory.absoluteFile 99 | ctx.getBean(RScriptsSynchronizer).skip() 100 | } 101 | 102 | if (ctx.containsBean(TRANSMART_EXTENSIONS_REGISTRY_BEAN_NAME)) { 103 | ctx.getBean(TRANSMART_EXTENSIONS_REGISTRY_BEAN_NAME) 104 | .registerAnalysisTabExtension('smartR', '/SmartR/loadScripts', 'addSmartRPanel') 105 | } 106 | 107 | } 108 | 109 | private boolean skipRScriptsTransfer(config) { 110 | (!config.RModules.host || 111 | config.RModules.host in ['127.0.0.1', '::1', 'localhost']) && 112 | Environment.currentEnvironment == Environment.DEVELOPMENT && 113 | !config.smartR.alwaysCopyScripts 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/groovy/heim/SmartRExecutorService.groovy: -------------------------------------------------------------------------------- 1 | package heim 2 | 3 | import com.google.common.util.concurrent.ListeningExecutorService 4 | import com.google.common.util.concurrent.MoreExecutors 5 | import groovy.transform.TypeChecked 6 | import org.springframework.beans.factory.DisposableBean 7 | import org.springframework.stereotype.Component 8 | 9 | import java.util.concurrent.LinkedBlockingQueue 10 | import java.util.concurrent.ThreadFactory 11 | import java.util.concurrent.ThreadPoolExecutor 12 | import java.util.concurrent.TimeUnit 13 | import java.util.concurrent.atomic.AtomicInteger 14 | 15 | /** 16 | * Created by glopes on 09-10-2015. 17 | */ 18 | @Component 19 | @TypeChecked 20 | class SmartRExecutorService implements ListeningExecutorService, 21 | DisposableBean { 22 | 23 | public static final int CORE_POOL_SIZE = 3 24 | public static final int MAXIMUM_POOL_SIZE = 20 25 | public static final String SMART_RTHREAD_POOL_NAME = 'smartRThreadPool' 26 | // TODO: make configurable 27 | 28 | @Delegate 29 | ListeningExecutorService executorService 30 | 31 | SmartRExecutorService() { 32 | def pool = new ThreadPoolExecutor( 33 | CORE_POOL_SIZE, 34 | MAXIMUM_POOL_SIZE, 35 | 1, 36 | TimeUnit.MINUTES, 37 | new LinkedBlockingQueue()) 38 | pool.with { 39 | threadFactory = new SmartRThreadFactory() 40 | } 41 | executorService = MoreExecutors.listeningDecorator(pool) 42 | } 43 | 44 | void destroy() { 45 | executorService.shutdownNow() 46 | } 47 | 48 | static class SmartRThreadFactory implements ThreadFactory { 49 | private final ThreadGroup group 50 | private final AtomicInteger threadNumber = new AtomicInteger(1); 51 | 52 | SmartRThreadFactory() { 53 | SecurityManager s = System.securityManager 54 | group = s?.threadGroup ?: Thread.currentThread().threadGroup 55 | } 56 | 57 | public Thread newThread(Runnable r) { 58 | new Thread(group, r, 59 | SMART_RTHREAD_POOL_NAME + '-' + 60 | threadNumber.getAndIncrement(), 61 | 0).with { 62 | daemon = false 63 | priority = Thread.NORM_PRIORITY 64 | (Thread) it 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/groovy/heim/SmartRRuntimeConstants.groovy: -------------------------------------------------------------------------------- 1 | package heim 2 | 3 | import grails.util.Holders 4 | import org.codehaus.groovy.grails.commons.GrailsApplication 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.stereotype.Component 7 | 8 | @Component 9 | class SmartRRuntimeConstants { 10 | private static SmartRRuntimeConstants instance; 11 | 12 | public static SmartRRuntimeConstants getInstance() { 13 | if (instance == null) { 14 | synchronized (SmartRRuntimeConstants.class) { 15 | if (instance == null) { 16 | instance = new SmartRRuntimeConstants(); 17 | instance.grailsApplication = Holders.grailsApplication; // thanks to http://stackoverflow.com/a/24501325/535203 (Holders.applicationContext.getBean("smartRRuntimeConstants") and Holders.applicationContext.getBean(SmartRRuntimeConstants) were failing with "org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'smartRRuntimeConstants' is defined" 18 | } 19 | } 20 | } 21 | instance; 22 | } 23 | 24 | @Autowired 25 | private GrailsApplication grailsApplication 26 | 27 | File pluginScriptDirectory 28 | 29 | void setPluginScriptDirectory(File dir) { 30 | assert dir.isDirectory() 31 | this.pluginScriptDirectory = dir.absoluteFile 32 | } 33 | 34 | void setRemoteScriptDirectoryDir(Object dir) { 35 | grailsApplication.config.smartR.remoteScriptDirectory = dir as File 36 | remoteScriptDirectoryDir // for the side effects 37 | } 38 | 39 | /** 40 | * Where to copy the R scripts to and execute them from 41 | * (in the machine where Rserve is running). 42 | */ 43 | File getRemoteScriptDirectoryDir() { 44 | def dir = grailsApplication.config.smartR.remoteScriptDirectory as File 45 | if (!dir.absolute) { 46 | throw new RuntimeException("Invalid configuration: " + 47 | "smartR.remoteScriptDirectory should be an absolute path," + 48 | " got '$dir'") 49 | } 50 | dir 51 | } 52 | 53 | File getBaseDir() { 54 | def dir = grailsApplication.config.smartR.baseDir 55 | if (!dir) { 56 | dir = File.createTempDir("smartR", ".baseDir"); 57 | setBaseDir(dir) 58 | } 59 | dir as File 60 | } 61 | 62 | void setBaseDir(Object dir) { 63 | grailsApplication.config.smartR.baseDir = dir as File 64 | baseDir // for the side effects 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/groovy/heim/jobs/JobInstance.groovy: -------------------------------------------------------------------------------- 1 | package heim.jobs 2 | 3 | import heim.tasks.Task 4 | 5 | /** 6 | * Created by glopes on 13-10-2015. 7 | */ 8 | interface JobInstance { 9 | 10 | String getWorkflow() 11 | 12 | Task createTask(String name, Map arguments) 13 | } 14 | -------------------------------------------------------------------------------- /src/groovy/heim/jobs/JobInstanceImpl.groovy: -------------------------------------------------------------------------------- 1 | package heim.jobs 2 | 3 | import heim.session.SmartRSessionScopeInterfaced 4 | import heim.tasks.Task 5 | import heim.tasks.TaskFactory 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.core.OrderComparator 9 | import org.springframework.stereotype.Component 10 | import org.transmartproject.core.exceptions.InvalidArgumentsException 11 | 12 | import javax.annotation.PostConstruct 13 | 14 | /** 15 | * Created by glopes on 09-10-2015. 16 | */ 17 | @SmartRSessionScopeInterfaced 18 | @Component('jobInstance') 19 | class JobInstanceImpl implements JobInstance { 20 | 21 | @Autowired 22 | List taskFactories 23 | 24 | @Value('#{workflowType}') 25 | String workflow 26 | 27 | @PostConstruct 28 | void init() { 29 | // only Spring 4 orders the list automatically 30 | // Grails 2.3 still uses Spring 3 31 | taskFactories.sort(new OrderComparator()) 32 | } 33 | 34 | Task createTask(String name, Map arguments) { 35 | TaskFactory selectedTaskFactory = 36 | taskFactories.find { it.handles(name, arguments) } 37 | 38 | if (!selectedTaskFactory) { 39 | throw new InvalidArgumentsException("No task factory found for " + 40 | "task name '$name' and arguments $arguments") 41 | } 42 | 43 | selectedTaskFactory.createTask(name, arguments) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/GenericJavaObjectAsJsonRFunctionArg.groovy: -------------------------------------------------------------------------------- 1 | package heim.rserve 2 | 3 | import groovy.json.JsonBuilder 4 | 5 | /** 6 | * Created by glopes on 09-10-2015. 7 | */ 8 | class GenericJavaObjectAsJsonRFunctionArg implements RFunctionArg { 9 | String name 10 | Object object 11 | 12 | @Override 13 | String asRExpression() { 14 | def string = new JsonBuilder(object).toString() 15 | "fromJSON('${RUtil.escapeRStringContent(string)}')" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/RConnectionProvider.groovy: -------------------------------------------------------------------------------- 1 | package heim.rserve 2 | 3 | import org.codehaus.groovy.grails.commons.GrailsApplication 4 | import org.rosuda.REngine.Rserve.RConnection 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.stereotype.Component 7 | 8 | @Component 9 | class RConnectionProvider { 10 | 11 | @Autowired 12 | GrailsApplication grailsApplication 13 | 14 | RConnection get() { 15 | def conn = new RConnection(grailsApplication.config.RModules.host ?: '127.0.0.1', 16 | grailsApplication.config.RModules.port ?: 6311) 17 | conn.eval("options(bitmapType='cairo')") 18 | conn 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/RFunctionArg.groovy: -------------------------------------------------------------------------------- 1 | package heim.rserve 2 | 3 | /** 4 | * Created by glopes on 09-10-2015. 5 | */ 6 | interface RFunctionArg { 7 | String getName() 8 | String asRExpression() 9 | } 10 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/RScriptOutputManager.groovy: -------------------------------------------------------------------------------- 1 | package heim.rserve 2 | 3 | import heim.SmartRRuntimeConstants 4 | import org.rosuda.REngine.Rserve.RConnection 5 | 6 | /** 7 | * Created by piotrzakrzewski on 08/10/15. 8 | */ 9 | class RScriptOutputManager { 10 | 11 | private final RConnection conn 12 | private final File baseDir 13 | private final File outputPath 14 | 15 | def RScriptOutputManager(RConnection conn, 16 | UUID sessionId, 17 | UUID taskId, 18 | SmartRRuntimeConstants constants) { 19 | this.conn = conn 20 | this.baseDir = constants.baseDir 21 | this.outputPath = generateOutputPath(sessionId, taskId) 22 | } 23 | 24 | List downloadFiles() { 25 | if (!outputPath.mkdirs()) { 26 | throw new IOException("Failed creating $outputPath") 27 | } 28 | 29 | List files = listFiles() 30 | files.collect { 31 | downloadFromRserve(it) 32 | } 33 | } 34 | 35 | private File generateOutputPath(UUID session, UUID taskId) { 36 | 37 | [session, taskId].inject(baseDir) { File accum, b -> 38 | new File(accum, b.toString()) 39 | } 40 | } 41 | 42 | private List listFiles() { 43 | RUtil.runRCommand(conn, 'list.files()').asNativeJavaObject() as List 44 | } 45 | 46 | private File downloadFromRserve(String name) { 47 | File targetFile = new File(outputPath, name) 48 | if (targetFile.parentFile != outputPath) { 49 | throw new RuntimeException("$targetFile is a child of $outputPath") 50 | } 51 | 52 | conn.openFile(name).withStream { InputStream is -> 53 | targetFile.withOutputStream { OutputStream os -> 54 | os << is 55 | } 56 | } 57 | 58 | targetFile 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/RServeSession.groovy: -------------------------------------------------------------------------------- 1 | package heim.rserve 2 | 3 | import groovy.util.logging.Log4j 4 | import heim.session.SmartRSessionScope 5 | import org.codehaus.groovy.grails.commons.GrailsApplication 6 | import org.rosuda.REngine.Rserve.RConnection 7 | import org.springframework.beans.factory.DisposableBean 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.beans.factory.annotation.Value 10 | import org.springframework.stereotype.Component 11 | 12 | import java.util.concurrent.locks.Lock 13 | import java.util.concurrent.locks.ReentrantLock 14 | 15 | @Log4j 16 | @SmartRSessionScope 17 | @Component 18 | class RServeSession implements DisposableBean { 19 | 20 | @Value('#{sessionId}') 21 | String sessionId 22 | 23 | @Autowired 24 | RConnectionProvider rConnectionProvider 25 | 26 | private volatile Thread rConnectionHoldingThread 27 | private volatile boolean shuttingDown 28 | private final Lock rConnectionLock = new ReentrantLock() 29 | 30 | @Lazy 31 | private RConnection rConnection = { 32 | rConnectionProvider.get() 33 | }() 34 | 35 | /* 36 | * TODO: the implementation will block threads unnecessarily. 37 | * Explore implementation with GPars' Agents or Actors? 38 | */ 39 | public R doWithRConnection(Closure callable) { 40 | if (shuttingDown) { 41 | log.warn("Rserve session already shutting down; " + 42 | "won't execute code") 43 | return null 44 | } 45 | 46 | rConnectionLock.lockInterruptibly() 47 | rConnectionHoldingThread = Thread.currentThread() 48 | log.debug "Thread ${Thread.currentThread().name} " + 49 | "got access to R connection in session $sessionId" 50 | try { 51 | return callable.call(rConnection) 52 | } finally { 53 | rConnectionLock.unlock() 54 | rConnectionHoldingThread = null 55 | log.debug "Thread ${Thread.currentThread().name} has released " + 56 | "access to R connection in session $sessionId" 57 | } 58 | } 59 | 60 | @Override 61 | void destroy() throws Exception { 62 | shuttingDown = true 63 | if (rConnection.isConnected()) { 64 | log.debug("Asked to disconnect R connection $rConnection") 65 | if (rConnectionHoldingThread) { 66 | log.info("Trying to interrupt thread holding the R connection") 67 | rConnectionHoldingThread.interrupt() 68 | } 69 | 70 | rConnectionLock.lock() 71 | try { 72 | boolean result = rConnection.close() 73 | if (!result) { 74 | log.warn( 75 | "Close() returned false on R connection $rConnection") 76 | } else { 77 | log.debug("Closed R connection $rConnection") 78 | } 79 | } finally { 80 | rConnection.unlock() 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/groovy/heim/rserve/RUtil.java: -------------------------------------------------------------------------------- 1 | package heim.rserve; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.rosuda.REngine.REXP; 5 | import org.rosuda.REngine.REXPMismatchException; 6 | import org.rosuda.REngine.REngineException; 7 | import org.rosuda.REngine.Rserve.RConnection; 8 | import org.rosuda.REngine.Rserve.RserveException; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Originally in RModules. 15 | */ 16 | public class RUtil { 17 | 18 | private static final Logger LOG = Logger.getLogger(RUtil.class); 19 | 20 | private static Map ESCAPE_SEQUENCES = new HashMap(); 21 | 22 | /* maximum part of command to take to show in error messages */ 23 | private static final int MAX_COMMAND_PRINT_SIZE = 1024; 24 | 25 | static { 26 | ESCAPE_SEQUENCES.put(0x07, "\\a"); // Bell 27 | ESCAPE_SEQUENCES.put(0x08, "\\b"); // Backspace 28 | ESCAPE_SEQUENCES.put(0x0C, "\\f"); // Form feed 29 | ESCAPE_SEQUENCES.put(0x0A, "\\n"); // Line feed 30 | ESCAPE_SEQUENCES.put(0x0D, "\\r"); // Carriage return 31 | ESCAPE_SEQUENCES.put(0x09, "\\t"); // Horizontal tab 32 | ESCAPE_SEQUENCES.put(0x0B, "\\v"); // Vertical tab 33 | ESCAPE_SEQUENCES.put(0x5C, "\\\\"); // Backslash 34 | ESCAPE_SEQUENCES.put(0x22, "\\\""); // Quotation mark 35 | ESCAPE_SEQUENCES.put(0x27, "\\'"); // Apostrophe 36 | } 37 | 38 | /** 39 | * Escapes a string so that it's safe to include inside a single- or 40 | * double-quoted R string. The result will only contain ASCII 41 | * characters so the string can be serialized to any (reasonable) byte 42 | * stream. 43 | * @param s 44 | * @return 45 | */ 46 | public static String escapeRStringContent(String s) { 47 | /* ok to overflow: will just give a NegativeArraySizeException */ 48 | StringBuffer sb = new StringBuffer(s.length() * 2); 49 | 50 | /* See StringValue() in R's lexer file (gram.y) 51 | * Mixing \xXX and \\uXXXX escape sequences is forbidden. If a string 52 | * contains a \\u (or \U) sequence, it will be stored internally in 53 | * UTF-8. Functions that use such string can generally choose 54 | * having UTF-8 strings converted to the current locale or work 55 | * with the UTF-8 data directly. See: 56 | * http://cran.r-project.org/doc/manuals/R-ints.html#Encodings-for-CHARSXPs */ 57 | final int length = s.length(); 58 | for (int offset = 0; offset < length; ) { 59 | final int cp = s.codePointAt(offset); 60 | 61 | if (cp >= 0x80) { /* high characters */ 62 | if (cp <= 0xFFFF) { 63 | sb.append(String.format("\\u%04X", cp)); 64 | } else { 65 | sb.append(String.format("\\U%08X", cp)); 66 | } 67 | } else if (ESCAPE_SEQUENCES.containsKey(cp)) { 68 | sb.append(ESCAPE_SEQUENCES.get(cp)); 69 | } else if (cp < 0x20 || cp == 0x7F) { 70 | /* other control characters. This will force the string to 71 | * be in UTF-8, which could otherwise not be necessary, but 72 | * doing it this way keeps the code a little simpler */ 73 | sb.append(String.format("\\u%04X", cp)); 74 | } else { 75 | sb.appendCodePoint(cp); 76 | } 77 | 78 | offset += Character.charCount(cp); 79 | } 80 | 81 | return sb.toString(); 82 | } 83 | 84 | public static REXP runRCommand(RConnection conn, String finalCommand) 85 | throws REXPMismatchException, REngineException { 86 | String wrappedCommand = "try({" + finalCommand + "}, silent=FALSE)"; 87 | if (LOG.isDebugEnabled()) { 88 | LOG.debug("About to run: " + wrappedCommand); 89 | } 90 | REXP rObject = conn.parseAndEval(wrappedCommand); 91 | 92 | if (rObject != null && rObject.inherits("try-error")) { 93 | Object c = finalCommand.length() > MAX_COMMAND_PRINT_SIZE ? 94 | (finalCommand.substring(0, MAX_COMMAND_PRINT_SIZE) + "...") : 95 | finalCommand; 96 | throw new RserveException(conn, 97 | "R command failure for: " + c + ": " + rObject.asString()); 98 | } 99 | 100 | return rObject; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/groovy/heim/session/SessionContext.groovy: -------------------------------------------------------------------------------- 1 | package heim.session 2 | 3 | import com.google.common.collect.HashMultimap 4 | import com.google.common.collect.Multimap 5 | import com.google.common.collect.Multimaps 6 | import groovy.util.logging.Log4j 7 | import org.transmartproject.core.users.User 8 | 9 | import java.util.concurrent.atomic.AtomicReference 10 | 11 | /** 12 | * Holds the data for a specific session. 13 | */ 14 | @Log4j 15 | class SessionContext { 16 | 17 | public final static String SMART_R_USER_BEAN = 'smartRBean' 18 | 19 | final UUID sessionId 20 | 21 | final String workflowType 22 | 23 | private final AtomicReference lastActive = new AtomicReference<>(new Date()) 24 | 25 | private Map beans = [:].asSynchronized() 26 | 27 | private Multimap destructionCallbacks = 28 | Multimaps.synchronizedMultimap(HashMultimap.create()) 29 | 30 | 31 | void updateLastModified() { 32 | lastActive.set(new Date()) 33 | } 34 | 35 | Date getLastActive() { 36 | lastActive.get() 37 | } 38 | 39 | SessionContext(User user, String workflowType) { 40 | sessionId = UUID.randomUUID() 41 | this.workflowType = workflowType 42 | beans[SMART_R_USER_BEAN] = user 43 | } 44 | 45 | Object getBean(String beanName) { 46 | beans[beanName] 47 | } 48 | 49 | void addBean(String beanName, Object value) { 50 | beans[beanName] = value 51 | } 52 | 53 | Object removeBean(String beanName) { 54 | destructionCallbacks.removeAll(beanName) 55 | beans.remove(beanName) 56 | } 57 | 58 | void registerDestructionCallback(String name, Runnable callback) { 59 | destructionCallbacks.put(name, callback) 60 | } 61 | 62 | void destroy() { 63 | destructionCallbacks.asMap().each { String bean, 64 | Collection callbacks -> 65 | log.debug("Calling destruction callbacks for bean $bean") 66 | callbacks.each { 67 | it.run() 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/groovy/heim/session/SessionFiles.groovy: -------------------------------------------------------------------------------- 1 | package heim.session 2 | 3 | import com.google.common.collect.HashBasedTable 4 | import com.google.common.collect.Table 5 | import org.springframework.stereotype.Component 6 | import org.transmartproject.core.exceptions.NoSuchResourceException 7 | 8 | import java.nio.file.NoSuchFileException 9 | 10 | /** 11 | * A holder for the files that were created during the session, 12 | * mapping the logical task/filename combination to a file path. 13 | */ 14 | @Component 15 | @SmartRSessionScope 16 | class SessionFiles { 17 | 18 | private Table files = 19 | HashBasedTable.create() 20 | 21 | void add(UUID task, String filename, File file) { 22 | files.put(task, filename, file) 23 | } 24 | 25 | File get(UUID taskId, String filename) throws NoSuchResourceException { 26 | files.get(taskId, filename) 27 | } 28 | 29 | void removeAll() { 30 | try { 31 | for (File f: files.values()) { 32 | f.delete() 33 | } 34 | } 35 | catch(NoSuchFileException e) {} 36 | finally { 37 | files = HashBasedTable.create() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/groovy/heim/session/SmartRSessionScope.java: -------------------------------------------------------------------------------- 1 | package heim.session; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import org.springframework.context.annotation.ScopedProxyMode; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | 10 | /** 11 | * Needs to be written in Java, otherwise you get 12 | * Annotation @org.springframework.context.annotation.Scope is not allowed on 13 | * element ANNOTATION 14 | */ 15 | @Scope(value = "smartRSession", proxyMode = ScopedProxyMode.TARGET_CLASS) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | public @interface SmartRSessionScope { 19 | } 20 | -------------------------------------------------------------------------------- /src/groovy/heim/session/SmartRSessionScopeInterfaced.java: -------------------------------------------------------------------------------- 1 | package heim.session; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import org.springframework.context.annotation.ScopedProxyMode; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | 10 | /** 11 | * Variant of {@link SmartRSessionScope} that creates interface proxies. 12 | */ 13 | @Scope(value = "smartRSession", proxyMode = ScopedProxyMode.INTERFACES) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | public @interface SmartRSessionScopeInterfaced { 17 | } 18 | -------------------------------------------------------------------------------- /src/groovy/heim/session/SmartRSessionSpringScope.groovy: -------------------------------------------------------------------------------- 1 | package heim.session 2 | 3 | import groovy.util.logging.Log4j 4 | import org.springframework.beans.BeansException 5 | import org.springframework.beans.factory.ObjectFactory 6 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor 7 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory 8 | import org.springframework.beans.factory.config.Scope 9 | import org.springframework.core.NamedThreadLocal 10 | import org.springframework.core.Ordered 11 | import org.springframework.stereotype.Component 12 | 13 | /** 14 | * Managed the @SessionScope scope. Object storage is the session itself. 15 | */ 16 | @Log4j 17 | @Component 18 | class SmartRSessionSpringScope implements Scope, BeanFactoryPostProcessor, Ordered { 19 | 20 | private static final String SCOPE_NAME = 'smartRSession' 21 | 22 | private static final boolean PROXY_CLASSES = true // rather than interfaces 23 | 24 | final int order = Ordered.LOWEST_PRECEDENCE; 25 | 26 | 27 | private static ThreadLocal ACTIVE_SESSION = 28 | new NamedThreadLocal("ActiveSmartRSession") { 29 | @Override 30 | protected SessionContext initialValue() { 31 | null 32 | } 33 | }; 34 | 35 | static T withActiveSession(SessionContext ctx, Closure closure) { 36 | if (ACTIVE_SESSION.get()) { 37 | if (ACTIVE_SESSION.get() != ctx) { 38 | throw new IllegalStateException( 39 | 'Attempt to set session in context with another ' + 40 | 'one already in place') 41 | } else { 42 | return 43 | } 44 | } 45 | try { 46 | ACTIVE_SESSION.set(ctx) 47 | closure.call() 48 | } finally { 49 | ACTIVE_SESSION.set(null) 50 | } 51 | } 52 | 53 | 54 | private SessionContext getSessionContext() { 55 | def res = ACTIVE_SESSION.get() 56 | if (!res) { 57 | throw new IllegalStateException('No active smartR session set') 58 | } 59 | res 60 | } 61 | 62 | @Override 63 | Object get(String name, ObjectFactory objectFactory) { 64 | def object = sessionContext.getBean(name) 65 | if (object == null) { 66 | object = objectFactory.getObject() 67 | sessionContext.addBean(name, object) 68 | } 69 | object 70 | } 71 | 72 | @Override 73 | Object remove(String name) { 74 | sessionContext.removeBean(name) 75 | } 76 | 77 | @Override 78 | void registerDestructionCallback(String name, Runnable callback) { 79 | sessionContext.registerDestructionCallback(name, callback) 80 | } 81 | 82 | @Override 83 | Object resolveContextualObject(String key) { 84 | if (sessionContext.hasProperty(key)) { 85 | sessionContext."$key" 86 | } 87 | } 88 | 89 | @Override 90 | String getConversationId() { 91 | sessionContext.sessionId.toString() 92 | } 93 | 94 | @Override 95 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 96 | beanFactory.registerScope('smartRSession', this) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/AbstractTask.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import com.google.common.base.Objects 4 | 5 | /** 6 | * Created by glopes on 09-10-2015. 7 | */ 8 | abstract class AbstractTask implements Task { 9 | 10 | final UUID uuid 11 | 12 | AbstractTask() { 13 | this.uuid = UUID.randomUUID() 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return Objects.toStringHelper(this) 19 | .add("class", getClass().simpleName) 20 | .add("uuid", uuid) 21 | .toString(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/RScriptExecutionTask.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import com.google.common.collect.ImmutableList 4 | import com.google.common.collect.ImmutableMap 5 | import groovy.transform.TypeChecked 6 | import groovy.util.logging.Log4j 7 | import heim.SmartRRuntimeConstants 8 | import heim.rserve.RFunctionArg 9 | import heim.rserve.RScriptOutputManager 10 | import heim.rserve.RServeSession 11 | import heim.rserve.RUtil 12 | import heim.session.SessionFiles 13 | import org.rosuda.REngine.REXP 14 | import org.rosuda.REngine.Rserve.RConnection 15 | 16 | import static heim.rserve.RUtil.runRCommand 17 | 18 | @Log4j 19 | @TypeChecked 20 | // TODO: convert to prototype bean 21 | class RScriptExecutionTask extends AbstractTask { 22 | 23 | UUID sessionId 24 | RServeSession rServeSession 25 | SessionFiles sessionFiles 26 | File fileToLoad // absolute 27 | String function = 'main' 28 | List arguments 29 | SmartRRuntimeConstants constants 30 | 31 | @Override 32 | TaskResult call() throws Exception { 33 | rServeSession.doWithRConnection { RConnection conn -> 34 | def outputManager = new RScriptOutputManager(conn, sessionId, uuid, constants) 35 | 36 | REXP res = callR(conn) 37 | List fileNames = outputManager.downloadFiles() 38 | 39 | convertSuccessfulResult(res, fileNames) 40 | } 41 | } 42 | 43 | private void injectScriptDir(RConnection conn) { 44 | def remoteScriptDir = fileToLoad.getParentFile().getParentFile() // We need the HeimScripts root 45 | String path = RUtil.escapeRStringContent(remoteScriptDir.absolutePath) 46 | runRCommand(conn, "remoteScriptDir <- \"$path\"") 47 | } 48 | 49 | private void sourceCoreUtils(RConnection conn) { 50 | def remoteScriptDirRoot = fileToLoad.getParentFile().getParentFile() 51 | File remoteCoreUtilsDir = new File(remoteScriptDirRoot, '_core') 52 | File coreUtilsIndex = new File(remoteCoreUtilsDir, 'index.R') 53 | runRCommand(conn, "source('" + 54 | "${RUtil.escapeRStringContent(coreUtilsIndex.absolutePath)}')") 55 | } 56 | 57 | private REXP callR(RConnection conn) { 58 | runRCommand(conn, 'library(jsonlite)') 59 | injectScriptDir(conn) 60 | sourceCoreUtils(conn) 61 | runRCommand(conn, "source('" + 62 | "${RUtil.escapeRStringContent(fileToLoad.toString())}')") 63 | def namedArguments = arguments.collect { RFunctionArg arg -> 64 | "${arg.name}=${arg.asRExpression()}" 65 | } 66 | runRCommand(conn, "main(${namedArguments.join(', ')})") 67 | } 68 | 69 | private TaskResult convertSuccessfulResult(REXP result, 70 | List files) { 71 | def builder = ImmutableMap.builder() 72 | builder.putAll(massage(result)) 73 | builder.put('files', ImmutableList.copyOf(files*.name)) 74 | 75 | files.each { File f -> 76 | sessionFiles.add(uuid, f.name, f) 77 | } 78 | new TaskResult( 79 | successful: true, 80 | artifacts: builder.build(), 81 | ) 82 | } 83 | 84 | private ImmutableMap massage(REXP result) { 85 | def asNative = result.asNativeJavaObject() 86 | if (!(asNative instanceof Map)) { 87 | asNative = [(asNative): 'value'] 88 | } 89 | 90 | /* list(a=1, b='a') results in a map where the keys (!) are arrays 91 | * with one element with value 1.0 and 'a' and the values are the names 92 | * of the list. Invert this and unwrap the values 93 | */ 94 | def builder = ImmutableMap.builder() 95 | asNative.each { k, v -> 96 | if (k == []) { 97 | return 98 | } 99 | assert k.getClass().isArray() && ((Object[])k).length == 1 100 | assert v instanceof String 101 | builder.put(v, ((Object[])k)[0]) 102 | } 103 | 104 | builder.build() 105 | } 106 | 107 | @Override 108 | void close() throws Exception { 109 | // nothing to do 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/RScriptExecutionTaskFactory.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import heim.SmartRRuntimeConstants 4 | import heim.jobs.JobInstance 5 | import heim.rserve.GenericJavaObjectAsJsonRFunctionArg 6 | import heim.rserve.RFunctionArg 7 | import heim.rserve.RScriptsSynchronizer 8 | import heim.rserve.RServeSession 9 | import heim.session.SessionFiles 10 | import heim.session.SmartRSessionScope 11 | import org.springframework.beans.factory.annotation.Autowired 12 | import org.springframework.beans.factory.annotation.Value 13 | import org.springframework.core.Ordered 14 | import org.springframework.stereotype.Component 15 | import org.transmartproject.core.exceptions.InvalidArgumentsException 16 | 17 | /** 18 | * Created by glopes on 09-10-2015. 19 | */ 20 | @Component 21 | @SmartRSessionScope 22 | class RScriptExecutionTaskFactory implements TaskFactory { 23 | 24 | @Autowired 25 | private RServeSession rServeSession 26 | 27 | @Autowired 28 | private JobInstance jobInstance 29 | 30 | @Autowired 31 | private RScriptsSynchronizer rScriptsSynchronizer 32 | 33 | @Autowired 34 | private SessionFiles sessionFiles 35 | 36 | @Autowired 37 | private SmartRRuntimeConstants constants 38 | 39 | @Value('#{sessionId}') 40 | private UUID sessionId 41 | 42 | final int order = Ordered.LOWEST_PRECEDENCE 43 | 44 | @Override 45 | boolean handles(String taskName, Map argument) { 46 | true // fallback factory, since it has the lowest precedence 47 | } 48 | 49 | private File calculateRemoteScriptPath(String taskType) { 50 | File dir = constants.remoteScriptDirectoryDir 51 | assert dir != null 52 | File workflowDir = new File(dir, jobInstance.workflow) 53 | new File(workflowDir, taskType + '.R') 54 | } 55 | 56 | @Override 57 | Task createTask(String taskName, Map arguments) { 58 | rScriptsSynchronizer.ensureScriptsAreCopied() 59 | try { 60 | File fileToLoad = calculateRemoteScriptPath(taskName) 61 | 62 | new RScriptExecutionTask( 63 | sessionFiles: sessionFiles, 64 | sessionId: sessionId, 65 | rServeSession: rServeSession, 66 | fileToLoad: fileToLoad, 67 | arguments: convertArguments(arguments), 68 | constants: constants, 69 | ) 70 | } catch (IOException ioe) { 71 | throw new InvalidArgumentsException("Bad script '$taskName' for " + 72 | "workflow '${jobInstance.workflow}'", ioe) 73 | } 74 | } 75 | 76 | List convertArguments(Map arguments) { 77 | arguments.collect { k, v -> 78 | new GenericJavaObjectAsJsonRFunctionArg( 79 | name: k, 80 | object: v) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/Task.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import java.util.concurrent.Callable 4 | 5 | /** 6 | * Created by glopes on 09-10-2015. 7 | */ 8 | interface Task extends Callable, AutoCloseable { 9 | 10 | UUID getUuid() 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/TaskFactory.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import org.springframework.core.Ordered 4 | 5 | /** 6 | * Created by glopes on 09-10-2015. 7 | */ 8 | interface TaskFactory extends Ordered { 9 | 10 | boolean handles(String taskName, 11 | Map argument) 12 | 13 | Task createTask(String name, 14 | Map arguments) 15 | } 16 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/TaskResult.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import com.google.common.collect.ImmutableMap 4 | import groovy.transform.ToString 5 | 6 | /** 7 | * Created by glopes on 09-10-2015. 8 | */ 9 | @ToString(includePackage = false, includeNames = true) 10 | final class TaskResult { 11 | final boolean successful 12 | final Exception exception 13 | final ImmutableMap artifacts 14 | 15 | TaskResult(Map args) { 16 | this.successful = args.successful 17 | this.exception = args.exception 18 | this.artifacts = args.artifacts 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/TaskState.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | /** 4 | * Created by glopes on 09-10-2015. 5 | */ 6 | enum TaskState { 7 | QUEUED, 8 | RUNNING, 9 | FINISHED, 10 | FAILED 11 | } 12 | -------------------------------------------------------------------------------- /src/groovy/heim/tasks/TestTaskFactory.groovy: -------------------------------------------------------------------------------- 1 | package heim.tasks 2 | 3 | import heim.tasks.AbstractTask 4 | import heim.tasks.Task 5 | import heim.tasks.TaskFactory 6 | import heim.tasks.TaskResult 7 | import org.springframework.core.Ordered 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean 10 | 11 | /** 12 | * This class is here for technical reasons (test classpath is not available 13 | * when setting up the Spring context). 14 | */ 15 | class TestTaskFactory implements TaskFactory { 16 | 17 | public final static String TEST_TASK_NAME = 'test' 18 | 19 | final int order = Ordered.HIGHEST_PRECEDENCE 20 | 21 | @Override 22 | boolean handles(String taskName, Map argument) { 23 | taskName == TEST_TASK_NAME 24 | } 25 | 26 | @Override 27 | Task createTask(String name, Map arguments) { 28 | assert arguments['closure'] instanceof Closure 29 | new AbstractTask() { 30 | AtomicBoolean isClosed = arguments.closed 31 | final Object monitor = arguments.monitor 32 | 33 | @Override 34 | void close() throws Exception { 35 | synchronized (monitor) { 36 | isClosed?.set(true) 37 | monitor.notify() 38 | } 39 | } 40 | 41 | @Override 42 | TaskResult call() throws Exception { 43 | arguments['closure'].call() 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/functional/heim/BaseAPITestCase.groovy: -------------------------------------------------------------------------------- 1 | package heim 2 | 3 | import com.grailsrocks.functionaltest.APITestCase 4 | 5 | import static org.hamcrest.Matchers.is 6 | import static org.junit.Assume.assumeThat 7 | 8 | abstract class BaseAPITestCase extends APITestCase { 9 | 10 | protected void setUp() { 11 | super.setUp() 12 | //To fix "org.apache.http.conn.HttpHostConnectException: Connection to http://localhost:8080 refused" 13 | baseURL = baseURL.replace('localhost', '127.0.0.1') 14 | } 15 | 16 | protected static String buildQueryParameters(Map map) { 17 | // you were supposes to be able to do: 18 | // get url, { foo = 'bar' } 19 | // but it seems to be broken 20 | // this is a workaround 21 | map.collect { k, v -> 22 | "$k=${URLEncoder.encode(v.toString(), 'UTF-8')}" 23 | }.join('&') 24 | } 25 | 26 | protected String /* session id */ createSession(String workflowName) { 27 | post('/RSession/create') { 28 | body json: [ 29 | workflow: workflowName 30 | ] 31 | } 32 | assumeThat client.responseStatus, is(201) 33 | JSON.sessionId 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/functional/heim/RSessionControllerTests.groovy: -------------------------------------------------------------------------------- 1 | package heim 2 | 3 | import org.springframework.http.HttpStatus 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat 6 | import static org.hamcrest.Matchers.* 7 | import static org.junit.Assume.assumeThat 8 | 9 | class RSessionControllerTests extends BaseAPITestCase { 10 | 11 | void testCreate() { 12 | post('/RSession/create') { 13 | body json: [ 14 | workflow: 'heatmap' 15 | ] 16 | } 17 | assertStatus HttpStatus.CREATED.value() 18 | 19 | assertThat JSON, hasEntry(equalTo('sessionId'), isA(String)) 20 | } 21 | 22 | void testDestroy() { 23 | post('/RSession/create') { 24 | body json: [ 25 | workflow: 'heatmap', 26 | ] 27 | } 28 | assumeThat client.responseStatus, is(201) 29 | def sessionId = JSON.sessionId 30 | 31 | post('/RSession/delete') { 32 | body json: [ 33 | sessionId: sessionId, 34 | ] 35 | } 36 | 37 | assertStatus HttpStatus.ACCEPTED.value() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/runit/run_tests.R: -------------------------------------------------------------------------------- 1 | library(RUnit) 2 | 3 | test.suite <- defineTestSuite("Heim", 4 | dirs = file.path("test/runit/tests"), 5 | testFileRegexp = ".*R$") # any R file 6 | 7 | test.result <- runTestSuite(test.suite) 8 | 9 | printTextProtocol(test.result) 10 | if (test.result$Heim$nFail > 0) { 11 | stop("Some Unit Tests failed - check the log for details.") 12 | } else { 13 | print("All R tests passed.") 14 | } 15 | -------------------------------------------------------------------------------- /test/runit/tests/boxplot_summary.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/boxplot/summary.R") 2 | 3 | 4 | ### main test data ### 5 | test_set <- 6 | data.frame( 7 | Row.Label = c("1007_s_at", "1053_at", "117_at", "121_at", "1255_g_at"), 8 | clinical_numeric = c(179.26, NA, 0, 0.00734,-23.0234), 9 | stringsAsFactors = F 10 | ) 11 | 12 | test_set_measurements <- 13 | test_set["clinical_numeric"] 14 | 15 | test_data <- list("n0_s1" = test_set) 16 | test_data_measurements <- list("n0_s1" = test_set_measurements) 17 | 18 | test_set_preprocessed <- test_set 19 | colnames(test_set_preprocessed) <- 20 | c("Row.Label" ,"clinical_numeric_n0_s1") 21 | 22 | .setUp <- function() { 23 | # A .png file is created by produce_boxplot. Should be written to temporary directory 24 | assign("origDirectory", getwd(), envir = .GlobalEnv) 25 | dir <- tempdir() 26 | dir.create(dir) 27 | setwd(dir) 28 | 29 | #loaded_variables and preprocessed are sometimes removed by a test function 30 | assign("loaded_variables", test_data, envir = .GlobalEnv) 31 | } 32 | 33 | .tearDown <- function() { 34 | dir <- getwd() 35 | setwd(origDirectory) 36 | unlink(dir, recursive = T, force = T) 37 | } 38 | 39 | 40 | 41 | ### unit tests for function extract_measurements ### 42 | 43 | #test if function works if data has a "Row.Label" and a "Bio.marker" column 44 | test.extract_measurements.simplecase1 <- function() { 45 | checkEquals(test_data_measurements, extract_measurements(test_data)) 46 | } 47 | 48 | #test if it works if data has "Row.Label" column but no "Bio.marker" column 49 | test.extract_measurements.simplecase2 <- function() { 50 | test_data_tmp <- 51 | list("n0_s1" = test_set[, c("Row.Label", "clinical_numeric")]) 52 | checkEquals(test_data_measurements, extract_measurements(test_data_tmp)) 53 | } 54 | -------------------------------------------------------------------------------- /test/runit/tests/core_input.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/_core/input.R") 2 | 3 | loaded_variables_with_a_vector <- list(should_be_df = c(1,2,3)) # a Vector instead of a df 4 | loaded_variables_df <- data.frame(a = c(1,2,3)) # data.frame instead of a list 5 | loaded_variables_proper1 <- list(box1_n1_s1 = data.frame(a = c(1,2,3) ) ) 6 | 7 | HDDproper <- data.frame(Row.Label = letters[1:5], Bio.marker = letters[1:5], NumVect = 1:5 ) 8 | ClinicalNumericProper <- data.frame(Row.Label = letters[1:5], NumVect = 1:5 ) 9 | 10 | 11 | # Case of loaded_variables containing smt else than a dataframe 12 | test.validateLoadedVariables1 <- function() { 13 | checkException( validateLoadedVariables(loaded_variables_with_a_vector) ) 14 | } 15 | 16 | # Case of loaded_variables of type data.frame instead of list 17 | test.validateLoadedVariables2 <- function() { 18 | checkException( validateLoadedVariables(loaded_variables_df ) ) 19 | } 20 | 21 | # Case of a proper loaded_variable 22 | test.validateLoadedVariables3 <- function() { 23 | # the inner checkException will throw an exception because there is no exception thrown 24 | # the outer checkException catches it and validates to true 25 | checkException(checkException( validateLoadedVariables(loaded_variables_proper1 ) )) 26 | } 27 | 28 | test.isHDD1 <- function() { 29 | checkException( isHDD(ClinicalNumericProper) ) 30 | } 31 | 32 | test.isHDD2 <- function() { 33 | checkTrue( isHDD(HDDproper) ) 34 | } 35 | -------------------------------------------------------------------------------- /test/runit/tests/heatmap_downloadData.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/heatmap/downloadData.R") 2 | 3 | .setUp <- function() { 4 | assign("origDirectory", getwd(), envir = .GlobalEnv) 5 | dir <- tempdir() 6 | dir.create(dir) 7 | setwd(dir) 8 | } 9 | 10 | .tearDown <- function() { 11 | dir <- getwd() 12 | setwd(origDirectory) 13 | unlink(dir, recursive = T, force = T) 14 | } 15 | 16 | 17 | test.zip.success <- function() { 18 | write("foobar1", "params.json") 19 | write("foobar2", "heatmap_data.tsv") 20 | write("foobar3", "heatmap_orig_values.tsv") 21 | 22 | main() 23 | 24 | checkTrue(file.exists('analysis_data.zip')) 25 | 26 | zipFiles <- unzip(zipfile = 'analysis_data.zip', list = T)$Name 27 | checkTrue(identical(sort(c("params.json", 28 | "heatmap_data.tsv", 29 | "heatmap_orig_values.tsv")), 30 | sort(zipFiles))) 31 | } 32 | 33 | test.zip.misingfile <- function() { 34 | checkException(main()) 35 | } 36 | -------------------------------------------------------------------------------- /test/runit/tests/heatmap_preprocess.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/heatmap/preprocess.R") 2 | 3 | test_data_aggregate <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1 = c(4,1,4,1), Assay2 = c(4,1,4,1) ) 4 | 5 | test_data_drop_empty <- data.frame(Bio.marker=c("","a",NA),b=c(1,2,3) ) 6 | 7 | #Simple case 8 | test.aggregate <- function() 9 | { 10 | result <- aggregate.probes(test_data_aggregate) 11 | expected <- data.frame(Row.Label=c("a1","b1"), Bio.marker = c("a","b"), Assay1=c(4,4), Assay2 = c(4,4), stringsAsFactors = F) 12 | checkEquals(result, expected) 13 | } 14 | 15 | test.drop.empty <- function() 16 | { 17 | result <- dropEmptyGene(test_data_drop_empty) 18 | expected <- test_data_drop_empty[2,] 19 | checkEquals(result, expected) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /test/runit/tests/heatmap_run.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/heatmap/run.R") 2 | 3 | ##SE: Fct not used anymore 4 | # # Test slash being removed - issue that used to broke sorting 5 | # test.fixString.slash.case <- function() 6 | # { 7 | # testString <- "a/b" 8 | # result <- fixString(testString) 9 | # expected <- "ab" 10 | # checkEquals(result,expected) 11 | # } 12 | # 13 | # #Dashes are allowed in probe_ids 14 | # test.fixString.dash.case <- function() 15 | # { 16 | # testString <- "a-b" 17 | # result <- fixString(testString) 18 | # expected <- "a-b" 19 | # checkEquals(result,expected) 20 | # } 21 | 22 | test.mergeFetchedData.singledf <- function() 23 | { 24 | df1 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1 = 1:4, Assay2 = 4:1 ) 25 | test_loaded_variables <- list(d1=df1) 26 | result <- mergeFetchedData(test_loaded_variables) 27 | expected <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1_d1 = 1:4, Assay2_d1 = 4:1 ) 28 | checkEquals(result,expected) 29 | } 30 | 31 | test.mergeFetchedData.twodfs <- function() 32 | { 33 | df1 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1 = 1:4, Assay2 = 4:1 ) 34 | df2 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay3 = 1:4, Assay4 = 4:1 ) 35 | test_loaded_variables <- list(d1=df1, d2=df2) 36 | merged <- mergeFetchedData(test_loaded_variables) 37 | checkTrue(all(colnames(merged) == c("Row.Label","Bio.marker","Assay1_d1","Assay2_d1","Assay3_d2","Assay4_d2"))) 38 | checkTrue(nrow(merged) == 4) 39 | } 40 | 41 | test.get.subset <- function() 42 | { 43 | test_patient_ids <- c("Assay1_blabla_s1","Assay2_bleblee_s2") 44 | result <- getSubset(test_patient_ids) 45 | expected <- c(1,2) 46 | checkEquals(result, expected) 47 | } 48 | 49 | test.get.subject <- function() 50 | { 51 | test_patient_ids <- c("120_Breast_s2", "121_Breast_s1", "122_Breast_s1", "123_Breast_s2") 52 | result <- getSubject(test_patient_ids) 53 | expected <- c("120", "121", "122", "123") 54 | checkEquals(result, expected) 55 | } 56 | 57 | test.getSubset1Length <- function() 58 | { 59 | testMeasurements <- data.frame("a_n0_s1" = 1:3, 60 | "b_n0_s1" = 1:3, "a_n1_s1" = 1:3, "b_n1_s1" = 1:3, 61 | "a_n0_s2" = 1:3, "b_n0_s2" = 1:3 ) 62 | expected <- 4 63 | result <- getSubset1Length(testMeasurements) 64 | checkEquals(result,expected) 65 | } 66 | 67 | ## SE: Adapted to fit modified getDesign() fct 68 | test.getDesign <- function() 69 | { 70 | testMeasurements <- data.frame("a_n0_s1" = 1:3, 71 | "b_n0_s1" = 1:3, "a_n1_s1" = 1:3, "b_n1_s1" = 1:3, 72 | "a_n0_s2" = 1:3, "b_n0_s2" = 1:3 ) 73 | expected <- matrix( 74 | c(1, 1, 1, 1, 0, 0, 75 | 0, 0, 0, 0, 1, 1), 76 | ncol = 2 77 | ) 78 | colnames(expected) <- c("S1", "S2") 79 | result <- getDesign(testMeasurements) 80 | checkEquals(result,expected) 81 | } 82 | 83 | test.getDEgenes <- function() 84 | { 85 | test_set<- data.frame( Row.Label = c("1007_s_at", "1053_at", "117_at"), 86 | Bio.marker = c("a", "b", "c"), 87 | GSM210004_n0_s1 = c(6.5, 4, 5), 88 | GSM210006_n1_s1 = c(8.1, 2, 4), 89 | GSM210007_n1_s1 = c(6.2, 3, 5), 90 | GSM210008_n1_s1 = c(8.34, 2, 5), 91 | GSM210006_n1_s2 = c(8.4, -2, 9), 92 | GSM210007_n1_s2 = c(6.4,2, 12), 93 | stringsAsFactors = F) 94 | result <- getDEgenes(test_set) 95 | result[,-c(1,2)]<-round(result[,-c(1,2)],4) 96 | 97 | expected <- data.frame( Row.Label = c("1007_s_at", "1053_at", "117_at"), 98 | Bio.marker = c("a", "b", "c"), 99 | logfold = c(0.115, -2.75, 5.75), 100 | ttest = c(0.089, -2.1275, 4.4485), 101 | pval = c(0.9306, 0.0548, 0.0008), 102 | adjpval = c(0.9306, 0.0822, 0.0024), 103 | bval = c(-5.7709, -3.7252, 3.1846), 104 | stringsAsFactors = F) 105 | checkEquals(result, expected) 106 | } 107 | -------------------------------------------------------------------------------- /test/runit/tests/volcanoplot_run.R: -------------------------------------------------------------------------------- 1 | source("web-app/HeimScripts/volcanoplot/run.R") 2 | 3 | ## SE: Fct not used anymore 4 | # # Test slash being removed - issue that used to broke sorting 5 | # test.fixString.slash.case <- function() 6 | # { 7 | # testString <- "a/b" 8 | # result <- fixString(testString) 9 | # expected <- "ab" 10 | # checkEquals(result,expected) 11 | # } 12 | # 13 | # #Dashes are allowed in probe_ids 14 | # test.fixString.dash.case <- function() 15 | # { 16 | # testString <- "a-b" 17 | # result <- fixString(testString) 18 | # expected <- "a-b" 19 | # checkEquals(result,expected) 20 | # } 21 | 22 | test.mergeFetchedData.singledf <- function() 23 | { 24 | df1 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1 = 1:4, Assay2 = 4:1 ) 25 | test_loaded_variables <- list(d1=df1) 26 | result <- mergeFetchedData(test_loaded_variables) 27 | expected <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1_d1 = 1:4, Assay2_d1 = 4:1 ) 28 | checkEquals(result,expected) 29 | } 30 | 31 | test.mergeFetchedData.twodfs <- function() 32 | { 33 | df1 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay1 = 1:4, Assay2 = 4:1 ) 34 | df2 <- data.frame(Row.Label=c("a1","a2","b1","b2") , Bio.marker= c("a","a","b","b"), Assay3 = 1:4, Assay4 = 4:1 ) 35 | test_loaded_variables <- list(d1=df1, d2=df2) 36 | merged <- mergeFetchedData(test_loaded_variables) 37 | checkTrue(all(colnames(merged) == c("Row.Label","Bio.marker","Assay1_d1","Assay2_d1","Assay3_d2","Assay4_d2"))) 38 | checkTrue(nrow(merged) == 4) 39 | } 40 | 41 | test.get.subset <- function() 42 | { 43 | test_patient_ids <- c("Assay1_blabla_s1","Assay2_bleblee_s2") 44 | result <- getSubset(test_patient_ids) 45 | expected <- c(1,2) 46 | checkEquals(result, expected) 47 | } 48 | 49 | test.get.subject <- function() 50 | { 51 | test_patient_ids <- c("120_Breast_s2", "121_Breast_s1", "122_Breast_s1", "123_Breast_s2") 52 | result <- getSubject(test_patient_ids) 53 | expected <- c("120", "121", "122", "123") 54 | checkEquals(result, expected) 55 | } 56 | 57 | test.getSubset1Length <- function() 58 | { 59 | testMeasurements <- data.frame("a_n0_s1" = 1:3, 60 | "b_n0_s1" = 1:3, "a_n1_s1" = 1:3, "b_n1_s1" = 1:3, 61 | "a_n0_s2" = 1:3, "b_n0_s2" = 1:3 ) 62 | expected <- 4 63 | result <- getSubset1Length(testMeasurements) 64 | checkEquals(result,expected) 65 | } 66 | 67 | ## SE: Adapted to fit modified getDesign() fct 68 | test.getDesign <- function() 69 | { 70 | testMeasurements <- data.frame("a_n0_s1" = 1:3, 71 | "b_n0_s1" = 1:3, "a_n1_s1" = 1:3, "b_n1_s1" = 1:3, 72 | "a_n0_s2" = 1:3, "b_n0_s2" = 1:3 ) 73 | expected <- matrix( 74 | c(1, 1, 1, 1, 0, 0, 75 | 0, 0, 0, 0, 1, 1), 76 | ncol = 2 77 | ) 78 | colnames(expected) <- c("S1", "S2") 79 | result <- getDesign(testMeasurements) 80 | checkEquals(result,expected) 81 | } 82 | 83 | test.getDEgenes <- function() 84 | { 85 | test_set<- data.frame( Row.Label = c("1007_s_at", "1053_at", "117_at"), 86 | Bio.marker = c("a", "b", "c"), 87 | GSM210004_n0_s1 = c(6.5, 4, 5), 88 | GSM210006_n1_s1 = c(8.1, 2, 4), 89 | GSM210007_n1_s1 = c(6.2, 3, 5), 90 | GSM210008_n1_s1 = c(8.34, 2, 5), 91 | GSM210006_n1_s2 = c(8.4, -2, 9), 92 | GSM210007_n1_s2 = c(6.4,2, 12), 93 | stringsAsFactors = F) 94 | result <- getDEgenes(test_set) 95 | result[,-c(1,2)]<-round(result[,-c(1,2)],4) 96 | 97 | expected <- data.frame( Row.Label = c("1007_s_at", "1053_at", "117_at"), 98 | Bio.marker = c("a", "b", "c"), 99 | logfold = c(0.115, -2.75, 5.75), 100 | ttest = c(0.089, -2.1275, 4.4485), 101 | pval = c(0.9306, 0.0548, 0.0008), 102 | adjpval = c(0.9306, 0.0822, 0.0024), 103 | bval = c(-5.7709, -3.7252, 3.1846), 104 | stringsAsFactors = F) 105 | checkEquals(result, expected) 106 | } 107 | -------------------------------------------------------------------------------- /test/unit/heim/RScriptOutputManagerSpec.groovy: -------------------------------------------------------------------------------- 1 | package heim 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | import heim.rserve.RScriptOutputManager 6 | import org.rosuda.REngine.Rserve.RConnection 7 | import spock.lang.Specification 8 | 9 | /** 10 | * See the API for {@link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions 11 | */ 12 | @TestMixin(GrailsUnitTestMixin) 13 | class RScriptOutputManagerSpec extends Specification { 14 | 15 | def setup() { 16 | 17 | } 18 | 19 | def cleanup() { 20 | new File(SmartRRuntimeConstants.instance.baseDir, "bogusfolder/init/plot.png").delete() // Access to a property of SmartRRuntimeConstants and "instance" mechanism inspired by http://stackoverflow.com/a/13846141/535203 21 | } 22 | 23 | void "test getScriptOutput() returning single plot"() { 24 | given: 25 | def conn = new RConnection() 26 | conn.eval('png("plot.png")') 27 | conn.eval('plot(c(0,1),c(1,1))') 28 | conn.eval('dev.off()') 29 | def sessionId = UUID.randomUUID() 30 | def taskId = UUID.randomUUID() 31 | def mngr = new RScriptOutputManager(conn, sessionId, taskId, SmartRRuntimeConstants.instance) 32 | 33 | when: "" 34 | def result = mngr.downloadFiles() 35 | then: "Result is a list containing plot.png and only that one file." 36 | result 37 | result.size() == 1 38 | result[0] instanceof File 39 | result[0].getAbsolutePath() == SmartRRuntimeConstants.instance.baseDir.toString()+"/$sessionId/$taskId/plot.png" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/unit/javascript/JavaScriptUnitTestKarmaSuite.groovy: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import de.is24.util.karmatestrunner.junit.KarmaTestSuiteRunner 4 | import org.junit.runner.RunWith 5 | 6 | @RunWith(KarmaTestSuiteRunner) 7 | @KarmaTestSuiteRunner.KarmaProcessName('./node_modules/.bin/karma') 8 | @KarmaTestSuiteRunner.KarmaConfigPath('karma.conf.js') 9 | class JavaScriptUnitTestKarmaSuite { 10 | } 11 | -------------------------------------------------------------------------------- /test/unit/javascript/runButtonDirectiveTests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('runButton', function() { 4 | var $compile, 5 | $rootScope, 6 | $httpBackend, 7 | $q, 8 | element, 9 | smartRUtils, 10 | rServeService; 11 | 12 | beforeEach(module('smartRApp')); 13 | beforeEach(module('smartRTemplates')); 14 | 15 | beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_, _$q_, _smartRUtils_, _rServeService_) { 16 | $compile = _$compile_; 17 | $rootScope = _$rootScope_; 18 | $httpBackend = _$httpBackend_; 19 | $q = _$q_; 20 | smartRUtils = _smartRUtils_; 21 | rServeService = _rServeService_; 22 | 23 | $rootScope.scriptResults = {}; 24 | })); 25 | 26 | it('should show "Creating plot, please wait"', function() { 27 | var html = ''; 28 | element = $compile(angular.element(html))($rootScope); 29 | $rootScope.$digest(); 30 | expect(element.isolateScope().waitMessage).toEqual("Creating plot, please wait"); 31 | }); 32 | 33 | it('should show "Running R-script, please wait"', function() { 34 | var html = ''; 35 | element = $compile(angular.element(html))($rootScope); 36 | $rootScope.$digest(); 37 | expect(element.isolateScope().waitMessage).toEqual("Running R-script, please wait"); 38 | }); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /test/unit/javascript/smartRUtilsServiceTests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('smartRUtils', function() { 4 | 5 | var smartRUtils; 6 | 7 | beforeEach(module('smartRApp')); 8 | 9 | beforeEach(inject(function(_smartRUtils_) { 10 | smartRUtils = _smartRUtils_; 11 | })); 12 | 13 | it('must be defined', function() { 14 | expect(smartRUtils).toBeDefined(); 15 | }); 16 | 17 | it('has working conceptBoxMapToConceptKeys()', function() { 18 | var params = {a: {concepts: ['c1', 'c2'], valid: true}, 19 | 'foo bar __-!*()123 abc': {concepts: ['c3'], valid: true}, 20 | b: {concepts: [], valid: true}, 21 | c: {concepts: ['12--_- c31/??/*&^/foobar'], valid: true} 22 | }; 23 | var expected = {a_n0: 'c1', a_n1: 'c2', 'foo bar __-!*()123 abc_n0': 'c3', c_n0: '12--_- c31/??/*&^/foobar'}; 24 | var result = smartRUtils.conceptBoxMapToConceptKeys(params); 25 | expect(result).toEqual(expected); 26 | }); 27 | 28 | it('has working makeSafeForCSS()', function() { 29 | var regex = /[_a-zA-Z]+[_a-zA-Z0-9-]*/; 30 | var testStr1 = '!@#$%^&*asdfghj _-;:,<.>/|\\"+=0987654321'; 31 | var testStr2 = '__foo-bar-123'; 32 | expect(testStr1.replace(regex, '')).not.toEqual(''); 33 | expect(smartRUtils.makeSafeForCSS(testStr1).replace(regex, '')).toEqual(''); 34 | expect(testStr2.replace(regex, '')).toEqual(''); 35 | expect(smartRUtils.makeSafeForCSS(testStr2).replace(regex, '')).toEqual(''); 36 | }); 37 | 38 | it('has working shortenConcept()', function() { 39 | var testStr1 = '\\foo bar\\ab c d'; 40 | var testStr2 = 'foo bar\\abcd'; 41 | var testStr3 = '\\a\\b\\c\\d\\e\\f\\g\\h'; 42 | var testStr4 = 'foo bar\\abcd\\'; 43 | expect(smartRUtils.shortenConcept(testStr1)).toEqual('foo bar/ab c d'); 44 | expect(smartRUtils.shortenConcept(testStr2)).toEqual('foo bar/abcd'); 45 | expect(smartRUtils.shortenConcept(testStr3)).toEqual('g/h'); 46 | expect(smartRUtils.shortenConcept(testStr4)).toEqual('foo bar/abcd'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/javascript/testSetup.js: -------------------------------------------------------------------------------- 1 | var pageInfo = {}; 2 | pageInfo.basePath = 'http://localhost'; 3 | 4 | // mockup of smartRApp to avoid GET request before it can be handled by $httpBackend 5 | window.smartRApp = angular.module('smartRApp', ['ngRoute', 'door3.css']) 6 | .run(function($rootScope) { 7 | 'use strict'; 8 | $rootScope.smartRPath = ''; 9 | }); 10 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_core/index.R: -------------------------------------------------------------------------------- 1 | if (!exists("remoteScriptDir")) { # Needed for unit-tests 2 | remoteScriptDir <- "web-app/HeimScripts/_core" 3 | } 4 | 5 | inputUtils <- paste(remoteScriptDir, "/_core/input.R", sep="") 6 | source(inputUtils) 7 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_func_test/autosourcing.R: -------------------------------------------------------------------------------- 1 | 2 | main <- function() { 3 | 4 | if (!exists('parseInput')) { 5 | stop("_core/input.R is not properly sourced") 6 | } 7 | return(list(success=TRUE)) 8 | } 9 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_func_test/sample.R: -------------------------------------------------------------------------------- 1 | main <- function(data_to_pass, data_to_write) { 2 | write(data_to_write, file='test_file') 3 | list(a=1,b='foobar',passed=data_to_pass) 4 | } 5 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_func_test/sourced.R: -------------------------------------------------------------------------------- 1 | 2 | testFun <- function() { 3 | "test" 4 | } 5 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_func_test/sourcing.R: -------------------------------------------------------------------------------- 1 | demo <- paste(remoteScriptDir, "/_func_test/sourced.R", sep="") 2 | source(demo) 3 | 4 | main <- function() { 5 | res <- testFun() 6 | list("shouldBeTest"=res) 7 | } 8 | 9 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_shared_functions/GEX/limmaUtils.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## This file contains more specific limma and limma-related functionalities ## 3 | ## to check the input data frame content and perform diff expr analysis for ## 4 | ## Microarray GEX data ## 5 | ################################################################################ 6 | 7 | 8 | 9 | 10 | ## Loading functions ## 11 | dataFrameUtils <- paste(remoteScriptDir, "/_shared_functions/GEX/DataFrameAndGEXmatrixUtils.R", sep="") 12 | 13 | source(dataFrameUtils) 14 | 15 | 16 | ## Differential expression analysis comparing Subset 1 to subset 2 17 | ## using Limma package 18 | getDEgenes <- function(df) { 19 | measurements <- getMeasurements(df) 20 | design <- getDesign(measurements) 21 | contrast.matrix <- makeContrasts( S2-S1, levels = design ) 22 | fit <- lmFit(measurements, design) 23 | fit <- contrasts.fit(fit, contrast.matrix) 24 | fit <- eBayes(fit) 25 | contr <- 1 # We need a vector, not a df, so we'll do [, contr] on all stats 26 | top.fit <- data.frame ( 27 | logfold = fit$coefficients[, contr], 28 | ttest = fit$t[, contr], 29 | pval = fit$p.value[, contr], 30 | adjpval = p.adjust( 31 | p = fit$p.value[, contr], 32 | method ='fdr'), 33 | bval = fit$lods[, contr] 34 | ) 35 | 36 | cbind(df[,1:2], top.fit) 37 | } 38 | 39 | 40 | 41 | ## Generate the design matrix 42 | getDesign <- function(measurements) { 43 | 44 | subsets <- getSubset(colnames(measurements)) #s1 = 1, s2 = 2 45 | classVectorS1 <- subsets #s1 = 1, s2 = 2 46 | classVectorS2 <- - subsets + 3 #s1 = 2, s2 = 1 47 | 48 | DesignMatrix = cbind(S1=subsets, S2=subsets) 49 | DesignMatrix[which(DesignMatrix[, "S1"]==2), "S1"]=0 50 | DesignMatrix[which(DesignMatrix[, "S2"]==1), "S2"]=0 51 | DesignMatrix[which(DesignMatrix[, "S2"]==2), "S2"]=1 52 | 53 | return(DesignMatrix) 54 | 55 | } 56 | 57 | 58 | 59 | # checking valid measurements on a matrix level 60 | isValidLimmaMeasurements <- function (measurements) { 61 | sum(apply(measurements, 1, validMeasurementsRow)) > 0 62 | } 63 | 64 | 65 | 66 | ## Writing jsn file containing the Limma diff expr analysis results 67 | writeMarkerTable <- function(markerTable, markerTableJson = "markerSelectionTable.json"){ 68 | colnames(markerTable) <- c("rowLabel", "biomarker", 69 | "log2FoldChange", "t", "pValue", "adjustedPValue", "B") 70 | jsn <- toJSON(markerTable, pretty = TRUE, digits = I(17)) 71 | write(jsn, file = markerTableJson) 72 | } 73 | 74 | 75 | 76 | ## Deleting the json-format topTable 77 | ## from last Limma diff expr analysis run 78 | cleanUpLimmaOutput <- function(markerTableJson = "markerSelectionTable.json") { 79 | if (file.exists(markerTableJson)) { 80 | file.remove(markerTableJson) 81 | } 82 | } 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /web-app/HeimScripts/_shared_functions/Generic/utils.R: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | ## This file contains generic functionalities when working with ## 3 | ## SmartR data objects ## 4 | ###################################################################### 5 | 6 | 7 | ## Retrieve High dim node for vector of patientIDs 8 | getNode <- function(patientIDs) { 9 | 10 | splittedIds <- strsplit(patientIDs,"_") # During merge, which is always 11 | # run we append subset id, either 12 | # _s1 or _s2 to PATIENTID. 13 | 14 | sapply(splittedIds, FUN = tail_elem,n = 2) # In proper patienid subset will 15 | # always be at the end. 16 | # This select last but one elemnt 17 | # - the node 18 | } 19 | 20 | 21 | 22 | getTimelineValues <- function(nodes, ontologyTerms) { 23 | sapply(nodes, function(n) { 24 | metaValue <- ontologyTerms[[n]]$metadata$seriesMeta$value 25 | t <- !is.na(as.numeric(metaValue)) 26 | if (length(t) && t) { 27 | as.numeric(metaValue) 28 | } else { 29 | Inf 30 | } 31 | }, USE.NAMES = FALSE) 32 | } 33 | 34 | getSubject <- function(str) { 35 | sub("_.*", "", str) 36 | } 37 | 38 | ## This function dumps the run parameters into a json format file 39 | ## The function can be called either 40 | ## ... with three unnamed params providing the following variables/info in this order: 41 | ## -- max_rows => integer() 42 | ## -- sorting => character("nodes", "subjects") 43 | ## -- ranking => charcter("logfold", "ttest", "pval", "adjpval", "mean", "median", "coef", "range") 44 | ## e.g.: "writeRunParams(max_rows, sorting, ranking)" 45 | ## ... or with a group of named params: 46 | ## e.g.: "writeRunParams(some_parameter=some_parameter, another_param=another_param, ... )" 47 | writeRunParams <- function(...) { 48 | 49 | ## Get the data objects passed 50 | params <- list(...) 51 | 52 | ## The default way of calling the function, this 53 | ## means providing 3 args and without named params 54 | if(length(names(params))==0 & length(params)==3) 55 | names(params) = c("max_rows", "sorting", "ranking") 56 | 57 | ## Get name of this function 58 | calledFun = as.list(sys.call())[[1]] 59 | 60 | ## Checking if params is a named list 61 | ## this means named parameters have been 62 | ## provided as input 63 | if(length(names(params))==0 & length(params)!=0) 64 | print(paste("Please provided named function parameters as input for ", calledFun, sep=": ")) 65 | 66 | params <- c(params, fetch_params) 67 | 68 | if (exists("preprocessed") && exists("preprocessing_params")) { 69 | params <- c(params, preprocessing_params) 70 | } 71 | write(toJSON(params, pretty = TRUE), 'params.json') 72 | } 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /web-app/HeimScripts/correlation/run.R: -------------------------------------------------------------------------------- 1 | library(reshape2) 2 | 3 | main <- function(method = "pearson", transformation = "raw", selectedPatientIDs = integer()) { 4 | 5 | df1 <- loaded_variables$datapoints_n0_s1 6 | df2 <- loaded_variables$datapoints_n1_s1 7 | 8 | if (nrow(df1) == 0) { 9 | stop(paste("Variable '", fetch_params$ontologyTerms$datapoints_n0$name, "' has no patients for subset 1"), sep="") 10 | } 11 | if (nrow(df2) == 0) { 12 | stop(paste("Variable '", fetch_params$ontologyTerms$datapoints_n1$name, "' has no patients for subset 1"), sep="") 13 | } 14 | num_data <- merge(df1, df2, by="Row.Label") 15 | colnames(num_data) <- c("patientID", "x", "y") 16 | 17 | if (transformation == "log2") { 18 | num_data$x <- log2(num_data$x) 19 | num_data$y <- log2(num_data$y) 20 | } else if (transformation == "log10") { 21 | num_data$x <- log10(num_data$x) 22 | num_data$y <- log10(num_data$y) 23 | } 24 | num_data <- na.omit(num_data) 25 | num_data <- num_data[!is.infinite(num_data$x), ] 26 | num_data <- num_data[!is.infinite(num_data$y), ] 27 | 28 | cat_data <- data.frame(patientID=integer(), annotation=character()) 29 | filtered.loaded_variables <- get.loaded_variables.by.source("annotations", loaded_variables) 30 | if (length(filtered.loaded_variables) > 0) { 31 | merged.df <- Reduce(function(...) merge(..., by='Row.Label', all=T), filtered.loaded_variables) 32 | merged.df <- merged.df[, colSums(is.na(merged.df)) != nrow(merged.df)] # remove NA columns 33 | 34 | annotations <- apply(merged.df[,-1], 1, function(row) { 35 | row <- row[row != ""] 36 | paste(row, collapse="-AND-") 37 | }) 38 | cat_data <- data.frame( 39 | patientID=as.integer(merged.df$Row.Label), 40 | annotation=as.character(annotations) 41 | ) 42 | } 43 | 44 | df <- num_data 45 | if (nrow(cat_data) > 0) { 46 | df <- merge(df, cat_data, by="patientID") 47 | } else { 48 | df$annotation <- '' 49 | } 50 | 51 | colnames(df) <- c("patientID", "x", "y", "annotation") 52 | 53 | if (length(selectedPatientIDs) > 0) { 54 | df <- df[df$patientID %in% selectedPatientIDs, ] 55 | } 56 | 57 | corTest <- tryCatch({ 58 | test <- cor.test(df$x, df$y, method=method) 59 | test$p.value <- ifelse(test$p.value == 0, paste("<", as.character(.Machine$double.eps)), as.character(test$p.value)) 60 | test 61 | }, error = function(e) { 62 | ll <- list() 63 | ll$estimate <- as.numeric(NA) 64 | ll$p.value <- as.numeric(NA) 65 | ll 66 | }) 67 | 68 | regLineSlope <- corTest$estimate * (sd(df$y) / sd(df$x)) 69 | regLineYIntercept <- mean(df$y) - regLineSlope * mean(df$x) 70 | 71 | output <- list( 72 | correlation = corTest$estimate, 73 | pvalue = corTest$p.value, 74 | regLineSlope = regLineSlope, 75 | regLineYIntercept = regLineYIntercept, 76 | xArrLabel = fetch_params$ontologyTerms$datapoints_n0$fullName, 77 | yArrLabel = fetch_params$ontologyTerms$datapoints_n1$fullName, 78 | method = method, 79 | transformation = transformation, 80 | patientIDs = df$patientID, 81 | annotations = unique(df$annotation), 82 | points = df 83 | ) 84 | toJSON(output) 85 | } 86 | 87 | conceptStrToFolderStr <- function(s) { 88 | splitString <- strsplit(s, "")[[1]] 89 | backslashs <- which(splitString == "\\") 90 | substr(s, 0, tail(backslashs, 2)[1]) 91 | } 92 | 93 | -------------------------------------------------------------------------------- /web-app/HeimScripts/heatmap/downloadData.R: -------------------------------------------------------------------------------- 1 | main <- function() { 2 | kZipFilename <- 'analysis_data.zip' 3 | kFiles <- c( 4 | "params.json", 5 | "heatmap_data.tsv", 6 | "heatmap_orig_values.tsv" 7 | ) 8 | kDefaultZipLocation = "/usr/bin/zip" 9 | 10 | # main function 11 | function() { 12 | lapply(kFiles, function(file) { 13 | if (!file.exists(file)) { 14 | stop(paste("File ", file, " doesn't exist. Has the analysis been run?")) 15 | } 16 | }) 17 | if (file.exists(kDefaultZipLocation)) { 18 | status <- zip(kZipFilename, kFiles, zip = kDefaultZipLocation) 19 | } else { 20 | # relying on default for zip parameter fails on my box: sh: 1: : Permission denied 21 | status <- zip(kZipFilename, kFiles) 22 | } 23 | 24 | if (status != 0) { 25 | stop(paste("Zipping failed with status ", as.character(status))) 26 | } 27 | list() 28 | } 29 | }() 30 | -------------------------------------------------------------------------------- /web-app/HeimScripts/heatmap/preprocess.R: -------------------------------------------------------------------------------- 1 | library(WGCNA) 2 | 3 | # Pre-processing data saved in preprocessed 4 | # Pre-processing parameters saved in global preprocessing_params 5 | # They can be considered fresh if the variable preprocessed exists 6 | 7 | 8 | # SE: Just to get things working for dev purposes 9 | #rm(list = ls()) 10 | #load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/data.Rda") 11 | #load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/fetchParams.Rda") 12 | #load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/loaded_variables_withLDD.Rda") 13 | #load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/fetch_params_withLDD.Rda") 14 | #setwd("/Users/serge/GitHub/SmartR") 15 | ####### 16 | 17 | 18 | if (!exists("remoteScriptDir")) { # Needed for unit-tests 19 | remoteScriptDir <- "web-app/HeimScripts" 20 | } 21 | 22 | ## Loading functions ## 23 | utils <- paste(remoteScriptDir, "/_shared_functions/Generic/utils.R", sep="") 24 | dataFrameUtils <- paste(remoteScriptDir, "/_shared_functions/GEX/DataFrameAndGEXmatrixUtils.R", sep="") 25 | 26 | source(utils) 27 | source(dataFrameUtils) 28 | 29 | 30 | ## SE: Handles Low and high dimensional data. Creates preprocessed variable as a list storing HD and LD data 31 | ## In case no LD data is available this list item is set to NULL 32 | main <- function(aggregate=FALSE) { 33 | msgs = c("") 34 | 35 | df <- loaded_variables[grep("highDimensional", names(loaded_variables))] 36 | 37 | ## High dimensional data 38 | df <- mergeFetchedData(loaded_variables[grep("highDimensional", names(loaded_variables))]) 39 | ## Low dimensional data 40 | ld.idx = grep( "^(categoric)|(numeric)", names(loaded_variables)) 41 | 42 | if(length(ld.idx)==0) 43 | ld = c() 44 | else 45 | ld = loaded_variables[ld.idx] 46 | 47 | 48 | ## SE: Modified cutoff for samples to 2 instead of 3 49 | good.input <- ncol(df) > 2 && nrow(df) > 0 && sum(df$Bio.marker != "") > 0 # min one sample, contains any rows, non empty Bio.marker column. 50 | 51 | if(aggregate && good.input){ 52 | df <- dropEmptyGene(df) 53 | 54 | aggr <- aggregate.probes(df) 55 | 56 | 57 | 58 | ## SE: modified assign value to list type to handle also low dim data 59 | assign("preprocessed", list(HD = aggr, LD=ld), envir = .GlobalEnv) 60 | 61 | discarded.rows <- nrow(df) - nrow(aggr) 62 | msgs <- paste("Total discarded rows:",discarded.rows) 63 | } 64 | else if(aggregate && !good.input){ 65 | stop("Incorrect subset - in order to perform probe aggregation more than one sample is needed.") 66 | }else{ 67 | msgs <- c("No preprocessing applied.") 68 | 69 | ## SE: modified assign value to list type to handle also low dim data 70 | assign("preprocessed", list(HD = df, LD = ld), envir = .GlobalEnv) 71 | } 72 | 73 | assign("preprocessing_params", list(aggregate=aggregate), envir = .GlobalEnv) 74 | 75 | 76 | list(finished=T,messages=msgs) 77 | 78 | ## SE: For debug 79 | #return(df) 80 | } 81 | 82 | 83 | 84 | 85 | ## SE: For debug 86 | #main(aggregate=T) 87 | # print(dim(df)) 88 | -------------------------------------------------------------------------------- /web-app/HeimScripts/linegraph/corrStats.R: -------------------------------------------------------------------------------- 1 | main <- function(params) { 2 | df <- runResults$data_matrix 3 | # we only care about categoric data in this script 4 | cat.df <- df[df$type == "categoric", ] 5 | 6 | patientIDs <- unique(cat.df$patientID) 7 | bioMarkers <- unique(cat.df$bioMarker) 8 | timeIntegers <- unique(cat.df$timeInteger) 9 | 10 | # create binary vector that represents occurence of params$bioMarker at params$timePoint for every patient 11 | time.df <- cat.df[cat.df$timeInteger == params$timeInteger & cat.df$bioMarker == params$bioMarker, ] 12 | bin.vec_1 <- as.numeric(patientIDs %in% time.df$patientID) 13 | 14 | # do the same for every other timepoint and biomarker to compute correlations 15 | output <- data.frame(bioMarker=character(), timeInteger=integer(), corrCoef=numeric(), pValue=numeric()) 16 | for (bioMarker in bioMarkers) { 17 | for (timeInteger in timeIntegers[timeIntegers != params$timeInteger]) { 18 | time.df <- cat.df[cat.df$timeInteger == timeInteger & cat.df$bioMarker == bioMarker, ] 19 | bin.vec_2 <- as.numeric(patientIDs %in% time.df$patientID) 20 | test <- cor.test(bin.vec_1, bin.vec_2, method="pearson") 21 | output <- rbind(output, data.frame(bioMarker=bioMarker, 22 | timeInteger=timeInteger, 23 | corrCoef=as.numeric(test$estimate), 24 | pValue=test$p.value)) 25 | } 26 | } 27 | json <- toJSON(output, digits=I(17)) 28 | return(json) 29 | } 30 | -------------------------------------------------------------------------------- /web-app/HeimScripts/linegraph/lineStats.R: -------------------------------------------------------------------------------- 1 | main <- function(params) { 2 | y.vec <- params$yVec 3 | 4 | output <- list() 5 | 6 | mean <- mean(y.vec) 7 | sd <- sd(y.vec) 8 | 9 | output$mean <- mean 10 | output$sd <- sd 11 | toJSON(output) 12 | } 13 | -------------------------------------------------------------------------------- /web-app/HeimScripts/patientmapper/run.R: -------------------------------------------------------------------------------- 1 | main <- function() { 2 | output <- list() 3 | output$sourceIDs <- c() 4 | output$sourceSubsets <- c() 5 | output$targetIDs <- c() 6 | variables <- names(loaded_variables) 7 | source.vars <- names(loaded_variables)[grep("source", variables)] 8 | target.vars <- names(loaded_variables)[grep("target", variables)] 9 | target.vars <- sub("_s[1-2]{1}$", "", target.vars) 10 | target.vars <- unique(target.vars) 11 | for (var in source.vars) { 12 | data <- loaded_variables[var][[1]] 13 | data <- na.omit(data) 14 | data <- data[data[, 2] != "", ] 15 | if (nrow(data)) { 16 | source.id <- data[1,2] 17 | sourceSubset <- sub("^.*_s", "", var) 18 | output$sourceIDs <- c(output$sourceIDs, source.id) 19 | output$sourceSubsets <- c(output$sourceSubsets, sourceSubset) 20 | } 21 | } 22 | cohortNodes <- data.frame(key=c(), 23 | level=c(), 24 | fullName=c(), 25 | name=c(), 26 | tooltip=c(), 27 | visualAttributes=c(), 28 | subset=c()) 29 | for (var in target.vars) { 30 | params <- fetch_params$ontologyTerms[var][[1]] 31 | subsets <- output$sourceSubsets[which(params$name == output$sourceIDs)] 32 | for (subset in subsets) { 33 | cohortNodes <- rbind(cohortNodes, data.frame(key=params$key, 34 | level=params$level, 35 | fullName=params$fullName, 36 | name=params$name, 37 | tooltip=params$tooltip, 38 | visualAttributes=paste(params$visualAttributes, collapse=","), 39 | subset=strtoi(subset))) 40 | } 41 | } 42 | output$cohortNodes <- cohortNodes 43 | toJSON(output) 44 | } 45 | -------------------------------------------------------------------------------- /web-app/HeimScripts/volcanoplot/run.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | library(reshape2) 4 | library(limma) 5 | library(jsonlite) 6 | 7 | ## SE: Just to get things working for dev purposes 8 | # rm(list = ls()) 9 | # load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/data.Rda") 10 | # load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/fetchParams.Rda") 11 | # load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/loaded_variables_withLDD.Rda") 12 | # load("/Users/serge/Documents/Projects/SmartR/Development_env_Input_workspace/R_workspace_objects/Heatmap/fetch_params_withLDD.Rda") 13 | # setwd("/Users/serge/GitHub/SmartR") 14 | 15 | if (!exists("remoteScriptDir")) { # Needed for unit-tests 16 | remoteScriptDir <- "web-app/HeimScripts" 17 | } 18 | 19 | ## Loading functions ## 20 | utils <- paste(remoteScriptDir, "/_shared_functions/Generic/utils.R", sep="") 21 | limmaUtils <- paste(remoteScriptDir, "/_shared_functions/GEX/limmaUtils.R", sep="") 22 | dataFrameUtils <- paste(remoteScriptDir, "/_shared_functions/GEX/DataFrameAndGEXmatrixUtils.R", sep="") 23 | 24 | 25 | source(utils) 26 | source(limmaUtils) 27 | source(dataFrameUtils) 28 | 29 | 30 | SUBSET1REGEX <- "_s1$" # Regex identifying columns of subset 1. 31 | markerTableJson <- "markerSelectionTable.json" # Name of the json file with limma outputs 32 | 33 | 34 | 35 | 36 | main <- function() { 37 | 38 | 39 | ## Get Gene Expression Matrix as data frame 40 | data.list = parseInput() 41 | df = data.list$HD 42 | 43 | 44 | ## Defining the content for these variables to 45 | ## perform differential expression analysis: 46 | max_rows = dim(df)[1] 47 | sorting = "nodes" 48 | ranking = "pval" 49 | 50 | 51 | ## File containing the original 52 | ## GEX values that can be downloaded 53 | ## by the user 54 | write.table( 55 | df, 56 | "volcanoplot_orig_values.tsv", 57 | sep = "\t", 58 | na = "", 59 | row.names = FALSE, 60 | col.names = TRUE 61 | ) 62 | 63 | df <- addStats(df, ranking, max_rows) 64 | df <- mergeDuplicates(df) 65 | df <- df[!is.na(df["LOGFOLD"][,1]),] # remove rows with NA valued logFC 66 | df <- df[1:min(max_rows, nrow(df)), ] # apply max_rows 67 | 68 | fields <- buildFields(df) 69 | uids <- df[, 1] 70 | patientIDs <- unique(fields["PATIENTID"])[,1] 71 | 72 | negativeLog10PvalValues = -log10(df["PVAL"][,1]) 73 | 74 | ## Output json object containing results 75 | jsn <- list( 76 | "uids" = uids, 77 | "logfoldValues" = df["LOGFOLD"][,1], 78 | "pvalValues" = df["PVAL"][,1], 79 | "negativeLog10PvalValues" = negativeLog10PvalValues, 80 | "patientIDs" = patientIDs, 81 | "warnings" = c() # initiate empty vector 82 | ) 83 | jsn <- toJSON(jsn) 84 | 85 | writeRunParams() 86 | 87 | measurements <- cleanUp(df) # temporary stats like SD and MEAN need to be removed for clustering to work 88 | 89 | write(jsn, file = "volcanoplot.json") 90 | # json file be served the same way 91 | # like any other file would - get name via 92 | # /status call and then /download 93 | 94 | msgs <- c("Finished successfuly") 95 | list(messages = msgs) 96 | 97 | #return(df) 98 | } 99 | 100 | 101 | ##################### 102 | ##################### 103 | 104 | 105 | ## #SE: For dev purposes we call the function here 106 | ##out = main() 107 | 108 | 109 | -------------------------------------------------------------------------------- /web-app/WEB-INF/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Grails application factory bean 8 | 9 | 10 | 11 | 12 | 13 | A bean that manages Grails plugins 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | utf-8 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web-app/WEB-INF/sitemesh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web-app/css/boxplot.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transmart/SmartR/6ce267606ff9b99bb63a5a2db0e1b5303d50d206/web-app/css/boxplot.css -------------------------------------------------------------------------------- /web-app/css/correlation.css: -------------------------------------------------------------------------------- 1 | .point { 2 | opacity: 0.8; 3 | stroke: #FFFFFF; 4 | } 5 | 6 | .regressionLine { 7 | stroke: #FFA500; 8 | stroke-width: 3px; 9 | } 10 | 11 | .regressionLine:hover { 12 | stroke: #FF0000; 13 | } 14 | 15 | .brush .extent { 16 | fill: blue; 17 | opacity: .125; 18 | shape-rendering: crispEdges; 19 | } 20 | 21 | .bar rect { 22 | fill: #FEEAAF; 23 | stroke: #DEAC3A; 24 | shape-rendering: crispEdges; 25 | } 26 | 27 | .bar text { 28 | fill: #ffa500; 29 | font-weight: bold; 30 | } 31 | 32 | .axis path, .axis line { 33 | fill: none; 34 | stroke: black; 35 | stroke-width: 1px; 36 | shape-rendering: crispEdges; 37 | } 38 | 39 | .axis { 40 | font-size: 10px; 41 | } 42 | 43 | .tick line { 44 | shape-rendering: crispEdges; 45 | opacity: 0.2; 46 | } 47 | 48 | .axisLabels { 49 | text-anchor: middle; 50 | font-size: 14px; 51 | font-weight: bold; 52 | } 53 | 54 | .legend1 { 55 | color: black; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /web-app/css/heatmap.css: -------------------------------------------------------------------------------- 1 | .square { 2 | } 3 | 4 | .square:hover { 5 | opacity: 0.4; 6 | } 7 | 8 | .squareHighlighted { 9 | opacity: 0.4; 10 | } 11 | 12 | .extraSquare { 13 | } 14 | 15 | .feature { 16 | } 17 | 18 | .featureSortText { 19 | } 20 | 21 | .selected { 22 | opacity: 1; 23 | } 24 | 25 | .extraSquare:hover { 26 | opacity: 0.4; 27 | } 28 | 29 | .bar { 30 | shape-rendering: crispEdges; 31 | } 32 | 33 | .bar:hover { 34 | opacity: 0.4; 35 | } 36 | 37 | .barHighlighted { 38 | opacity: 0.4; 39 | } 40 | 41 | .cutoffHighlight { 42 | opacity: 0.4; 43 | } 44 | 45 | .box { 46 | fill-opacity: 0; 47 | shape-rendering: crispEdges; 48 | } 49 | 50 | .box:hover { 51 | cursor: pointer; 52 | fill-opacity: 0.2; 53 | } 54 | 55 | .sortedBy { 56 | stroke: red; 57 | } 58 | 59 | .rowname { 60 | cursor: pointer; 61 | } 62 | 63 | .rowname:hover { 64 | fill: blue; 65 | text-decoration: underline; 66 | } 67 | 68 | .selectText { 69 | } 70 | 71 | .highlight { 72 | font-weight: bold; 73 | fill: red; 74 | } 75 | 76 | .node { 77 | fill: white; 78 | stroke: rgb(0, 126, 126); 79 | stroke-width: 1.5px; 80 | } 81 | 82 | .node:hover { 83 | cursor: pointer; 84 | fill: rgb(0, 126, 126); 85 | } 86 | 87 | .link { 88 | fill: none; 89 | stroke: #FF8700; 90 | stroke-width: 2px; 91 | } 92 | 93 | .sr-heatmap-table { 94 | border-collapse: collapse; 95 | } 96 | 97 | .sr-heatmap-table th { 98 | font-weight: bold; 99 | } 100 | 101 | .sr-heatmap-table tr { 102 | 103 | } 104 | 105 | .sr-heatmap-table td { 106 | border: solid 1px black; 107 | padding: 4px; 108 | } 109 | 110 | .sr-workflow-controls>div { 111 | float: right; 112 | padding: 5px 15px 5px 15px; 113 | } 114 | 115 | #sr-heatmap-cluster-select { 116 | width: 100%; 117 | } 118 | 119 | #sr-heatmap-zoom-range { 120 | float: right; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /web-app/css/linegraph.css: -------------------------------------------------------------------------------- 1 | #visualisation { 2 | background: rgb(24, 24, 24); 3 | } 4 | 5 | /* all text elements */ 6 | #visualisation text { 7 | fill: rgb(145, 145, 145); 8 | } 9 | 10 | /* axis itself */ 11 | .sr-lg-x-axis path { 12 | fill: none; 13 | stroke: none; 14 | } 15 | 16 | /* axis ticks */ 17 | .sr-lg-x-axis line { 18 | fill: none; 19 | stroke: rgb(52, 52, 52); 20 | stroke-width: 1px; 21 | } 22 | 23 | /* axis itself */ 24 | .sr-lg-y-axis path { 25 | fill: none; 26 | stroke: none; 27 | } 28 | 29 | /* axis ticks */ 30 | .sr-lg-y-axis line { 31 | fill: none; 32 | stroke: rgb(52, 52, 52); 33 | stroke-width: 1px; 34 | } 35 | 36 | /* all boxes of cat plot */ 37 | .sr-lg-cat-plot-box { 38 | stroke-width: 2px; 39 | stroke: rgb(24, 24, 24); 40 | } 41 | 42 | /* subset 1 boxes of cat plot */ 43 | .sr-lg-cat-plot-box.subset-1 { 44 | fill: rgba(94, 198, 216, 0.08); 45 | } 46 | 47 | .sr-lg-cat-plot-box.subset-1:hover { 48 | fill: rgba(94, 198, 216, 1); 49 | } 50 | 51 | /* subset 2 boxes of cat plot */ 52 | .sr-lg-cat-plot-box.subset-2 { 53 | fill: rgba(217, 54, 51, 0.08); 54 | } 55 | .sr-lg-cat-plot-box.subset-2:hover { 56 | fill: rgba(217, 54, 51, 1); 57 | } 58 | 59 | .sr-lg-cat-plot-sig-bar { 60 | fill: #009e20; 61 | shape-rendering: crispEdge; 62 | } 63 | 64 | /* icons of cat plot */ 65 | .sr-lg-cat-icon { 66 | stroke-width: 1px; 67 | stroke: rgb(24, 24, 24); 68 | shape-rendering: crispEdges; 69 | cursor: pointer; 70 | } 71 | .sr-lg-cat-icon:hover { 72 | 73 | } 74 | /* special class for icon hl */ 75 | .icon-highlight { 76 | } 77 | 78 | .sr-lg-cat-stat-icon { 79 | stroke: none; 80 | fill: white; 81 | } 82 | 83 | /* arrows for scrolling up and down */ 84 | .sr-lg-shift-element { 85 | fill: rgb(38, 38, 38); 86 | stroke: rgb(67, 67, 67); 87 | } 88 | .sr-lg-shift-element:hover { 89 | fill: rgb(45, 45, 45); 90 | cursor: pointer; 91 | } 92 | 93 | /* all boxes for num plots */ 94 | .sr-lg-num-plot>rect { 95 | fill: none; 96 | stroke: none; 97 | } 98 | 99 | /* legend rects for subset 1 */ 100 | .sr-lg-num-legend.subset-1 { 101 | fill: rgb(94, 198, 216); 102 | } 103 | 104 | /* legend rects for subset 1 */ 105 | .sr-lg-num-legend.subset-2 { 106 | fill: rgb(217, 54, 51); 107 | } 108 | 109 | /* boxplots */ 110 | .sr-lg-boxplot rect { 111 | stroke-width: 1px; 112 | shape-rendering: crispEdges; 113 | } 114 | .sr-lg-boxplot.subset-1 rect { 115 | stroke: none; 116 | fill: rgb(94, 198, 216); 117 | } 118 | 119 | .sr-lg-boxplot.subset-2 rect { 120 | stroke: none; 121 | fill: rgb(217, 54, 51); 122 | } 123 | .sr-lg-boxplot rect:hover { 124 | opacity: 0.5; 125 | } 126 | 127 | /* connection line between boxplots */ 128 | .sr-lg-timeline { 129 | fill: none; 130 | stroke-width: 2px; 131 | } 132 | .sr-lg-timeline-highlight { 133 | stroke: yellow !important; 134 | stroke-width: 4px; 135 | } 136 | .sr-lg-timeline.subset-1 { 137 | stroke: rgb(94, 198, 216); 138 | } 139 | .sr-lg-timeline.subset-2 { 140 | stroke: rgb(217, 54, 51); 141 | } 142 | 143 | /* special hl class for all timeline objects */ 144 | .timeline-lowlight { 145 | opacity: 0.2; 146 | } 147 | 148 | .sr-lg-sd-line>text { 149 | } 150 | 151 | .sr-lg-sd-line>line { 152 | fill: none; 153 | stroke-width: 3px; 154 | } 155 | 156 | /* invisible axis box to catch mouse events */ 157 | .sr-lg-time-element > rect { 158 | opacity: 0; 159 | } 160 | .sr-lg-time-element > rect:hover { 161 | cursor: move; 162 | } 163 | 164 | /* arrows that appear left and right when hovering over axis element */ 165 | .sr-lg-time-element > polygon { 166 | fill: rgba(94, 198, 216, 0.7); 167 | } 168 | 169 | /* control panel */ 170 | .sr-workflow-controls>div { 171 | float: right; 172 | padding: 5px 15px 5px 15px; 173 | } 174 | 175 | .d3-tip { 176 | background: rgba(145, 145, 145, 0.8) !important; 177 | color: #000 !important; 178 | } 179 | 180 | .d3-tip:after { 181 | color: rgba(145, 145, 145, 0.8) !important; 182 | } 183 | 184 | .sr-lg-legend-item { 185 | cursor: ns-resize; 186 | } 187 | 188 | .sr-lg-legend-item>rect { 189 | fill: white; 190 | opacity: 0; 191 | } 192 | -------------------------------------------------------------------------------- /web-app/css/patientmappper.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transmart/SmartR/6ce267606ff9b99bb63a5a2db0e1b5303d50d206/web-app/css/patientmappper.css -------------------------------------------------------------------------------- /web-app/css/tooltip.css: -------------------------------------------------------------------------------- 1 | .d3-tip { 2 | z-index: 9999; 3 | line-height: 1; 4 | font-weight: bold; 5 | padding: 12px; 6 | background: rgba(0, 0, 0, 0.8); 7 | color: #fff; 8 | border-radius: 2px; 9 | pointer-events: none; 10 | } 11 | 12 | /* Creates a small triangle extender for the tooltip */ 13 | .d3-tip:after { 14 | box-sizing: border-box; 15 | display: inline; 16 | font-size: 16px; 17 | width: 100%; 18 | line-height: 0.8; 19 | color: rgba(0, 0, 0, 0.8); 20 | position: absolute; 21 | pointer-events: none; 22 | } 23 | 24 | /* Northward tooltips */ 25 | .d3-tip.n:after { 26 | content: "\25BC"; 27 | margin: -1px 0 0 0; 28 | top: 100%; 29 | left: 0; 30 | text-align: center; 31 | } 32 | 33 | /* Eastward tooltips */ 34 | .d3-tip.e:after { 35 | content: "\25C0"; 36 | margin: -4px 0 0 0; 37 | top: 50%; 38 | left: -12px; 39 | } 40 | 41 | /* Southward tooltips */ 42 | .d3-tip.s:after { 43 | content: "\25B2"; 44 | margin: 0 0 1px 0; 45 | top: -8px; 46 | left: 0; 47 | text-align: center; 48 | } 49 | 50 | /* Westward tooltips */ 51 | .d3-tip.w:after { 52 | content: "\25B6"; 53 | margin: -4px 0 0 -1px; 54 | top: 50%; 55 | left: 100%; 56 | } 57 | -------------------------------------------------------------------------------- /web-app/css/volcanoplot.css: -------------------------------------------------------------------------------- 1 | .point { 2 | 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: black; 9 | shape-rendering: crispEdges; 10 | } 11 | 12 | .square { 13 | 14 | } 15 | 16 | .tick line { 17 | shape-rendering: crispEdges; 18 | opacity: 0.2; 19 | } 20 | 21 | .pLine { 22 | stroke: red; 23 | stroke-width: 2px; 24 | shape-rendering: crispEdges; 25 | } 26 | 27 | .pHandle { 28 | cursor: ns-resize; 29 | opacity: 0; 30 | } 31 | 32 | .logFCLine { 33 | stroke: #0000FF; 34 | stroke-width: 2px; 35 | shape-rendering: crispEdges; 36 | } 37 | 38 | .logFCHandle { 39 | cursor: ew-resize; 40 | opacity: 0; 41 | } 42 | 43 | .axisText { 44 | font-size: 14px; 45 | } 46 | 47 | .volcanoplot-table, .myth, .mytd { 48 | border: 1px solid black; 49 | border-collapse: collapse; 50 | } 51 | 52 | .myth, .mytd { 53 | padding: 5px; 54 | } 55 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/boxplot.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=boxplot.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('BoxplotController', [ 6 | '$scope', 7 | 'smartRUtils', 8 | 'commonWorkflowService', 9 | function($scope, smartRUtils, commonWorkflowService) { 10 | 11 | commonWorkflowService.initializeWorkflow('boxplot', $scope); 12 | 13 | $scope.fetch = { 14 | running: false, 15 | disabled: false, 16 | button: { 17 | disabled: false, 18 | message: '' 19 | }, 20 | loaded: false, 21 | selectedBiomarkers: [], 22 | conceptBoxes: { 23 | numData: {concepts: [], valid: false}, 24 | highDimensional: {concepts: [], valid: false}, 25 | groups: {concepts: [], valid: true} 26 | } 27 | }; 28 | 29 | $scope.runAnalysis = { 30 | running: false, 31 | disabled: true, 32 | scriptResults: {}, 33 | params: { 34 | transformation: 'raw' 35 | } 36 | }; 37 | 38 | $scope.$watch(function() { 39 | return $scope.fetch.conceptBoxes.highDimensional.concepts.length + ' ' + $scope.fetch.selectedBiomarkers.length; 40 | }, 41 | function() { 42 | if ($scope.fetch.conceptBoxes.highDimensional.concepts.length > 0 && 43 | ($scope.fetch.selectedBiomarkers.length === 0 || $scope.fetch.selectedBiomarkers.length > 10)) { 44 | $scope.fetch.button.disabled = true; 45 | $scope.fetch.button.message = 'Please select between 1 and 10 biomarkers for your high dimensional data'; 46 | } else { 47 | $scope.fetch.button.disabled = false; 48 | $scope.fetch.button.message = ''; 49 | } 50 | }); 51 | 52 | $scope.$watchGroup(['fetch.running', 'runAnalysis.running'], 53 | function(newValues) { 54 | var fetchRunning = newValues[0], 55 | runAnalysisRunning = newValues[1]; 56 | 57 | // clear old results 58 | if (fetchRunning) { 59 | $scope.runAnalysis.scriptResults = {}; 60 | } 61 | 62 | // disable tabs when certain criteria are not met 63 | $scope.fetch.disabled = runAnalysisRunning; 64 | $scope.runAnalysis.disabled = fetchRunning || !$scope.fetch.loaded; 65 | } 66 | ); 67 | 68 | }]); 69 | 70 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/correlation.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=correlation.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('CorrelationController', 6 | ['$scope', 'smartRUtils', 'commonWorkflowService', function($scope, smartRUtils, commonWorkflowService) { 7 | 8 | commonWorkflowService.initializeWorkflow('correlation', $scope); 9 | 10 | $scope.fetch = { 11 | disabled: false, 12 | running: false, 13 | loaded: false, 14 | conceptBoxes: { 15 | datapoints: {concepts: [], valid: false}, 16 | annotations: {concepts: [], valid: true} 17 | } 18 | }; 19 | 20 | $scope.runAnalysis = { 21 | disabled: true, 22 | running: false, 23 | scriptResults: {}, 24 | params: { 25 | method: 'pearson', 26 | transformation: 'raw' 27 | } 28 | }; 29 | 30 | $scope.$watch('runAnalysis.params.transformation', function(newValue, oldValue) { 31 | // spearman and kendall are resistant to log transformation. Therefor the default to spearman if log used 32 | if (newValue !== oldValue && newValue !== 'raw' && $scope.runAnalysis.params.method === 'pearson') { 33 | $scope.runAnalysis.params.method = 'spearman'; 34 | } 35 | }); 36 | 37 | $scope.$watchGroup(['fetch.running', 'runAnalysis.running'], 38 | function(newValues) { 39 | var fetchRunning = newValues[0], 40 | runAnalysisRunning = newValues[1]; 41 | 42 | // clear old results 43 | if (fetchRunning) { 44 | $scope.runAnalysis.scriptResults = {}; 45 | } 46 | 47 | // disable tabs when certain criteria are not met 48 | $scope.fetch.disabled = runAnalysisRunning; 49 | $scope.runAnalysis.disabled = fetchRunning || !$scope.fetch.loaded; 50 | } 51 | ); 52 | 53 | }]); 54 | 55 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/heatmap.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=heatmap.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('HeatmapController', [ 6 | '$scope', 7 | 'commonWorkflowService', 8 | 'smartRUtils', 9 | function($scope, commonWorkflowService, smartRUtils) { 10 | 11 | commonWorkflowService.initializeWorkflow('heatmap', $scope); 12 | 13 | // ------------------------------------------------------------- // 14 | // Fetch data // 15 | // ------------------------------------------------------------- // 16 | $scope.fetch = { 17 | disabled: false, 18 | running: false, 19 | loaded: false, 20 | conceptBoxes: { 21 | highDimensional: {concepts: [], valid: false}, 22 | numeric: {concepts: [], valid: true}, 23 | categoric: {concepts: [], valid: true} 24 | }, 25 | selectedBiomarkers: [], 26 | scriptResults: {} 27 | }; 28 | 29 | // ------------------------------------------------------------- // 30 | // Preprocess // 31 | // ------------------------------------------------------------- // 32 | $scope.preprocess = { 33 | disabled: true, 34 | running: false, 35 | params: { 36 | aggregate: false 37 | }, 38 | scriptResults: {} 39 | }; 40 | 41 | // ------------------------------------------------------------- // 42 | // Run Heatmap // 43 | // ------------------------------------------------------------- // 44 | $scope.runAnalysis = { 45 | disabled: true, 46 | running: false, 47 | params: { 48 | selections: { 49 | selectedRownames: [], 50 | }, 51 | max_row: 100, 52 | sorting: 'nodes', 53 | ranking: 'coef', 54 | geneCardsAllowed: false, 55 | }, 56 | download: { 57 | disabled: true 58 | }, 59 | scriptResults: {} 60 | }; 61 | 62 | $scope.common = { 63 | totalSamples: 0, 64 | numberOfRows: 0, 65 | subsets: 0 66 | }; 67 | 68 | $scope.$watchGroup(['fetch.running', 'preprocess.running', 'runAnalysis.running'], function(newValues) { 69 | var fetchRunning = newValues[0], 70 | preprocessRunning = newValues[1], 71 | runAnalysisRunning = newValues[2]; 72 | 73 | // clear old results 74 | if (fetchRunning) { 75 | $scope.preprocess.scriptResults = {}; 76 | $scope.runAnalysis.scriptResults = {}; 77 | $scope.runAnalysis.params.ranking = ''; 78 | $scope.common.subsets = smartRUtils.countCohorts(); 79 | } 80 | 81 | // clear old results 82 | if (preprocessRunning) { 83 | $scope.runAnalysis.scriptResults = {}; 84 | $scope.runAnalysis.params.ranking = ''; 85 | $scope.common.subsets = smartRUtils.countCohorts(); 86 | } 87 | 88 | // disable tabs when certain criteria are not met 89 | $scope.fetch.disabled = preprocessRunning || runAnalysisRunning; 90 | $scope.preprocess.disabled = fetchRunning || runAnalysisRunning || !$scope.fetch.loaded; 91 | $scope.runAnalysis.disabled = fetchRunning || preprocessRunning || !$scope.fetch.loaded; 92 | 93 | // disable buttons when certain criteria are not met 94 | $scope.runAnalysis.download.disabled = runAnalysisRunning || 95 | $.isEmptyObject($scope.runAnalysis.scriptResults); 96 | 97 | // set ranking criteria 98 | if (!fetchRunning && 99 | !preprocessRunning && 100 | $scope.common.totalSamples < 2 && 101 | $scope.runAnalysis.params.ranking === '') { 102 | $scope.runAnalysis.params.ranking = 'mean'; 103 | } else if (!fetchRunning && 104 | !preprocessRunning && 105 | $scope.common.subsets < 2 && 106 | $scope.runAnalysis.params.ranking === '') { 107 | $scope.runAnalysis.params.ranking = 'coef'; 108 | } else if (!fetchRunning && 109 | !preprocessRunning && 110 | $scope.common.subsets > 1 && 111 | $scope.runAnalysis.params.ranking === '') { 112 | $scope.runAnalysis.params.ranking = 'adjpval'; 113 | } 114 | }); 115 | }]); 116 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/ipaconnector.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=ipaconnector.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('IpaconnectorController', [ 6 | '$scope', 7 | 'commonWorkflowService', 8 | 'smartRUtils', 9 | function($scope, commonWorkflowService, smartRUtils) { 10 | commonWorkflowService.initializeWorkflow('ipaconnector', $scope); 11 | 12 | $scope.debug = true; 13 | 14 | // ------------------------------------------------------------- // 15 | // Fetch data // 16 | // ------------------------------------------------------------- // 17 | $scope.fetch = { 18 | disabled: false, 19 | running: false, 20 | loaded: false, 21 | conceptBoxes: { 22 | highDimensional: {concepts: [], valid: false}, 23 | }, 24 | selectedBiomarkers: [], 25 | scriptResults: {} 26 | }; 27 | 28 | // ------------------------------------------------------------- // 29 | // IPA // 30 | // ------------------------------------------------------------- // 31 | function clearAll() { 32 | $scope.differentiallyExpressed = null 33 | } 34 | $scope.clearAll = clearAll; 35 | 36 | $scope.$watch('runAnalysis.params', function(newValue) { 37 | clearAll(); 38 | }, true); 39 | 40 | $scope.runAnalysis = { 41 | disabled: false, 42 | running: false, 43 | params: { 44 | significanceMeasure: 'pval', 45 | significanceCutoff: 0.05, 46 | fcCutoff: 1.5, 47 | }, 48 | ipaCredentials: { 49 | username: null, 50 | password: null, 51 | }, 52 | ipaConnectionIsSecure: window.location.protocol == 'https:' ? true : false, 53 | scriptResults: {} 54 | }; 55 | 56 | function listsToSimpleTable(lists) { 57 | if (lists == null) 58 | return null; 59 | 60 | var simpleTable; 61 | if ('header' in lists) { 62 | simpleTable = { 63 | header: lists.header, 64 | rows: [], 65 | }; 66 | if (lists.columnTypes != null) { 67 | simpleTable.columnTypes = lists.columnTypes; 68 | } 69 | // ensure all columns exist 70 | for (var c=0; c < simpleTable.header.length; c++) { 71 | if (! c in lists) 72 | throw "column "+c+" not in lists"; 73 | } 74 | } else { 75 | // NOTE this is not ordered 76 | simpleTable = { 77 | header: [], 78 | rows: [], 79 | }; 80 | for (var c in lists) { 81 | simpleTable.header.push(c); 82 | } 83 | } 84 | 85 | // empty 86 | if (simpleTable.header.length == 0 || lists[simpleTable.header[0]].length == 0) 87 | return null; 88 | 89 | for (var i=0; i < lists[simpleTable.header[0]].length; i++) { 90 | var row = [] 91 | for (var c=0; c < simpleTable.header.length; c++) { 92 | row.push(lists[simpleTable.header[c]][i]); 93 | } 94 | simpleTable.rows.push(row); 95 | } 96 | 97 | return simpleTable; 98 | } 99 | 100 | $scope.$watch('fetch.conceptBoxes.highDimensional', function(newValue) { 101 | $scope.differentiallyExpressed = null; 102 | }, true); 103 | 104 | $scope.$watch('runAnalysis.scriptResults', function(newValue) { 105 | $scope.differentiallyExpressed = listsToSimpleTable($scope.runAnalysis.scriptResults); 106 | }, true); 107 | 108 | $scope.common = { 109 | totalSamples: 0, 110 | numberOfRows: 0, 111 | subsets: 0 112 | }; 113 | 114 | $scope.$watchGroup(['fetch.running', 'fetch.loaded', 'runAnalysis.running'], function(newValues) { 115 | var fetchRunning = newValues[0], 116 | fetchLoaded = newValues[1], 117 | runAnalysisRunning = newValues[2]; 118 | 119 | // clear old results 120 | if (fetchRunning) { 121 | $scope.runAnalysis.scriptResults = {}; 122 | $scope.common.subsets = smartRUtils.countCohorts(); 123 | } 124 | 125 | // disable tabs when certain criteria are met 126 | $scope.fetch.disabled = runAnalysisRunning; 127 | $scope.runAnalysis.disabled = fetchRunning || !fetchLoaded; 128 | }); 129 | }]); 130 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/linegraph.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=linegraph.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('LinegraphController', 6 | ['$scope', 'smartRUtils', 'commonWorkflowService', function($scope, smartRUtils, commonWorkflowService) { 7 | 8 | commonWorkflowService.initializeWorkflow('linegraph', $scope); 9 | 10 | $scope.fetch = { 11 | disabled: false, 12 | running: false, 13 | loaded: false, 14 | selectedBiomarkers: [], 15 | button: { 16 | disabled: false 17 | }, 18 | conceptBoxes: { 19 | highData: {concepts: [], valid: true}, 20 | numData: {concepts: [], valid: true}, 21 | catData: {concepts: [], valid: true} 22 | } 23 | }; 24 | 25 | $scope.runAnalysis = { 26 | disabled: true, 27 | running: false, 28 | scriptResults: {}, 29 | params: {} 30 | }; 31 | 32 | $scope.$watch(function() { 33 | return $scope.fetch.conceptBoxes.highData.concepts.length + ' ' + $scope.fetch.selectedBiomarkers.length; 34 | }, 35 | function() { 36 | if ($scope.fetch.conceptBoxes.highData.concepts.length > 0 && 37 | ($scope.fetch.selectedBiomarkers.length === 0 || $scope.fetch.selectedBiomarkers.length > 10)) { 38 | $scope.fetch.button.disabled = true; 39 | $scope.fetch.button.message = 'Please select between 1 and 10 biomarker for your high dimensional data'; 40 | } else { 41 | $scope.fetch.button.disabled = false; 42 | $scope.fetch.button.message = ''; 43 | } 44 | }); 45 | 46 | $scope.$watchGroup(['fetch.running', 'runAnalysis.running'], 47 | function(newValues) { 48 | var fetchRunning = newValues[0], 49 | runAnalysisRunning = newValues[1]; 50 | 51 | // clear old results 52 | if (fetchRunning) { 53 | $scope.runAnalysis.scriptResults = {}; 54 | } 55 | 56 | // disable tabs when certain criteria are not met 57 | $scope.fetch.disabled = runAnalysisRunning; 58 | $scope.runAnalysis.disabled = fetchRunning || !$scope.fetch.loaded; 59 | } 60 | ); 61 | 62 | }]); 63 | 64 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/patientmapper.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=patientmapper.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('PatientmapperController', 6 | ['$scope', 'smartRUtils', 'commonWorkflowService', function($scope, smartRUtils, commonWorkflowService) { 7 | 8 | commonWorkflowService.initializeWorkflow('patientmapper', $scope); 9 | 10 | $scope.fetch = { 11 | disabled: false, 12 | running: false, 13 | loaded: false, 14 | conceptBoxes: { 15 | source: {concepts: [], valid: false}, 16 | target: {concepts: [], valid: false} 17 | } 18 | }; 19 | 20 | $scope.runAnalysis = { 21 | disabled: true, 22 | running: false, 23 | scriptResults: {}, 24 | params: {} 25 | }; 26 | 27 | 28 | $scope.$watch('runAnalysis.scriptResults', function(results) { 29 | if (!$.isEmptyObject(results)) { 30 | clearQuery(); 31 | results.cohortNodes.forEach(function(d) { 32 | var panel = document.querySelector('.panelModel[subset="' + d.subset + '"] .panelBoxList'); 33 | var concept = { 34 | name: d.name, 35 | key: d.key, 36 | level: d.level, 37 | tooltip: d.tooltip, 38 | dimcode: d.fullname, 39 | value: { 40 | mode: 'novalue', 41 | operator: 'LT', 42 | highlowselect: 'N', 43 | }, 44 | oktousevalues: 'N', 45 | nodeType: 'alphaicon', 46 | visualattributes: d.visualAttributes, 47 | applied_path: '@', 48 | modifiedNode: {} 49 | }; 50 | selectConcept(createPanelItemNew(panel, concept)); 51 | }); 52 | appendQueryPanelInto(1); 53 | appendQueryPanelInto(2); 54 | runAllQueries(function() { 55 | $.ajax({ 56 | url: pageInfo.basePath + '/chart/clearGrid', 57 | method: 'POST', 58 | data: { 59 | charttype: 'cleargrid', 60 | } 61 | }); 62 | }); 63 | } 64 | }); 65 | }]); 66 | 67 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/controllers/volcanoplot.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=volcanoplot.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.controller('VolcanoplotController', [ 6 | '$scope', 7 | 'smartRUtils', 8 | 'commonWorkflowService', 9 | function($scope, smartRUtils, commonWorkflowService) { 10 | 11 | commonWorkflowService.initializeWorkflow('volcanoplot', $scope); 12 | 13 | $scope.fetch = { 14 | disabled: false, 15 | running: false, 16 | loaded: false, 17 | conceptBoxes: { 18 | highDimensional: {concepts: [], valid: false} 19 | } 20 | }; 21 | 22 | $scope.runAnalysis = { 23 | params: {}, 24 | disabled: true, 25 | running: false, 26 | scriptResults: {} 27 | }; 28 | 29 | $scope.$watchGroup(['fetch.running', 'runAnalysis.running'], function(newValues) { 30 | var fetchRunning = newValues[0], 31 | runAnalysisRunning = newValues[1]; 32 | 33 | // clear old results 34 | if (fetchRunning) { 35 | $scope.runAnalysis.scriptResults = {}; 36 | } 37 | 38 | // disable tabs when certain criteria are not met 39 | $scope.fetch.disabled = runAnalysisRunning; 40 | $scope.runAnalysis.disabled = fetchRunning || !$scope.fetch.loaded; 41 | }); 42 | 43 | }]); 44 | 45 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/capturePlotButton.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=capturePlotButton.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('capturePlotButton', [function() { 6 | 7 | return { 8 | restrict: 'E', 9 | scope: { 10 | disabled: '=?', 11 | filename: '@', 12 | target: '@' 13 | }, 14 | template: '', 15 | link: function(scope, elements) { 16 | if (!scope.filename) { 17 | // default filename 18 | scope.filename = 'image.svg'; 19 | } 20 | 21 | 22 | var template_btn = elements.children()[0]; 23 | template_btn.addEventListener('click', function() { 24 | var svg = $(scope.target + ' svg'); 25 | if (!svg.length) { 26 | return; 27 | } 28 | 29 | var smartRSheets = []; 30 | for (var i = 0; i < document.styleSheets.length; i++) { 31 | var sheet = document.styleSheets[i]; 32 | if (sheet.href && sheet.href.indexOf('smart-r') !== -1) { 33 | smartRSheets.push(sheet); 34 | } 35 | } 36 | 37 | var rules = []; 38 | smartRSheets.forEach(function(d) { 39 | for (var key in d.cssRules) { 40 | if (d.cssRules.hasOwnProperty(key) && d.cssRules[key] instanceof CSSStyleRule) { 41 | rules.push(d.cssRules[key].cssText); 42 | } 43 | } 44 | }); 45 | 46 | function b64toBlob(b64Data, contentType, sliceSize) { 47 | contentType = contentType || ''; 48 | sliceSize = sliceSize || 512; 49 | 50 | var byteCharacters = atob(b64Data); 51 | var byteArrays = []; 52 | 53 | for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { 54 | var slice = byteCharacters.slice(offset, offset + sliceSize); 55 | 56 | var byteNumbers = new Array(slice.length); 57 | for (var i = 0; i < slice.length; i++) { 58 | byteNumbers[i] = slice.charCodeAt(i); 59 | } 60 | 61 | var byteArray = new Uint8Array(byteNumbers); 62 | 63 | byteArrays.push(byteArray); 64 | } 65 | 66 | var blob = new Blob(byteArrays, {type: contentType}); 67 | return blob; 68 | } 69 | 70 | var defs = ''; 71 | svg.attr({version: '1.1' , xmlns:"http://www.w3.org/2000/svg"}); 72 | svg.append(defs); 73 | $(scope.target + ' svg').wrap('
'); 74 | var b64 = btoa(unescape(encodeURIComponent($('#sr-capture-container').html()))); 75 | var blob = b64toBlob(b64, 'image/svg+xml'); 76 | var blobUrl = URL.createObjectURL(blob); 77 | var a = document.createElement('a'); 78 | a.style = 'display: none'; 79 | a.href = blobUrl; 80 | a.download = scope.filename; 81 | a.click(); 82 | URL.revokeObjectURL(blobUrl); 83 | $(scope.target + ' svg').unwrap(); 84 | $(scope.target + ' svg defs').remove(); 85 | }); 86 | } 87 | }; 88 | }]); 89 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/cohortSummaryInfo.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=cohortSummaryInfo.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('cohortSummaryInfo', [function() { 6 | 7 | return { 8 | restrict: 'E', 9 | template: '', 10 | controller: function($scope, $element) { 11 | var span = $element.children()[0]; 12 | 13 | function _showCohortInfo() { 14 | var cohortsSummary = ''; 15 | 16 | for(var i = 1; i <= GLOBAL.NumOfSubsets; i++) { 17 | var currentQuery = getQuerySummary(i); 18 | if(currentQuery !== '') { 19 | cohortsSummary += '
Subset ' + (i) + ':
'; 20 | cohortsSummary += currentQuery; 21 | cohortsSummary += '
'; 22 | } 23 | } 24 | if (!cohortsSummary) { 25 | cohortsSummary = '
WARNING: No subsets have been selected! Please go to the "Comparison" tab and select your subsets.'; 26 | } 27 | 28 | span.innerHTML = cohortsSummary; 29 | } 30 | 31 | // Trigger for update is clicking the SmartR panel item. Maybe there is a more elegant way? 32 | $scope.$evalAsync(function() { 33 | _showCohortInfo(); // set it one time initially 34 | $('#resultsTabPanel__smartRPanel').on('click', function() { 35 | _showCohortInfo(); 36 | }); 37 | }); 38 | } 39 | }; 40 | 41 | }]); 42 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/downloadResultsButton.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=downloadResultsButton.js 2 | 3 | window.smartRApp.directive('downloadResultsButton', ['rServeService', function(rServeService) 4 | { 5 | function downloadFile(data) { 6 | var link = jQuery('') 7 | .attr('href', rServeService.urlForFile(data.executionId, 'analysis_data.zip')) 8 | .attr('download', 'analysis_data.zip') 9 | .css('display', 'none'); 10 | jQuery('body').append(link); 11 | link[0].click(); 12 | link.remove(); 13 | } 14 | 15 | return { 16 | restrict: 'E', 17 | scope: { 18 | disabled: '=' 19 | }, 20 | template: 21 | '' + 22 | '', 23 | link: function(scope, element) { 24 | 25 | var template_btn = element.children()[0]; 26 | var template_msg = element.children()[1]; 27 | 28 | template_btn.disabled = scope.disabled; 29 | 30 | scope.$watch('disabled', function (newValue) { 31 | template_btn.disabled = newValue; 32 | }, true); 33 | 34 | template_btn.onclick = function() { 35 | 36 | template_msg.innerHTML = 'Download data, please wait _'; 37 | 38 | rServeService.startScriptExecution({ 39 | taskType: 'downloadData', 40 | arguments: {} 41 | }).then( 42 | function (data){ 43 | // download file 44 | template_msg.innerHTML = ''; 45 | downloadFile(data); 46 | }, 47 | function (msg){ 48 | template_msg.innerHTML = 'Failure: ' + msg; 49 | } 50 | ) 51 | }; 52 | } 53 | }; 54 | }]); 55 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/ngTranscludeReplace.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=ngTranscludeReplace.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('ngTranscludeReplace', ['$log', function ($log) { 6 | return { 7 | terminal: true, 8 | restrict: 'EA', 9 | 10 | link: function ($scope, $element, $attr, ctrl, transclude) { 11 | if (!transclude) { 12 | $log.error('orphan', 13 | 'Illegal use of ngTranscludeReplace directive in the template! ' + 14 | 'No parent directive that requires a transclusion found. '); 15 | return; 16 | } 17 | transclude(function (clone) { 18 | if (clone.length) { 19 | $element.replaceWith(clone); 20 | } 21 | else { 22 | $element.remove(); 23 | } 24 | }); 25 | } 26 | }; 27 | }]); 28 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/preprocessButton.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=preprocessButton.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('preprocessButton', [ 6 | 'rServeService', 7 | '$rootScope', 8 | function(rServeService, $rootScope) { 9 | return { 10 | restrict: 'E', 11 | scope: { 12 | running: '=?', 13 | params: '=?', 14 | showSummaryStats: '=', 15 | summaryData: '=', 16 | allSamples: '=?', 17 | numberOfRows: '=?', 18 | projection: '@?' 19 | }, 20 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/preprocessButton.html', 21 | link: function(scope, element) { 22 | 23 | var template_btn = element.children()[0]; 24 | var template_msg = element.children()[1]; 25 | 26 | var _onSuccess = function() { 27 | template_msg.innerHTML = 'Task complete! Go to the "Run Analysis" tab to continue.'; 28 | template_btn.disabled = false; 29 | scope.running = false; 30 | }; 31 | 32 | var _onFail = function(msg) { 33 | template_msg.innerHTML = 'Error: ' + msg; 34 | template_btn.disabled = false; 35 | scope.running = false; 36 | }; 37 | 38 | // we add this conditional $watch because there is some crazy promise resolving for allSamples 39 | // going on. This is a workaround which observes allSamples and uses it as criteria for successful 40 | // completion. FIXME 41 | scope.$watch('summaryData', function(newValue) { 42 | if (scope.summaryData && 43 | scope.showSummaryStats && 44 | scope.running && 45 | Object.keys(newValue).indexOf('subsets') !== -1) { 46 | scope.allSamples = newValue.allSamples; 47 | scope.numberOfRows = newValue.numberOfRows; 48 | _onSuccess(); 49 | } 50 | }, true); 51 | 52 | var _showSummaryStats = function() { 53 | template_msg.innerHTML = 'Execute summary statistics, please wait _'; 54 | rServeService.executeSummaryStats('preprocess', scope.projection).then( 55 | function (data) { 56 | scope.summaryData = data.result; // this will trigger $watch 57 | }, 58 | _onFail 59 | ); 60 | }; 61 | 62 | template_btn.onclick = function() { 63 | scope.summaryData = {}; 64 | scope.disabled = true; 65 | scope.running = true; 66 | template_msg.innerHTML = 'Preprocessing, please wait _'; 67 | 68 | var params = scope.params ? scope.params : {}; 69 | rServeService.preprocess(params).then( 70 | scope.showSummaryStats ? _showSummaryStats : _onSuccess, 71 | _onFail 72 | ); 73 | }; 74 | } 75 | }; 76 | }]); 77 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/runButton.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=runButton.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('runButton', [ 6 | '$rootScope', 7 | 'rServeService', 8 | 'smartRUtils', 9 | function($rootScope, rServeService, smartRUtils) { 10 | return { 11 | restrict: 'E', 12 | scope: { 13 | running: '=?', 14 | storage: '=storeResultsIn', 15 | script: '@scriptToRun', 16 | name: '@buttonName', 17 | filename: '@?', 18 | params: '=?argumentsToUse', 19 | waitMessage: '@?' 20 | }, 21 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/runButton.html', 22 | link: function(scope, element) { 23 | var params = scope.params ? scope.params : {}; 24 | if (!scope.waitMessage) { 25 | scope.waitMessage = 'Creating plot, please wait'; 26 | } 27 | 28 | var template_btn = element.children()[0], 29 | template_msg = element.children()[1]; 30 | 31 | var _onSuccess = function(data) { 32 | scope.storage = data; 33 | template_msg.innerHTML = ''; 34 | scope.disabled = false; 35 | scope.running = false; 36 | }; 37 | 38 | var _onFail = function(msg) { 39 | template_msg.innerHTML = 'Error: ' + msg; 40 | scope.disabled = false; 41 | scope.running = false; 42 | }; 43 | 44 | var _prepareResults = function(response) { 45 | if (scope.filename) { 46 | // when filename is specified it is assumed that results are serialized 47 | // if results are serialized, we need to deserialized them by 48 | // downloading the results files. 49 | rServeService.downloadJsonFile(response.executionId, scope.filename).then( 50 | function(d) { _onSuccess(d.data); }, 51 | _onFail 52 | ); 53 | } else { 54 | _onSuccess(JSON.parse(response.result.artifacts.value)); 55 | } 56 | }; 57 | 58 | template_btn.onclick = function() { 59 | smartRUtils.cleanUp(); 60 | scope.disabled = true; 61 | scope.storage = {}; 62 | scope.running = true; 63 | template_msg.innerHTML = scope.waitMessage + ' _'; 64 | 65 | rServeService.startScriptExecution({ 66 | taskType: scope.script, 67 | arguments: params 68 | }).then( 69 | _prepareResults, 70 | _onFail 71 | ); 72 | }; 73 | } 74 | }; 75 | }]); 76 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/sortingCriteria.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=sortingCriteria.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('sortingCriteria', [ 6 | '$rootScope', 7 | function($rootScope) { 8 | return { 9 | restrict: 'E', 10 | scope: { 11 | criteria : '=', 12 | samples: '=', 13 | subsets: '=' 14 | }, 15 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/sortingCriteria.html' 16 | }; 17 | } 18 | ]); 19 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/summaryStatistics.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=summaryStatistics.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('summaryStats', [ 6 | '$rootScope', 7 | function($rootScope) { 8 | return { 9 | restrict: 'E', 10 | scope: { 11 | summaryData: '=' 12 | }, 13 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/summaryStatistics.html' 14 | }; 15 | } 16 | ]); 17 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/tabContainer.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=tabContainer.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('tabContainer', 6 | ['$rootScope', 'smartRUtils', '$timeout', function($rootScope, smartRUtils, $timeout) { 7 | return { 8 | restrict: 'E', 9 | transclude: true, 10 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/tabContainer.html', 11 | controller: function($scope) { 12 | $scope.tabs = []; 13 | this.addTab = function(tab) { 14 | $scope.tabs.push(tab); 15 | }; 16 | }, 17 | link: function() { 18 | $timeout(function() { // init jQuery UI tabs after DOM has rendered 19 | $('#heim-tabs').tabs(); 20 | }); 21 | } 22 | }; 23 | }]); 24 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/workflowControls.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=workflowControls.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('workflowControls', [ 6 | '$rootScope', 7 | 'smartRUtils', 8 | function($rootScope, smartRUtils) { 9 | return { 10 | restrict: 'E', 11 | transclude: true, 12 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/workflowControls.html', 13 | link: function(scope, element) { 14 | var controls = element.children()[0]; 15 | var scrollbarWidth = smartRUtils.getScrollBarWidth(); 16 | controls.style.bottom = scrollbarWidth + 'px'; 17 | controls.style.right = scrollbarWidth + 105 + 'px'; 18 | } 19 | }; 20 | } 21 | ]); 22 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/workflowTab.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=workflowTab.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('workflowTab', ['smartRUtils', function(smartRUtils) { 6 | return { 7 | restrict: 'E', 8 | scope: { 9 | name: '@tabName', 10 | disabled: '=' 11 | }, 12 | require: '^tabContainer', 13 | transclude: true, 14 | template: '', 15 | link: function(scope, element, attrs, tabContainerCtrl) { 16 | var id = 'fragment-' + smartRUtils.makeSafeForCSS(scope.name); 17 | scope.id = id; 18 | element[0].id = id; 19 | tabContainerCtrl.addTab(scope); 20 | } 21 | }; 22 | }]); 23 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/directives/workflowWarnings.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=workflowWarnings.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('workflowWarnings', [ 6 | '$rootScope', 7 | function($rootScope) { 8 | return { 9 | restrict: 'E', 10 | scope: { 11 | warnings: '=' 12 | }, 13 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/workflowWarnings.html', 14 | link: function(scope) { 15 | scope.$watch('warnings', function() { 16 | scope.visibility = $.isEmptyObject(scope.warnings) ? 'hidden' : 'visible'; 17 | scope.text = ''; 18 | for (var warn in scope.warnings) { 19 | if (scope.warnings.hasOwnProperty(warn)) { 20 | scope.text += scope.warnings[warn] + '\n'; 21 | } 22 | } 23 | }, true); 24 | } 25 | }; 26 | } 27 | ]); 28 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/services/commonWorkflowService.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=commonWorkflowService.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.factory('commonWorkflowService', ['rServeService', '$css', function(rServeService, $css) { 6 | 7 | var service = {}; 8 | 9 | service.initializeWorkflow = function(workflowName, scope) { 10 | service.currentScope = scope; 11 | // load workflow specific css 12 | $css.bind({ 13 | href: scope.smartRPath + '/css/' + workflowName + '.css' 14 | }, scope); 15 | 16 | rServeService.destroyAndStartSession(workflowName); 17 | }; 18 | 19 | return service; 20 | 21 | }]); 22 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/smartRApp.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=smartRApp.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp = angular.module('smartRApp', ['ngRoute', 'door3.css', 'ipaApi']) 6 | .config(['$httpProvider', function($httpProvider) { 7 | //initialize get if not there 8 | if (!$httpProvider.defaults.headers.get) { 9 | $httpProvider.defaults.headers.get = {}; 10 | } 11 | //disable IE ajax request caching 12 | $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; 13 | }]) 14 | .run(function($rootScope, $http) { 15 | // get plugin context path and put it in root scope 16 | $http.get(pageInfo.basePath + '/SmartR/smartRContextPath').then( 17 | function(d) { $rootScope.smartRPath = d.data; }, 18 | function(msg) { throw 'Error: ' + msg; } 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/biomarkerSelection.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | Biomarker can be a gene, pathway, mirID or UniProtID. 6 |
7 |
  • 8 |
    9 | {{biomarker.type}} 10 | {{biomarker.name}} 11 | {{biomarker.synonyms}} 12 |
    13 | 14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/boxplot.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/conceptBox.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | Drag at least {{min}} node(s) into the box
5 | Select at most {{max}} node(s)
6 | Node(s) do not have the correct type
7 | Nodes must have the same platform 8 |
10 |
11 |
12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/fetchButton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/heatmap.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 | 12 | 13 |
14 |
15 |
24 |
25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 |
34 | 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/ipaApi.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{ ipaState.launchIpaMsg }} _ 5 |
6 | 7 |
8 | Analysis name in IPA: {{ ipaState.analysisName }} 9 |
10 | 11 |
12 |
Limma
13 |
Canonical Pathways
14 |
Upstream Regulators
15 |
Tox Functions
16 |
Networks
17 |
Diseases and Bio Functions
18 |
Analysis Ready Molecules
19 |
20 | 25 |
26 |
27 | 31 |
32 |
33 | 37 |
38 |
39 | 43 |
44 |
45 | 49 |
50 |
51 | 55 |
56 |
57 | 62 |
63 |
64 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/linegraph.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/preprocessButton.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/runButton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/sortingCriteria.html: -------------------------------------------------------------------------------- 1 |

Ranking Criteria:

2 |
3 |
4 |

Expression variability

5 |
8 |
11 |
15 |
16 |
17 |

Expression level

18 |
21 |
24 |
25 |
26 |
27 |
28 |

Differential expression

29 |
32 |
35 |
38 |
41 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/summaryStatistics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 22 | 27 | 28 |
PlotLabelsSubset 1Subset 2
13 |
    14 |
  • {{key}} :
  • 15 |
16 |
18 |
    19 |
  • {{value}}
  • 20 |
21 |
23 |
    24 |
  • {{value}}
  • 25 |
26 |
29 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/tabContainer.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/workflowControls.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/templates/workflowWarnings.html: -------------------------------------------------------------------------------- 1 |
2 | {{text}} 3 |
4 | -------------------------------------------------------------------------------- /web-app/js/smartR/_angular/viz/plotlyBoxplot.js: -------------------------------------------------------------------------------- 1 | //# sourceURL=plotlyBoxplot.js 2 | 3 | 'use strict'; 4 | 5 | window.smartRApp.directive('boxplot', [ 6 | 'smartRUtils', 7 | 'rServeService', 8 | '$rootScope', 9 | function(smartRUtils, rServeService, $rootScope) { 10 | 11 | return { 12 | restrict: 'E', 13 | scope: { 14 | data: '=' 15 | }, 16 | templateUrl: $rootScope.smartRPath + '/js/smartR/_angular/templates/boxplot.html', 17 | link: function (scope, element) { 18 | var vizDiv = element.children()[0]; 19 | /** 20 | * Watch data model (which is only changed by ajax calls when we want to (re)draw everything) 21 | */ 22 | scope.$watch('data', function () { 23 | $(vizDiv).empty(); 24 | if (! $.isEmptyObject(scope.data)) { 25 | createBoxplot(scope, vizDiv); 26 | } 27 | }); 28 | } 29 | }; 30 | 31 | function createBoxplot(scope, vizDiv) { 32 | var cf = crossfilter(scope.data.dataMatrix); 33 | var byValue = cf.dimension(function(d) { return d.value; }); 34 | var byBioMarker = cf.dimension(function(d) { return d.bioMarker; }); 35 | 36 | var plotData = []; 37 | smartRUtils.unique(smartRUtils.getValuesForDimension(byBioMarker)).forEach(function(bioMarker) { 38 | byBioMarker.filterExact(bioMarker); 39 | plotData.push({ 40 | type: 'box', 41 | y: smartRUtils.getValuesForDimension(byValue), 42 | name: bioMarker, 43 | boxpoints: 'all', 44 | boxmean: 'sd', 45 | jitter: 0.5 46 | }); 47 | byBioMarker.filterAll(); 48 | }); 49 | 50 | var title = 'Boxplots (' + scope.data.transformation + ')'; 51 | title += scope.data.pValue ? ' ANOVA pValue = ' + scope.data.pValue : ''; 52 | var layout = { 53 | title: title, 54 | height: 800 55 | }; 56 | Plotly.newPlot(vizDiv, plotData, layout); 57 | } 58 | }]); 59 | 60 | -------------------------------------------------------------------------------- /web-app/js/smartR/smartR.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Avoid `console` errors in browsers that lack a console. 4 | (function() { 5 | var method; 6 | var noop = function () {}; 7 | var methods = [ 8 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 9 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 10 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 11 | 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn' 12 | ]; 13 | var length = methods.length; 14 | console = (window.console = window.console || {}); // jshint ignore:line 15 | 16 | while (length--) { 17 | method = methods[length]; 18 | 19 | // Only stub undefined methods. 20 | if (!console[method]) { 21 | console[method] = noop; 22 | } 23 | } 24 | }()); 25 | 26 | window.smartRPanel = new Ext.Panel({ 27 | id: 'smartRPanel', 28 | title: 'SmartR', 29 | region: 'center', 30 | split: true, 31 | height: 90, 32 | layout: 'fit', 33 | collapsible: true, 34 | autoScroll: true, 35 | tbar: new Ext.Toolbar({ 36 | id: 'smartRToolbar', 37 | title: 'R Scripts', 38 | items: [] 39 | }), 40 | listeners: { 41 | render: function (panel) { 42 | /** 43 | * WORKAROUND : code below is needed to reorder the javascript script load that're broken due to 44 | * ExtJS panel 45 | */ 46 | // start workaround 47 | var updater = panel.getUpdater(); 48 | updater.on('update', function() { 49 | var panelBody = jQuery(arguments[0].dom); 50 | var scripts = panelBody.children('script'); 51 | scripts.remove(); // remove scripts from panel body 52 | panelBody.append(scripts); // re-append again 53 | }); 54 | updater.update({ 55 | url: pageInfo.basePath + '/smartR/index', 56 | method: 'POST', 57 | scripts: false }); 58 | // end workaround 59 | } 60 | } 61 | }); 62 | 63 | window.addSmartRPanel = function addSmartRPanel(parentPanel) { 64 | parentPanel.insert(4, window.smartRPanel); 65 | }; 66 | 67 | function cleanUpSmartR() { 68 | var d3tips = document.getElementsByClassName('d3-tip'); 69 | Array.prototype.forEach.call(d3tips, function(el) { // d3tips is array-like object 70 | el.parentNode.removeChild(el); 71 | }); 72 | } 73 | cleanUpSmartR(); 74 | 75 | --------------------------------------------------------------------------------