Changeset 3


Ignore:
Timestamp:
Jan 12, 2011, 9:45:08 PM (13 years ago)
Author:
robert@…
Message:

Externalized configuration; improved assay view (detail views of runs and samples); implemented uploading and parsing of FASTA and QUAL files

Location:
trunk
Files:
13 added
1 deleted
19 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk

    • Property svn:ignore
      •  

        old new  
        44.classpath
        55.project
         6fileuploads
  • trunk/grails-app/conf/BaseFilters.groovy

    r2 r3  
    2121class BaseFilters {
    2222        def gscfService
     23        def synchronizationService
    2324       
    2425        // define filters
     
    9394                }
    9495               
     96                fullSynchronization(controller:'*', action:'*') {
     97                        before = {
     98                                // Never perform full synchronization on rest call when the synchronize controller is used
     99                                if( controllerName == "rest" || controllerName == "synchronize" ) {
     100                                        return true;
     101                                }
     102
     103                                // Never perform synchronization on a POST request
     104                                if( request.method == "POST" )
     105                                        return true;
     106                                       
     107                                if( synchronizationService.timeForFullSynchronization() ) {
     108                                        redirect( url: synchronizationService.urlForFullSynchronization( params ) );
     109                                        return false
     110                                }
     111                               
     112                                return true
     113
     114                        }
     115                }
     116               
    95117                defineStyle(controller: '*', action: '*') {
    96118                        // before every execution
  • trunk/grails-app/conf/Config.groovy

    r2 r3  
     1// ##################################################################################
     2// DO NOT Change these settings!!!
     3// You can overwrite them by adding your own configuration file in ~/.grails/[environment]-[appName].properties
     4
    15// locations to search for config files that get merged into the main config
    26// config files can either be Java properties files or ConfigSlurper scripts
     7grails.config.locations = [
     8        // the default per-environment configuration
     9        // (e.g. grails-app/conf/config-development.properties)
     10        "classpath:config-${grails.util.GrailsUtil.environment}.properties",
     11        "file:${ basedir }/grails-app/conf/config-${grails.util.GrailsUtil.environment}.properties",
     12        "classpath:config-${grails.util.GrailsUtil.environment}.groovy",
     13       
     14        // the external configuration to override the default
     15        // configuration (e.g. ~/.grails-config/ci-gscf.properties)
     16        "file:${userHome}/.grails-config/${grails.util.GrailsUtil.environment}-${appName}.properties",
     17        "file:${userHome}/.grails-config/${grails.util.GrailsUtil.environment}-${appName}.groovy",
     18        "file:${userHome}/.grails/${grails.util.GrailsUtil.environment}-${appName}.properties",
     19        "file:${userHome}/.grails/${grails.util.GrailsUtil.environment}-${appName}.groovy"
     20]
    321
    4 // grails.config.locations = [ "classpath:${appName}-config.properties",
    5 //                             "classpath:${appName}-config.groovy",
    6 //                             "file:${userHome}/.grails/${appName}-config.properties",
    7 //                             "file:${userHome}/.grails/${appName}-config.groovy"]
     22// Add extra location if desired
     23if(System.properties["${appName}.config.location"]) {
     24        grails.config.locations << "file:" + System.properties["${appName}.config.location"]
     25}
    826
    9 // if(System.properties["${appName}.config.location"]) {
    10 //    grails.config.locations << "file:" + System.properties["${appName}.config.location"]
    11 // }
     27// Whether to synchronize with gscf or not. By default, synchronization is turned on
     28metagenomics.synchronization = true
     29
     30// By default, Metagenomics only fetches studies that have been changed by GSCF. GSCF sends
     31// notifications to the module, when something changes. However, it is possible that these messages
     32// can't be delivered or some other error happens. In that case, the data in the module is out of sync.
     33// To prevent that, every now and then a complete synchronization is performed. The frequency of this
     34// operation can be set by the following parameter. The parameter determines the number of seconds
     35// between two consecutive full synchronizations. Setting it to zero disables full synchronization, but
     36// is strongly discouraged.
     37metagenomics.fullSynchronization = 24 * 3600 // 1 day
     38
     39// Temporary directory to upload files to.
     40// If the directory is given relative (e.g. 'fileuploads/temp'), it is taken relative to the web-app directory
     41// Otherwise, it should be given as an absolute path (e.g. '/home/user/sequences')
     42// The directory should be writable by the webserver user
     43metagenomics.fileUploadDir = "filuploads/temp"
     44
     45// Maximum age that uploaded files should be kept on the server before deleting them. When a user uploads a file,
     46// but doesn't process the file (e.g. because he leaves the page beforehand or his computer crashes), the files
     47// remain in the upload directory. This mechanism ensures that the upload directory is cleaned after some time.
     48// The time is given in seconds.
     49metagenomics.fileUploadMaxAge = 2 * 24 * 3600 // 2 days
     50
     51// Directory to save the uploaded files permanently
     52// If the directory is given relative (e.g. 'fileuploads/temp'), it is taken relative to the web-app directory
     53// Otherwise, it should be given as an absolute path (e.g. '/home/user/sequences')
     54// The directory should be writable by the webserver user
     55metagenomics.fileDir = "fileuploads/permanent"
     56
     57// Path in GSCF that is used after baseURL for adding a new study
     58gscf.addStudyPath = "/studyWizard/index?jump=create"
    1259
    1360grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
     
    3582grails.views.gsp.encoding = "UTF-8"
    3683grails.converters.encoding = "UTF-8"
     84
    3785// enable Sitemesh preprocessing of GSP pages
    3886grails.views.gsp.sitemesh.preprocess = true
    39 // scaffolding templates configuration
    40 grails.scaffolding.templates.domainSuffix = 'Instance'
    4187
    4288// Set to false to use the new Grails 1.2 JSONBuilder in the render method
    4389grails.json.legacy.builder = false
     90
    4491// enabled native2ascii conversion of i18n properties files
    4592grails.enable.native2ascii = true
     93
    4694// whether to install the java.util.logging bridge for sl4j. Disable for AppEngine!
    4795grails.logging.jul.usebridge = true
     96
    4897// packages to include in Spring bean scanning
    4998grails.spring.bean.packages = []
    50 
    51 // ##################################################################################
    52 // DO NOT Change these settings!!!
    53 // You can overwrite them by adding a /grails-app/conf/LocalConfig.groovy.
    54 // Use /grails-app/conf/_LocalConfig.groovy as an example!
    55 // Make sure to set SVN to ignore this file!
    56 
    57 // Whether to synchronize with gscf or not. By default, synchronization is turned on
    58 metagenomics.synchronization = true
    59 
    60 // Temporary directory to upload files to.
    61 // If the directory is given relative (e.g. 'fileuploads/temp'), it is taken relative to the web-app directory
    62 // Otherwise, it should be given as an absolute path (e.g. '/home/user/sequences')
    63 // The directory should be writable by the webserver user
    64 metagenomics.fileUploadDir = "fileuploads"
    65 
    66 // Maximum age that uploaded files should be kept on the server before deleting them. When a user uploads a file,
    67 // but doesn't process the file (e.g. because he leaves the page beforehand or his computer crashes), the files
    68 // remain in the upload directory. This mechanism ensures that the upload directory is cleaned after some time.
    69 // The time is given in seconds.
    70 metagenomics.fileUploadMaxAge = 2 * 24 * 3600
    71 
    72 // Directory to save the uploaded files permanently
    73 // If the directory is given relative (e.g. 'fileuploads/temp'), it is taken relative to the web-app directory
    74 // Otherwise, it should be given as an absolute path (e.g. '/home/user/sequences')
    75 // The directory should be writable by the webserver user
    76 metagenomics.fileDir = "/home/robert/projects/grails/metagenomics/sequences"
    77 
    78 // Path in GSCF that is used after baseURL for adding a new study
    79 gscf.addStudyPath = "/studyWizard/index?jump=create"
    80 
    81 environments { // set per-environment serverURL stem for creating absolute links
    82         development {
    83                 //development Environment (German Server)
    84                 grails.serverURL = "http://localhost:8184"
    85 
    86                 gscf.baseURL = "http://localhost:8080/gscf"
    87                 //gscf.baseURL = "http://ci.gscf.nmcdsp.org"
    88                 metagenomics.baseURL = "http://localhost:8184/metagenomics"
    89                 metagenomics.consumerID = metagenomics.baseURL
    90 
    91                 // Turn off synchronization in development mode
    92                 metagenomics.synchronization = true
    93         }
    94         ci {
    95                 //CI Environment (German Server)
    96                 grails.serverURL = "http://ci.metagenomics.nmcdsp.org"
    97                 gscf.baseURL = "http://ci.gscf.nmcdsp.org"
    98                 metagenomics.baseURL = "http://ci.metagenomics.nmcdsp.org"
    99                 metagenomics.consumerID = metagenomics.baseURL
    100         }
    101         test {
    102                 //Test Environment (German Server)
    103                 grails.serverURL = "http://test.gscf.nmcdsp.org"
    104                 gscf.baseURL = "http://test.gscf.nmcdsp.org"
    105                 metagenomics.baseURL = "http://test.metagenomics.nmcdsp.org"
    106                 metagenomics.consumerID = metagenomics.baseURL
    107         }
    108         nbx14 {
    109                 // nbx14 instance
    110                 grails.serverURL = "http://nbx14.osx.eu"
    111                 gscf.baseURL = "http://nbx14.osx.eu"
    112                 metagenomics.baseURL = "http://metagenomics.nbx14.osx.eu"
    113                 metagenomics.consumerID = metagenomics.baseURL
    114         }
    115         demo {
    116                 // demo instance
    117                 grails.serverURL = "http://demo.nbx14.osx.eu"
    118                 gscf.baseURL = "http://demo.nbx14.osx.eu"
    119                 metagenomics.baseURL = "http://demo.metagenomics.nbx14.osx.eu"
    120                 metagenomics.consumerID = metagenomics.baseURL
    121         }
    122         production {
    123                 //Production Environment (German Server)
    124                 grails.serverURL = "http://www.nmcdsp.org"
    125                 gscf.baseURL = "http://www.nmcdsp.org"
    126                 metagenomics.baseURL = "http://metagenomics.nmcdsp.org"
    127                 metagenomics.consumerID = metagenomics.baseURL
    128         }
    129         www {
    130                 //Production Environment used in deploy scripts (German Server)
    131                 grails.serverURL = "http://metagenomics.nmcdsp.org"
    132                 gscf.baseURL = "http://www.nmcdsp.org"
    133                 metagenomics.baseURL = "http://metagenomics.nmcdsp.org"
    134                 metagenomics.consumerID = metagenomics.baseURL
    135         }
    136 }
    137 // ##################################################################################
    138 
    13999
    140100// log4j configuration
     
    167127grails.views.javascript.library = "jquery"
    168128jquery.version = '1.4.4'
    169 
    170 // Include local config to overwrite defaults
    171 def localConfigFile = "${System.properties['base.dir']}/grails-app/conf/LocalConfig.groovy"
    172 
    173 if (new File(localConfigFile)?.canRead()) {
    174         grails.config.locations << "file:${localConfigFile}"
    175 }
  • trunk/grails-app/conf/DataSource.groovy

    r2 r3  
    1010    cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
    1111}
    12 // environment specific settings
    13 environments {
    14     development {
    15         dataSource {
    16             //dbCreate = "create-drop" // one of 'create', 'create-drop','update'
    17             //url = "jdbc:hsqldb:mem:devDB"
    18                        
    19                         dbCreate = "update"
    20                         username = "metagenomics"
    21                         password = "ETBryeunTczeHYDt"
    22 
    23                         // MySQL
    24                         driverClassName = "com.mysql.jdbc.Driver"
    25                         url = "jdbc:mysql://localhost/metagenomics"
    26         }
    27     }
    28         ci {
    29                 // used by deploy scripts
    30                 dataSource {
    31                         dbCreate = "update"
    32                         username = "metagenomics"
    33                         password = "metagenomics"
    34 
    35                         // PostgreSQL
    36                         driverClassName = "org.postgresql.Driver"
    37                         url = "jdbc:postgresql://localhost:5432/metagenomics-ci"
    38                         dialect = org.hibernate.dialect.PostgreSQLDialect
    39                 }
    40         }
    41         test {
    42                 // used by deploy scripts
    43                 dataSource {
    44             dbCreate = "create-drop" // one of 'create', 'create-drop','update'
    45             url = "jdbc:hsqldb:mem:devDB"
    46                 }
    47         }
    48         nbx14 {
    49                 // used by deploy scripts
    50                 dataSource {
    51                         dbCreate = "update"
    52                         username = "metagenomics"
    53                         password = "metagenomics"
    54 
    55                         // PostgreSQL
    56                         driverClassName = "org.postgresql.Driver"
    57                         url = "jdbc:postgresql://localhost:5432/metagenomics-nbx14"
    58                         dialect = org.hibernate.dialect.PostgreSQLDialect
    59                 }
    60         }
    61         demo {
    62                 // used by deploy scripts
    63                 dataSource {
    64                         dbCreate = "update"
    65                         username = "metagenomics"
    66                         password = "metagenomics"
    67 
    68                         // PostgreSQL
    69                         driverClassName = "org.postgresql.Driver"
    70                         url = "jdbc:postgresql://localhost:5432/metagenomics-demo"
    71                         dialect = org.hibernate.dialect.PostgreSQLDialect
    72                 }
    73         }
    74         production {
    75                 dataSource {
    76                         dbCreate = "update"
    77                         username = "metagenomics"
    78                         password = "metagenomics"
    79 
    80                         // PostgreSQL
    81                         driverClassName = "org.postgresql.Driver"
    82                         url = "jdbc:postgresql://localhost:5432/metagenomics-www"
    83                         dialect = org.hibernate.dialect.PostgreSQLDialect
    84                 }
    85         }
    86         www {
    87                 // used by deploy scripts
    88                 dataSource {
    89                         dbCreate = "update"
    90                         username = "metagenomics"
    91                         password = "metagenomics"
    92 
    93                         // PostgreSQL
    94                         driverClassName = "org.postgresql.Driver"
    95                         url = "jdbc:postgresql://localhost:5432/metagenomics-www"
    96                         dialect = org.hibernate.dialect.PostgreSQLDialect
    97                 }
    98         }
    99 }
  • trunk/grails-app/controllers/nl/tno/metagenomics/AssayController.groovy

    r2 r3  
    99        def fileService
    1010        def excelService
    11         def fastaService
    1211
    1312        // Fields to be edited using excel file and manually
     
    494493        }
    495494
    496         /**************************************************************************
    497          *
    498          * Methods for handling uploaded sequence and quality files
    499          *
    500          *************************************************************************/
    501 
    502         /**
    503          * Processes uploaded files and tries to combine them with samples
    504          */
    505         def process = {
    506                 // load study with id specified by param.id
    507                 def assay = Assay.get(params.id as Long)
    508 
    509                 if (!assay) {
    510                         flash.message = "No assay found with id: $params.id"
    511                         redirect('action': 'errorPage')
    512                         return
    513                 }
    514 
    515                 // Check whether files are given
    516                 def names = params.sequencefiles
    517 
    518                 if( !names ) {
    519                         flash.message = "No files uploaded for processing"
    520                         redirect('action': 'show', 'id': params.id)
    521                         return
    522                 }
    523 
    524                 // If only 1 file is uploaded, it is given as String
    525                 ArrayList filenames = []
    526                 if( names instanceof String )
    527                         filenames << names
    528                 else
    529                         names.each { filenames << it }
    530 
    531                 /* Parses uploaded files, discards files we can not handle
    532                  *
    533                  * [
    534                  *              success: [
    535                  *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
    536                  *                      [filename: 'cde.fasta', type: FASTA, numSequences: 140]
    537                  *                      [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
    538                  *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    539                  *              ],
    540                  *              failure: [
    541                  *                      [filename: 'testing.xls', message: 'Type not recognized']
    542                  *              ]
    543                  * ]
    544                  */
    545                 def parsedFiles = fastaService.parseFiles( filenames );
    546 
    547                 // Match files with samples in the database
    548                 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assay.assaySamples );
    549 
    550                 // Sort files on filename
    551                 matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }
    552 
    553                 // Saved file matches in session to use them later on
    554                 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ];
    555 
    556                 [assay: assay, parsedFiles: parsedFiles, matchedFiles: matchedFiles, selectedRun: params.selectedRun ]
    557         }
    558 
    559         /**
    560          * Saves processed files to the database, based on the selections made by the user
    561          */
    562         def saveProcessedFiles = {
    563                 // load study with id specified by param.id
    564                 def assay = Assay.get(params.id as Long)
    565 
    566                 if (!assay) {
    567                         flash.message = "No assay found with id: $params.id"
    568                         redirect('action': 'errorPage')
    569                         return
    570                 }
    571 
    572                 // Check whether files are given
    573                 def files = params.file
    574 
    575                 if( !files ) {
    576                         flash.message = "No files were selected."
    577                         redirect('action': 'show', 'id': params.id)
    578                         return
    579                 }
    580 
    581                 File permanentDir = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir )
    582                 int numSuccesful = 0;
    583                 def errors = [];
    584                
    585                 // Loop through all files Those are the numeric elements in the 'files' array
    586                 def digitRE = ~/^\d+$/;
    587                 files.findAll { it.key.matches( digitRE ) }.each { file ->
    588                         def filevalue = file.value;
    589                        
    590                         // Check if the file is selected
    591                         if( filevalue.include == "on" ) {
    592                                 if( fileService.fileExists( filevalue.fasta ) ) {
    593                                         try {
    594                                                 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, session.processedFiles );
    595                                                
    596                                                 // Save the data into the database
    597                                                 SequenceData sd = new SequenceData();
    598                                                
    599                                                 sd.sequenceFile = permanent.fasta
    600                                                 sd.qualityFile = permanent.qual
    601                                                 sd.numSequences = permanent.numSequences
    602                                                 sd.averageQuality = permanent.avgQuality
    603                                                
    604                                                 // Couple the data to the right run and sample
    605                                                 def run = Run.get( filevalue.run )
    606                                                 if( run )
    607                                                         run.addToSequenceData( sd );
    608                                                        
    609                                                 def sample = AssaySample.get( filevalue.assaySample );
    610                                                 if( sample )
    611                                                         sample.addToSequenceData( sd );
    612                                                
    613                                                 if( !sd.validate() ) {
    614                                                         errors << "an error occurred while saving " + filevalue.fasta + ": validation of SequenceData failed.";
    615                                                 } else {
    616                                                         sd.save(flush:true);
    617                                                 }
    618                                                
    619                                                 numSuccesful++;
    620                                         } catch( Exception e ) {
    621                                                 errors << "an error occurred while saving " + filevalue.fasta + ": " + e.getMessage()
    622                                         }
    623                                 }
    624                         } else {
    625                                 // File doesn't need to be included in the system. Delete it also from disk
    626                                 fileService.delete( filevalue.fasta );
    627                         }
    628                 }
    629 
    630                 // Return a message to the user
    631                 if( numSuccesful == 0 ) {
    632                         flash.error = "None of the files were imported, because "
    633                        
    634                         if( errors.size() > 0 ) {
    635                                 errors.each {
    636                                         flash.error += "<br />- " + it
    637                                 }
    638                         } else {
    639                                 flash.error = "none of the files were selected for import."
    640                         }
    641                 } else {
    642                         flash.message = numSuccesful + " files have been added to the system. "
    643 
    644                         if( errors.size() > 0 ) {
    645                                 flash.error += errors.size() + " errors occurred during import: "
    646                                 errors.each {
    647                                         flash.error += "<br />- " + it
    648                                 }
    649                         }
    650                 }
    651                
    652                 redirect( action: "show", id: params.id )
    653         }
    654 
    655495}
  • trunk/grails-app/controllers/nl/tno/metagenomics/StudyController.groovy

    r2 r3  
    1818                // Filter studies for the ones the user is allowed to see
    1919                def studies = Study.findAll();
    20                 [studies: studies.findAll { it.canRead( session.user ) }, gscfAddUrl: gscfService.urlAddStudy() ]
     20                [studies: studies.findAll { it.canRead( session.user ) }, gscfAddUrl: gscfService.urlAddStudy(), lastSynchronized: synchronizationService.lastFullSynchronization ]
    2121        }
     22
    2223}
  • trunk/grails-app/controllers/nl/tno/metagenomics/auth/LogoutController.groovy

    r2 r3  
    1414                log.info("Session.sessionToken is now ${session.sessionToken}")
    1515               
    16                 //logout on GSCF side, and do not redirect back to metagenomics (&spring-security-redirect=${ConfigurationHolder.config.metagenomics.baseURL}/study/list)
     16                //logout on GSCF side, and do not redirect back to metagenomics (&spring-security-redirect=${ConfigurationHolder.config.grails.serverURL}/study/list)
    1717                def redirectURL = "${ConfigurationHolder.config.gscf.baseURL}/logout/remote?consumer=${ConfigurationHolder.config.metagenomics.ConsumerID}&token=${session.sessionToken}"
    1818                log.info("Redirecting to: ${redirectURL}")
  • trunk/grails-app/controllers/nl/tno/metagenomics/integration/RestController.groovy

    r2 r3  
    339339                        }
    340340                       
    341                         def url = [ 'url' : ConfigurationHolder.config.metagenomics.baseURL + '/assay/show/' + assay.id.toString() ]
     341                        def url = [ 'url' : ConfigurationHolder.config.grails.serverURL + '/assay/show/' + assay.id.toString() ]
    342342
    343343                        render url as JSON
    344344                }
    345         }
     345        }.metagenomics.baseURL
    346346       
    347347        /***************************************************/
  • trunk/grails-app/domain/nl/tno/metagenomics/AssaySample.groovy

    r2 r3  
    1111        private long _numSequences = -1;
    1212        private float _averageQuality = -1.0;
    13        
     13
    1414        Integer numUniqueSequences      // Number of unique sequences / OTUs. Is only available after preprocessing
    1515
     
    4949
    5050        /**
     51         * Returns the number of sequence files in the system, belonging to this
     52         * assay-sample combination.
     53         *
     54         * @return
     55         */
     56        public int numSequenceFiles() {
     57                if( !sequenceData )
     58                        return 0
     59
     60                int numFiles = 0;
     61                sequenceData.each {
     62                        if( it.sequenceFile )
     63                                numFiles++
     64                }
     65
     66                return numFiles;
     67        }
     68       
     69        /**
     70         * Returns the number of quality files in the system, belonging to this
     71         * assay-sample combination.
     72         *
     73         * @return
     74         */
     75        public int numQualityFiles() {
     76                if( !sequenceData )
     77                        return 0
     78
     79                int numFiles = 0;
     80                sequenceData.each {
     81                        if( it.qualityFile )
     82                                numFiles++
     83                }
     84
     85                return numFiles;
     86        }
     87       
     88        /**
    5189         * Returns the number of sequences in the files on the system, belonging to this
    5290         * assay-sample combination.
     
    5795                if( _numSequences > -1 )
    5896                        return _numSequences;
    59                        
     97
    6098                if( !sequenceData )
    6199                        return 0
     
    93131                // Save as cache
    94132                _averageQuality = averageQuality;
    95                
     133
    96134                return averageQuality;
    97135        }
     136       
     137        /**
     138         * Reset the statistics to their default value, in order to ensure that the values are recomputed next time.
     139         */
     140        public void resetStats() {
     141                _numSequences = -1;
     142                _averageQuality = -1;
     143        }
    98144}
  • trunk/grails-app/domain/nl/tno/metagenomics/Run.groovy

    r2 r3  
    1111class Run {
    1212        def fileService
    13        
     13
    1414        String  name
    1515        Date    date
     
    2020        static hasMany = [sequenceData: SequenceData, assays: Assay]
    2121        static belongsTo = Assay        // Only used to determine the owner of the many-to-many relationship assay-run
    22        
     22
    2323        static mapping = {
    2424                columns {
     
    3333                parameterFile(nullable:true)
    3434        }
    35        
     35
    3636        def beforeDelete = {
    3737                // Remove the file if the object is deleted
    3838                if( this.parameterFile )
    3939                        fileService.delete( this.parameterFile, fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir ) )
    40          }
    41  
     40        }
     41
    4242        /**
    4343         * Sets the properties of this run from a form
     
    4747        public setPropertiesFromForm( params ) {
    4848                this.properties = params.run
    49                
     49
    5050                // Enter date or default NULL
    5151                if( params.run_date ) {
     
    5454                        this.date = null
    5555                }
    56                
     56
    5757                // Enter filename if needed
    5858                if( params.parameterFile ) {
    5959                        this.parameterFile = fileService.handleUploadedFile(
    60                                 fileService.get( params.parameterFile ),
    61                                 "",
    62                                 fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir )
    63                         );
     60                                        fileService.get( params.parameterFile ),
     61                                        "",
     62                                        fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir )
     63                                        );
    6464                }
    6565        }
     66
     67        /**
     68         * Returns the number of files in the system, belonging to this run
     69         *
     70         * @return
     71         */
     72        public int numFiles() {
     73                if( !sequenceData )
     74                        return 0
     75
     76                int numFiles = 0;
     77                sequenceData.each { numFiles += it.numFiles() }
     78
     79                return numFiles;
     80        }
     81
     82        /**
     83         * Returns the number of sequences in the files in the system, belonging to this run
     84         *
     85         * @return
     86         */
     87        public long numSequences() {
     88                if( !sequenceData )
     89                        return 0
     90
     91                long numSequences = 0;
     92                sequenceData.each { numSequences += it.numSequences }
     93
     94                return numSequences;
     95        }
     96
     97        /**
     98         * Returns the assaySamples associated with this run
     99         *
     100         * @return
     101         */
     102        public ArrayList samples( def assayId ) {
     103                if( !sequenceData )
     104                        return []
     105
     106                def list = []
     107                sequenceData.each {
     108                        if( it.sample.assay.id == assayId )
     109                                list << it.sample
     110                }
     111
     112                return list.unique().toList();
     113        }
    66114}
  • trunk/grails-app/domain/nl/tno/metagenomics/SequenceData.groovy

    r2 r3  
    11package nl.tno.metagenomics
    22
     3import org.codehaus.groovy.grails.commons.ConfigurationHolder
     4
    35class SequenceData {
     6        def fileService
     7       
    48        String sequenceFile = ''
    59        String qualityFile = ''
     
    2832                return number;
    2933        }
     34       
     35        def beforeDelete = {
     36                def permanentDir = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir.toString() )
     37                if( sequenceFile ) {
     38                        fileService.delete( sequenceFile, permanentDir )
     39                }
     40
     41                if( qualityFile )
     42                        fileService.delete( qualityFile, permanentDir )
     43               
     44                // Reset statistics of the assay sample, to ensure the deleted files are removed from statistics
     45                sample.resetStats();
     46        }
    3047}
  • trunk/grails-app/services/nl/tno/metagenomics/FastaService.groovy

    r2 r3  
    11package nl.tno.metagenomics
    2 
    32
    43import java.io.File;
     
    98        def fileService
    109        def fuzzySearchService
    11        
    12     static transactional = true
     10
     11        static transactional = true
    1312
    1413        /**
    1514         * Parses uploaded files and checks them for FASTA and QUAL files
    1615         * @param filenames             List of filenames currently existing in the upload directory
     16         * @param onProgress    Closure to execute when progress indicators should be updated.
     17         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     18         *                                              of files and bytes that have been processed in total
    1719         * @param directory             Directory to move the files to
    1820         * @return                              Structure with information about the parsed files. The 'success' files are
     
    3234         *
    3335         */
    34         def parseFiles( ArrayList filenames, File directory = null ) {
     36        def parseFiles( ArrayList filenames, Closure onProgress, File directory = null ) {
    3537                if( filenames.size() == 0 ) {
    3638                        return [ success: [], failure: [] ];
     
    4345                def success = [];
    4446                def failure = [];
     47
     48                long filesProcessed = 0;
     49                long bytesProcessed = 0;
    4550
    4651                // Loop through all filenames
     
    5560                        } else {
    5661                                try {
    57                                         def contents = parseFile( file, filetype );
    58                                        
     62                                        def contents = parseFile( file, filetype, { files, bytes ->
     63                                                filesProcessed += files;
     64                                                bytesProcessed += bytes;
     65
     66                                                onProgress( filesProcessed, bytesProcessed );
     67                                        } );
     68
    5969                                        contents.filename = file.getName();
    60                                         contents.originalfilename = fileService.originalFilename( contents.filename ) 
    61                                        
     70                                        contents.originalfilename = fileService.originalFilename( contents.filename )
     71
    6272                                        if( contents.success ) {
    6373                                                success << contents;
     
    7181                                        failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: e.getMessage() ];
    7282                                }
    73        
     83
    7484                        }
    7585                }
     
    103113                def quals = parsedFiles.findAll { it.type == "qual" }
    104114                samples = samples.toList()
    105                
     115
    106116                def files = [];
    107                
     117
    108118                fastas.each { fastaFile ->
    109119                        // Remove extension
    110                         def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.filename.lastIndexOf( '.' ) )
    111                        
     120                        def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) )
     121
    112122                        // Determine feasible quals (based on number of sequences )
    113123                        def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences }
    114                        
     124
    115125                        // Best matching qual file
    116126                        def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename );
     
    119129                        if( qualIdx != null )
    120130                                qual = feasibleQuals[ qualIdx ];
    121                        
     131
    122132                        // Best matching sample
    123133                        def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name );
    124134                        def assaySample = null
    125                         if( sampleIdx != null ) { 
     135                        if( sampleIdx != null ) {
    126136                                assaySample = samples[ sampleIdx ];
    127137                        }
    128138
    129139                        files << [
    130                                 fasta: fastaFile,
    131                                 feasibleQuals: feasibleQuals,
    132                                 qual: qual,
    133                                 assaySample: assaySample
    134                         ]
    135                 }
    136                
     140                                                fasta: fastaFile,
     141                                                feasibleQuals: feasibleQuals,
     142                                                qual: qual,
     143                                                assaySample: assaySample
     144                                        ]
     145                }
     146
    137147                return files;
    138                
    139                
    140         }
    141        
    142        
     148
     149
     150        }
     151
     152
    143153        /**
    144154         * Determines the file type of a given file, based on the extension.
     
    195205        /**
    196206         * Parses the given file
    197          * @param file          File to parse
    198          * @param filetype      Type of the given file
    199          * @return                      List structure. Examples:
     207         * @param file                  File to parse
     208         * @param filetype              Type of the given file
     209         * @param onProgress    Closure to execute when progress indicators should be updated.
     210         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     211         *                                              of files and bytes that have been processed in this file (so the first parameter should
     212         *                                              only be 1 when the file is finished)
     213         *
     214         * @return                              List structure. Examples:
    200215         *
    201216         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
     
    203218         *   [ success: false, filename: 'abc.txt', type: 'txt', message: 'Filetype could not be parsed.' ]
    204219         */
    205         protected def parseFile( File file, String filetype ) {
     220        protected def parseFile( File file, String filetype, Closure onProgress ) {
    206221                switch( filetype ) {
    207222                        case "fasta":
    208                                 return parseFasta( file );
     223                                return parseFasta( file, onProgress );
    209224                        case "qual":
    210                                 return parseQual( file );
     225                                return parseQual( file, onProgress );
    211226                        default:
     227                                onProgress( 1, file.length() );
    212228                                return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ]
    213229                }
     
    216232        /**
    217233         * Parses the given FASTA file
    218          * @param file          File to parse
    219          * @return                      List structure. Examples:
     234         * @param file                  File to parse
     235         * @param onProgress    Closure to execute when progress indicators should be updated.
     236         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     237         *                                              of files and bytes that have been processed in this file (so the first parameter should
     238         *                                              only be 1 when the file is finished)
     239         * @return                              List structure. Examples:
    220240         *
    221241         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
    222242         *   [ success: false, filename: 'def.fasta', type: 'fasta', message: 'File is not a valid FASTA file' ]
    223243         */
    224         protected def parseFasta( File file ) {
    225                
     244        protected def parseFasta( File file, Closure onProgress ) {
     245
    226246                long startTime = System.nanoTime();
    227                 log.trace "Start parsing FASTA " + file.getName() 
    228                
     247                log.trace "Start parsing FASTA " + file.getName()
     248
    229249                // Count the number of lines, starting with '>' (and where the following line contains a character other than '>')
    230250                long numSequences = 0;
     251                long bytesProcessed = 0;
    231252                boolean lookingForSequence = false;
    232253
     
    241262                                        }
    242263                                }
    243                         }
    244                 }
     264
     265
     266                                // Update progress every time 1MB is processed
     267                                bytesProcessed += line.size();
     268                                if( bytesProcessed > 1000000 ) {
     269                                        onProgress( 0, bytesProcessed );
     270                                        bytesProcessed = 0;
     271                                }
     272                        }
     273
     274
     275
     276                }
     277
     278                // Update progress and say we're finished
     279                onProgress( 1, bytesProcessed );
    245280
    246281                log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    251286        /**
    252287         * Parses the given QUAL file
    253          * @param file          File to parse
    254          * @return                      List structure. Examples:
     288         * @param file                  File to parse
     289         * @param onProgress    Closure to execute when progress indicators should be updated.
     290         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     291         *                                              of files and bytes that have been processed in this file (so the first parameter should
     292         *                                              only be 1 when the file is finished)
     293         * @return                              List structure. Examples:
    255294         *
    256295         *   [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 31 ]
    257296         *   [ success: false, filename: 'def.qual', type: 'qual', message: 'File is not a valid QUAL file' ]
    258297         */
    259         protected def parseQual( File file ) {
     298        protected def parseQual( File file, Closure onProgress ) {
    260299                long startTime = System.nanoTime();
    261300                log.trace "Start parsing QUAL " + file.getName()
    262301
    263                 // Count the number of lines, starting with '>'. After we've found such a character, we continue looking for 
     302                // Count the number of lines, starting with '>'. After we've found such a character, we continue looking for
    264303                // quality scores
    265304                long numSequences = 0;
     305                long bytesProcessed = 0;
    266306                def quality = [ quality: 0.0, number: 0L ]
    267                
     307
    268308                boolean lookingForFirstQualityScores = false;
    269309                file.eachLine { line ->
     
    275315                                                numSequences++;
    276316                                                lookingForFirstQualityScores = false;
    277                                                
     317
    278318                                                quality = updateQuality( quality, line );
    279319                                        }
     
    281321                                        quality = updateQuality( quality, line );
    282322                                }
    283                         }
    284                 }
     323                               
     324                                // Update progress every time 1MB is processed
     325                                bytesProcessed += line.size();
     326                                if( bytesProcessed > 1000000 ) {
     327                                        onProgress( 0, bytesProcessed );
     328                                        bytesProcessed = 0;
     329                                }
     330                        }
     331
     332                }
     333
     334                // Update progress and say we're finished
     335                onProgress( 1, bytesProcessed );
    285336
    286337                log.trace "Finished parsing QUAL " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    288339                return [ success: true, type: "qual", numSequences: numSequences, avgQuality: quality.quality ];
    289340        }
    290        
     341
    291342        /**
    292343         * Parses the given line and updates the average quality based on the scores
     
    298349                // Determine current average
    299350                List tokens = line.tokenize();
    300                 Long total = 0; 
    301                
     351                Long total = 0;
     352
    302353                tokens.each {
    303354                        total += Integer.parseInt( it );
    304355                }
    305                
     356
    306357                int numTokens = tokens.size();
    307                
     358
    308359                // Update the given average
    309360                if( numTokens > 0 ) {
     
    311362                        quality.quality = quality.quality + ( ( total / numTokens as double ) - quality.quality ) / quality.number * numTokens;
    312363                }
    313                
     364
    314365                return quality
    315366        }
    316        
     367
    317368        /**
    318369         * Moves a fasta and qual file to their permanent location, and returns information about these files
     
    326377                File permanentDirectory = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir );
    327378                def returnStructure = [:];
    328                
     379
    329380                if( fileService.fileExists( fastaFile ) ) {
    330381                        // Lookup the original filename
     
    340391                        throw new Exception( "Fasta file to save doesn't exist on disk" );
    341392                }
    342                
     393
    343394                if( qualFile && fileService.fileExists( qualFile ) ) {
    344395                        // Lookup the original filename
    345396                        def qualData = processedFiles.parsed.success.find { it.filename == qualFile };
    346                        
     397
    347398                        if( qualData ) {
    348399                                returnStructure.qual = fileService.moveFileToUploadDir( fileService.get(qualFile ), qualData.originalfilename, permanentDirectory );
     
    358409                        returnStructure.avgQuality = 0;
    359410                }
    360                
     411
    361412                return returnStructure;
    362413        }
  • trunk/grails-app/services/nl/tno/metagenomics/integration/GscfService.groovy

    r2 r3  
    2727         */
    2828        public String urlAuthRemote( def params, def token ) {
    29                 def redirectURL = "${config.gscf.baseURL}/login/auth_remote?moduleURL=${this.moduleURL()}&consumer=${this.consumerID()}&token=${token}&returnUrl=${config.metagenomics.baseURL}"
     29                def redirectURL = "${config.gscf.baseURL}/login/auth_remote?moduleURL=${this.moduleURL()}&consumer=${this.consumerID()}&token=${token}&returnUrl=${config.grails.serverURL}"
    3030
    3131                if (params.controller != null){
     
    330330         */
    331331        private String moduleURL() {
    332                 return config.metagenomics.baseURL
     332                return config.grails.serverURL
    333333        }
    334334
  • trunk/grails-app/services/nl/tno/metagenomics/integration/SynchronizationService.groovy

    r2 r3  
    77class SynchronizationService {
    88        def gscfService
    9        
     9
    1010        String sessionToken = ""        // Session token to use for communication
    1111        User user = null                        // Currently logged in user. Must be set when synchronizing authorization
    1212        boolean eager = false           // When set to true, this method fetches data about all studies from GSCF. Otherwise, it will only look at the
    13                                                                 // studies marked as dirty in the database. Defaults to false.
     13        // studies marked as dirty in the database. Defaults to false.
     14
     15        // Keeps track of the last time this module performed a full synchronization.
     16        static Date lastFullSynchronization = null;
    1417
    1518        static transactional = true
    16        
     19
    1720        /**
    1821         * Determines whether the synchronization should be performed or not. This can be entered
     
    2225        protected performSynchronization() {
    2326                def conf = ConfigurationHolder.config.metagenomics.synchronization;
    24                
     27
    2528                // If nothing is entered in configuration, return true (default value)
    2629                if( conf == null )
    2730                        return true
     31
     32                return conf
     33        }
     34       
     35        /**
     36         * Returns true iff a full synchronization should be performed
     37         * @return
     38         */
     39        public boolean timeForFullSynchronization() {
     40                if( SynchronizationService.lastFullSynchronization == null )
     41                        return true
    2842                       
    29                 return conf
     43                // Compute the time since the last full synchronization in  milliseconds
     44                Date today = new Date();
     45                long difference = SynchronizationService.lastFullSynchronization.getTime() - today.getTime()
     46               
     47                if( difference / 1000 > ConfigurationHolder.config.metagenomics.fullSynchronization )
     48                        return true
     49                else
     50                        return false
     51        }
     52
     53        /**
     54         * Redirects to a temporary page to give the user a 'waiting' page while synchronizing
     55         * @return
     56         */
     57        public String urlForFullSynchronization( def params ) {
     58                def returnUrl = ConfigurationHolder.config.grails.serverURL
     59                if (params.controller != null){
     60                        returnUrl += "/${params.controller}"
     61                        if (params.action != null){
     62                                returnUrl += "/${params.action}"
     63                                if (params.id != null){
     64                                        returnUrl += "/${params.id}"
     65                                }
     66                        }
     67                }
     68                if( timeForFullSynchronization() ) {
     69                        return ConfigurationHolder.config.grails.serverURL + "/synchronize/full?redirect=" + returnUrl.encodeAsURL()
     70                } else {
     71                        return returnUrl
     72                }
     73        }
     74
     75        /**
     76         * Performs a full synchronization in order to retrieve all studies
     77         * @return
     78         */
     79        public void fullSynchronization() {
     80                if( !timeForFullSynchronization() )
     81                        return
     82
     83                def previousEager = eager
     84                eager = true
     85                synchronizeStudies();
     86                eager = previousEager
     87               
     88                SynchronizationService.lastFullSynchronization = new Date();
    3089        }
    3190
     
    3796                if( !performSynchronization() )
    3897                        return Study.findAll()
    39                                
     98
    4099                // When eager fetching is enabled, ask for all studies, otherwise only ask for studies marked dirty
    41100                // Synchronization is performed on all studies, not only the studies the user has access to. Otherwise
     
    47106                        studies = Study.findAllWhere( [isDirty: true] );
    48107                }
    49                
     108
    50109                // Perform no synchronization if no studies have to be synchronized
    51110                // Perform synchronization on only one study directly, because otherwise
     
    57116                        println "Study: " + studies[ 0]
    58117                        def newStudy = synchronizeStudy( studies[0] );
    59                         if( newStudy ) 
     118                        if( newStudy )
    60119                                return [ newStudy ];
    61                         else 
     120                        else
    62121                                return []
    63122                }
    64                
     123
    65124                // Fetch all studies from GSCF
    66125                def newStudies
     
    68127                        if( !eager ) {
    69128                                def studyTokens = studies.studyToken;
    70                                
     129
    71130                                if( studyTokens instanceof String ) {
    72131                                        studyTokens = [studyTokens];
     
    86145                synchronizeStudies( newStudies );
    87146                studies = handleDeletedStudies( studies, newStudies );
    88                
     147
    89148                log.trace( "Returning " + studies.size() + " studies after synchronization" )
    90                
     149
    91150                return studies
    92151        }
    93        
     152
    94153        /**
    95154         * Synchronizes all studies given by 'newStudies' with existing studies in the database, and adds them
     
    107166
    108167                                Study studyFound = Study.findByStudyToken( gscfStudy.studyToken as String )
    109                                
     168
    110169                                if(studyFound) {
    111170                                        log.trace( "Study found with name " + studyFound.name )
    112                                        
     171
    113172                                        // Synchronize the study itself with the data retrieved
    114173                                        synchronizeStudy( studyFound, gscfStudy );
     
    123182                                        synchronizeAuthorization(studyFound);
    124183                                        synchronizeStudyAssays(studyFound);
    125                                        
     184
    126185                                        // Mark the study as clean
    127186                                        studyFound.isDirty = false
     
    131190                }
    132191        }
    133        
     192
    134193        /**
    135194         * Removes studies from the database that are expected but not found in the list from GSCF
     
    151210                                log.trace( "Study " + existingStudy.studyToken + " not found. Check whether it is removed or the user just can't see it." )
    152211
    153                                 // Study was not given to us by GSCF. This might be because the study is removed, or because the study is not visible (anymore) 
     212                                // Study was not given to us by GSCF. This might be because the study is removed, or because the study is not visible (anymore)
    154213                                // to the current user.
    155214                                // Synchronize authorization and see what is the case (it returns null if the study has been deleted)
     
    160219                        }
    161220                }
    162                
     221
    163222                return studies
    164223        }
    165        
     224
    166225        /**
    167226         * Synchronizes the given study with the data from GSCF
     
    172231                if( !performSynchronization() )
    173232                        return study
    174                        
     233
    175234                if( study == null )
    176235                        return null
     
    179238                if( !eager && !study.isDirty )
    180239                        return study;
    181                        
     240
    182241                // Retrieve the study from GSCF
    183242                def newStudy
     
    218277                if( !performSynchronization() )
    219278                        return study
    220                        
     279
    221280                if( study == null || newStudy == null)
    222281                        return null
     
    224283                // If the study hasn't changed, don't update anything
    225284                if( !eager && !study.isDirty ) {
    226                         return study;                   
     285                        return study;
    227286                }
    228287
     
    231290                        return null;
    232291                }
    233                
     292
    234293                // Mark study dirty to enable synchronization
    235294                study.isDirty = true;
    236295                synchronizeAuthorization( study );
    237296                synchronizeStudyAssays( study );
    238                
     297
    239298                // Update properties and mark as clean
    240299                study.name = newStudy.title
     
    254313                        return study.assays.toList()
    255314
    256                 if( !eager && !study.isDirty ) 
    257                         return study.assays as List     
    258                
     315                if( !eager && !study.isDirty )
     316                        return study.assays as List
     317
    259318                // Also update all assays, belonging to this study
    260319                // Retrieve the assays from GSCF
     
    287346                return handleDeletedAssays( study, newAssays );
    288347        }
    289        
     348
    290349        /**
    291350         * Synchronizes the assays of a study with the given data from GSCF
     
    323382                }
    324383        }
    325        
     384
    326385        /**
    327386         * Removes assays from the system that have been deleted from GSCF
     
    354413                        }
    355414                }
    356                
     415
    357416                return study.assays.toList()
    358417        }
    359        
     418
    360419        /**
    361420         * Retrieves the authorization for the currently logged in user
     
    374433                        throw new Exception( "Property user of SynchronizationService must be set to the currently logged in user" );
    375434                }
    376                
     435
    377436                // Only perform synchronization if needed
    378437                if( !eager && !study.isDirty )
    379438                        return Auth.findByUserAndStudy( user, study )
    380                        
     439
    381440                if( !performSynchronization() )
    382441                        return Auth.findByUserAndStudy( user, study )
    383                
     442
    384443                def gscfAuthorization
    385444                try {
     
    389448                        // TODO: handle deletion
    390449                        log.trace( "Study " + study.studyToken + " has been deleted. Remove all authorization on that study")
    391                        
     450
    392451                        Auth.findAllByStudy( study ).each {
    393452                                it.delete()
    394453                        }
    395                        
    396                         return null
    397                 }
    398                
     454
     455                        return null
     456                }
     457
    399458                // Update the authorization object, or create a new one
    400459                Auth a = Auth.authorization( study, user )
    401                
     460
    402461                if( !a ) {
    403462                        log.trace( "Authorization not found for " + study.studyToken + " and " + user.username + ". Creating a new object" );
    404                        
     463
    405464                        a = Auth.createAuth( study, user );
    406465                }
    407                
     466
    408467                // Copy properties from gscf object
    409468                a.canRead = gscfAuthorization.canRead
    410469                a.canWrite = gscfAuthorization.canWrite
    411470                a.isOwner = gscfAuthorization.isOwner
    412                
     471
    413472                a.save()
    414                
     473
    415474                return a
    416475        }
     
    431490                if( !eager && !assay.study.isDirty )
    432491                        return assay
    433                        
     492
    434493                // Retrieve the assay from GSCF
    435494                def newAssay
     
    477536                if( !eager && !assay.study.isDirty )
    478537                        return assay
    479                        
     538
    480539                // If new assay is empty, something went wrong
    481540                if( newAssay.size() == 0 ) {
     
    535594                }
    536595
    537                
     596
    538597                synchronizeAssaySamples( assay, newSamples );
    539598                return handleDeletedSamples( assay, newSamples );
    540599        }
    541        
     600
    542601        /**
    543602         * Synchronize all samples for a given assay with the data from GSCF
     
    597656                }
    598657        }
    599        
     658
    600659        /**
    601660         * Removes samples from the system that have been removed from an assay in GSCF
     
    612671                        for( int i = numAssaySamples - 1; i >= 0; i-- ) {
    613672                                def existingSample = assaySamples[i];
    614        
     673
    615674                                AssaySample sampleFound = newSamples.find { it.name == existingSample.sample.sampleToken }
    616        
     675
    617676                                if( !sampleFound ) {
    618677                                        log.trace( "Sample " + existingSample.sample.sampleToken + " not found. Removing it." )
    619        
     678
    620679                                        // The sample has been removed
    621680                                        // TODO: What to do with the data associated with this AssaySample (e.g. sequences)? See also GSCF ticket #255
  • trunk/grails-app/views/assay/_addFilesDialog.gsp

    r2 r3  
    22        <h2>Upload sequence files</h2>
    33       
    4         <g:form name="addFiles" controller="assay", action="process" id="${assay.id}">
    5                 <p>Select the run these files belong to.</p>
    6                 <p>
    7                 <g:select name="selectedRun" from="${assay.runs}" optionKey="id" optionValue="name" />
    8                 </p>
     4        <g:form name="addFiles" controller="fasta" action="showProcessScreen" id="${assay.id}">
     5                <p>Select the run these files belong to: <g:select name="selectedRun" from="${assay.runs}" optionKey="id" optionValue="name" /></p>
    96                <p>
    107                        Select sequence and quality files to upload. It is possible to zip the files before upload.
  • trunk/grails-app/views/assay/show.gsp

    r2 r3  
    1111                <g:javascript src="assay.show.enterTagsDialog.js" />
    1212                <g:javascript src="assay.show.runDialogs.js" />
     13                <g:javascript src="assay.show.showSampleDialog.js" />
     14                <g:javascript src="assay.show.showRunDialog.js" />
    1315
    1416                <g:javascript src="fileuploader.new.js" />
     
    6668                                <g:each in="${assaySamples}" var="assaySample">
    6769                                        <tr>
    68                                                 <td>${assaySample.sample.name}</td>
     70                                                <td><a href="#" onClick="showSample(${assaySample.id}); return false;">${assaySample.sample.name}</a></td>
    6971                                                <td>${assaySample.tagSequence}</td>
    7072                                                <td>${assaySample.numSequences()}</td>
     
    9597                        <g:render template="addFilesDialog" model="[assay: assay]" />
    9698                </g:if>
     99                <g:render template="showSampleDialog" model="[assay: assay]" />
    97100        </g:else>       
    98101
     
    108111                                        <th nowrap>name</th>
    109112                                        <th nowrap>date</th>
     113                                        <th nowrap>supplier</th>
    110114                                        <th nowrap>machine</th>
    111                                         <th nowrap>supplier</th>
    112                                         <th nowrap>configuration</th>
     115                                        <th nowrap>parameter file</th>
    113116                                        <th nowrap>other assays</th>
    114117                                        <th class="nonsortable"></th>
     
    120123                                <g:each in="${runs}" var="run">
    121124                                        <tr>
    122                                                 <td>${run.name}</td>
     125                                                <td><a href="#" onClick="showRun(${run.id}); return false;">${run.name}</a></td>
    123126                                                <td><g:formatDate format="dd-MM-yyyy" date="${run.date}"/></td>
     127                                                <td>${run.supplier}</td>
    124128                                                <td>${run.machine}</td>
    125                                                 <td>${run.supplier}</td>
    126129                                                <td><g:uploadedFile value="${run.parameterFile}"/></td>
    127130                                                <td>
     
    148151                <g:render template="editRunDialog" model="[assay: assay]" />
    149152        </g:if>
     153        <g:render template="showRunDialog" model="[assay: assay]" />
     154       
    150155</body>
    151156</html>
  • trunk/grails-app/views/fasta/showProcessResult.gsp

    r2 r3  
    22        <head>
    33                <meta name="layout" content="main" />
    4                 <title>Process files for assay ${assay.name} | Metagenomics | dbNP</title>
     4                <title>Processed files for assay ${assay.name} | Metagenomics | dbNP</title>
    55               
    6                 <g:javascript src="jquery.ui.tabbeddialog.js" />
    7 
    86                <script>
    97                        var assayId = ${assay.id};
     
    6664                                        </td>
    6765                                        <td>
    68                                                 ${selectedRun}
    6966                                                <g:if test="${sortedRuns.size()}">
    7067                                                        <select name="file.${i}.run">
     
    116113       
    117114        <p>
    118                 <g:link action="show" id="${assay.id}">Return to assay</g:link>
     115                <g:link controller="assay" action="show" id="${assay.id}">Return to assay</g:link>     
    119116        </p>
    120117</body>
  • trunk/grails-app/views/layouts/main.gsp

    r2 r3  
    3434                       
    3535                        <div id="content">
     36                                <g:if test="${lastSynchronized}">
     37                                        <p>Last full synchronization: ${lastSynchronized}</p>
     38                                </g:if>
    3639                                <g:if test="${flash.error}">
    3740                                        <p class="error">${flash.error}</p>
  • trunk/web-app/css/metagenomics.css

    r2 r3  
    22        margin: 0;
    33        padding: 0;
    4         font: 10px normal Arial, Helvetica, sans-serif;
     4        font: 11px normal Arial, Helvetica, sans-serif;
    55        background: #fff url(../images/metagenomics/body_bg.gif) repeat-x;
    66}
     
    466466
    467467/* END :: special select option styles */
     468
     469.spinner {
     470    background: url(../images/spinner.gif) no-repeat left top;
     471    width: 16px;
     472    height: 16px;
     473    display: none;
     474}
  • trunk/web-app/css/showAssay.css

    r2 r3  
    22
    33#enterTagsDialog tr.example td { padding-top: 2px; padding-bottom: 2px; color: #666; }
     4
     5#showSampleDialog ul, #showRunDialog ul { list-style-type: none; margin-left: 0; padding-left: 0; }
     6#showSampleDialog ul li, #showRunDialog ul li { margin: 4px 0; padding-left: 0; }
Note: See TracChangeset for help on using the changeset viewer.