Changeset 70 for trunk


Ignore:
Timestamp:
Jun 17, 2011, 1:54:56 PM (8 years ago)
Author:
robert@…
Message:
  • Installed templates (in order to extend session lifetime to 2 hours)
  • Implemented background worker to do work outside the HTTP request
Location:
trunk
Files:
28 added
20 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/application.properties

    r58 r70  
    11#Grails Metadata file
    2 #Wed May 11 12:10:59 CEST 2011
     2#Wed Jun 15 09:48:55 CEST 2011
    33app.build.display.info=0
    4 app.grails.version=1.3.7
    5 app.name=massSequencing
    6 app.servlet.version=2.4
    74app.version=0.1
     5plugins.tomcat=1.3.7
    86plugins.famfamfam=1.0.1
    9 plugins.hibernate=1.3.7
    107plugins.jquery=1.4.4.1
    118plugins.jquery-ui=1.8.7
    12 plugins.tomcat=1.3.7
     9app.servlet.version=2.4
     10plugins.hibernate=1.3.7
     11app.name=massSequencing
     12app.grails.version=1.3.7
     13plugins.executor=0.2
  • trunk/grails-app/conf/BaseFilters.groovy

    r63 r70  
    198198                                // Also never perform synchronization when files are uploaded. That could lead to concurrent modification
    199199                                // errors, since the progress of the upload is retrieved many times, while the processing is still busy
    200                                 if( controllerName == "import" ) {
     200                                if( controllerName == "import" || controllerName == "export" || controllerName == "worker" ) {
    201201                                        return true;
    202202                                }
  • trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy

    r63 r70  
    33import java.util.List;
    44import grails.converters.JSON
     5import nl.tno.massSequencing.classification.*;
    56
    67import org.codehaus.groovy.grails.commons.ConfigurationHolder
     
    4142                   "COUNT( DISTINCT s )",
    4243                   "SUM( s.numSequences ) / COUNT( DISTINCT s )",
     44                   "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.assay = a)",
    4345                   "a.study.studyToken"
    4446           ]
    4547           
    46            def groupColumns = columns[0..2] + columns[ 5 ];
     48           def groupColumns = columns[0..2] + columns[ 6 ];
    4749           def orderByMapping = null;
    4850           
     
    5961                   def numSamples = it[ 3 ];
    6062                   def numSequences = it[ 4 ];
    61                    def studyToken = it[ 5 ];
     63                   def numClassification = it[ 5 ];
     64                   def studyToken = it[ 6 ];
    6265                   
    6366                   // Create buttons in the last three columns
     
    7780                           numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-",  // it.numSequences(),
    7881                           numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-",      // it.numQualScores(),
     82                           numClassification > 0 ? g.formatNumber( number: numClassification, format: "###,###,##0" ) : "-",    // it.numClassification(),
    7983                           editButton,
    8084                           chartButton
     
    119123                def otherRuns = Run.list( sort: "name" ).findAll { !it.assays.contains( assay ) }
    120124
     125                // Determine other parameters to show on screen
     126                def numClassified = Classification.executeQuery( "SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample IN (:assaySamples)", [ "assaySamples": assay.assaySamples ] );
     127
    121128                // Send the assay information to the view
    122                 [assay: assay, editable: assay.study.canWrite( session.user ), otherRuns: otherRuns]
     129                [assay: assay, editable: assay.study.canWrite( session.user ), otherRuns: otherRuns, "numClassified": numClassified ? numClassified[ 0 ] : 0]
    123130        }
    124131       
     
    556563         */
    557564        def exportAsFasta = {
    558                 def assaySamples = getAssaySamples( params );
     565                def assaySamples = getAssaySamples( params ).unique();
    559566                def name
    560567
     
    567574                }
    568575
    569                 // Export the sequences and quality scores
    570                 response.setHeader "Content-disposition", "attachment; filename=" + name.trim() + ".zip"
    571                 try {
    572                         fastaService.export( assaySamples.unique(), response.getOutputStream() );
    573                         response.outputStream.flush();
    574                 } catch( Exception e ) {
    575                         log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
    576                         e.printStackTrace();
    577                 }
     576                // Start the export in the background
     577                def returnUrl = params.url ? params.url.toString() : createLink( controller: "assay", action: "index" ).toString()
     578                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
     579                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
     580               
     581                // Show a waiting screen
     582                redirect( url: url );
    578583        }
    579584
     
    602607                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
    603608                        }
     609                        sampleExcelService.sessionToken = session.sessionToken
    604610                        sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() );
    605611                        response.outputStream.flush();
  • trunk/grails-app/controllers/nl/tno/massSequencing/AssaySampleController.groovy

    r67 r70  
    22
    33import java.util.List;
     4import org.codehaus.groovy.grails.commons.ConfigurationHolder
    45
    56class AssaySampleController {
    67        def fastaService
    78        def sampleExcelService
     9        def fileService
     10        def workerService
    811
    912        /**
     
    99102         */
    100103        def exportAsFasta = {
    101                 def assaySamples = getAssaySamples( params );
    102                 def name
     104                def assaySamples = getAssaySamples( params )?.unique();
    103105
    104106                if( assaySamples?.size() == 0 ) {
     
    108110                }
    109111
    110                 name = "samples";
    111 
     112                // Start the export in the background
     113                def name = "samples";
     114                def returnUrl = createLink( controller: params.entityType, action: "show", id: params.entityId ).toString()
     115                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
     116                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
     117
     118                // Show a waiting screen
     119                redirect( url: url );
     120        }
     121       
     122        def downloadFasta = {
     123                def processId = params.processId;
     124               
    112125                // Export the sequences and quality scores
    113                 response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
     126                response.setHeader "Content-disposition", "attachment; filename=" + session.process[ processId ].name  + ".zip"
    114127                try {
    115                         fastaService.export( assaySamples.unique(), response.getOutputStream() );
     128                        response.outputStream << fileService.get( session.process[ processId ].filename ).newInputStream();
    116129                        response.outputStream.flush();
    117130                } catch( Exception e ) {
    118131                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
    119132                        e.printStackTrace();
     133                } finally {
     134                        // Delete the file since it has to be downloaded only once
     135                        fileService.delete( session.process[ processId ].filename );
    120136                }
    121137        }
     
    145161                        }
    146162                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
    147 
     163                       
     164                        sampleExcelService.sessionToken = session.sessionToken
     165                       
    148166                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) {
    149167                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
  • trunk/grails-app/controllers/nl/tno/massSequencing/RunController.groovy

    r67 r70  
    44import grails.converters.JSON
    55import nl.tno.massSequencing.auth.*
     6import nl.tno.massSequencing.classification.*;
    67
    78import org.codehaus.groovy.grails.commons.ConfigurationHolder
     
    3334                        "r.name",
    3435                        "COUNT( DISTINCT a )",
    35                         "SUM( a.numSequences )"
     36                        "SUM( a.numSequences )",
     37                        "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.run = r)"
    3638                ]
    3739
     
    4143                // Retrieve data from assaySample table
    4244                def from = "Run r LEFT JOIN r.assaySamples a"
    43 
     45               
    4446                // This closure determines what to do with a row that is retrieved from the database.
    4547                def convertClosure = {
     
    4850                        def numSamples = it[ 2 ];
    4951                        def numSequences = it[ 3 ];
     52                        def numClassified = it[ 4 ];
    5053
    5154                        // Create buttons in the last three columns
     
    7174                                numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-",     // it.numSequences(),
    7275                                numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(),
     76                                numClassified > 0 ? g.formatNumber( number: numClassified, format: "###,###,##0" ) : "-", // it.percentageClassified
    7377                                editButton,
    7478                                deleteButton,
     
    107111
    108112                // Determine several parameters to show on screen
    109                
     113                def numClassified = Classification.executeQuery( "SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample IN (:assaySamples)", [ "assaySamples": run.assaySamples ] );
    110114               
    111115                // Send the assay information to the view
    112                 [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true]
     116                [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true, "numClassified": numClassified ? numClassified[ 0 ] : 0 ]
    113117        }
    114118       
     
    817821                        name = "runs";
    818822
    819                 // Export the sequences and quality scores
    820                 response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
    821                 try {
    822                         fastaService.export( assaySamples.unique(), response.getOutputStream() );
    823                         response.outputStream.flush();
    824                 } catch( Exception e ) {
    825                         log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
    826                         e.printStackTrace();
    827                 }
     823                       
     824                // Start the export in the background
     825                def returnUrl = createLink( controller: "run", action: "index" ).toString()
     826                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
     827                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
     828               
     829                // Show a waiting screen
     830                redirect( url: url );
    828831        }
    829832       
     
    858861                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
    859862
     863                        sampleExcelService.sessionToken = session.sessionToken
     864                       
    860865                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) {
    861866                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
  • trunk/grails-app/controllers/nl/tno/massSequencing/SampleController.groovy

    r55 r70  
    1414
    1515        /**
    16         * Exports data about one or more studies in fasta format
    17         */
    18    def exportAsFasta = {
    19            def assaySamples = getAssaySamples( params );
    20            def name
     16         * Exports data about one or more studies in fasta format
     17         */
     18        def exportAsFasta = {
     19                def assaySamples = getAssaySamples( params );
     20                def name
    2121
    22            if( assaySamples == null ) {
    23                    return
    24            } else if( assaySamples*.sample.unique().size() == 1 ) {
    25                    name = "Sample_" + assaySamples[0].sample.name?.replace( ' ', '_' );
    26            } else {
    27                    name = "samples";
    28            }
     22                if( assaySamples == null ) {
     23                        return
     24                } else if( assaySamples*.sample.unique().size() == 1 ) {
     25                        name = "Sample_" + assaySamples[0].sample.name?.replace( ' ', '_' );
     26                } else {
     27                        name = "samples";
     28                }
    2929
    30            // Export the sequences and quality scores
    31            response.setHeader "Content-disposition", "attachment; filename=" + name.trim() + ".zip"
    32            try {
    33                    fastaService.export( assaySamples.unique(), response.getOutputStream() );
    34                    response.outputStream.flush();
    35            } catch( Exception e ) {
    36                    log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
     30                // Start the export in the background
     31                def returnUrl = params.url ? params.url.toString() : createLink( controller: "run" ).toString()
     32                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
     33                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
     34               
     35                // Show a waiting screen
     36                redirect( url: url );
     37        }
     38
     39        /**
     40         * Export metadata of one or more studies in excel format
     41         */
     42        def exportMetaData = {
     43                def assaySamples = getAssaySamples( params );
     44                def name
     45
     46                if( assaySamples == null ) {
     47                        return
     48                } else if( assaySamples*.sample.unique().size() == 1 ) {
     49                        name = "Sample_" + assaySamples[0].sample.name?.replace( ' ', '_' );
     50                } else {
     51                        name = "samples";
     52                }
     53
     54                // Export the metadata
     55                response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
     56                try {
     57                        // The export functionality needs a assaySample-tag list, but it
     58                        // should be empty when only exporting metadata
     59                        def tags = [];
     60                        assaySamples.unique().each { assaySample ->
     61                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
     62                        }
     63
     64                        sampleExcelService.sessionToken = session.sessionToken
     65
     66                        sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() );
     67                        response.outputStream.flush();
     68                } catch( Exception e ) {
     69                        log.error( "Exception occurred during export of metadata. Probably the user has cancelled the download." );
    3770                        e.printStackTrace();
    38            }
    39    }
     71                }
     72        }
    4073
    41    /**
    42         * Export metadata of one or more studies in excel format
    43         */
    44    def exportMetaData = {
    45            def assaySamples = getAssaySamples( params );
    46            def name
    4774
    48            if( assaySamples == null ) {
    49                    return
    50            } else if( assaySamples*.sample.unique().size() == 1 ) {
    51                    name = "Sample_" + assaySamples[0].sample.name?.replace( ' ', '_' );
    52            } else {
    53                    name = "samples";
    54            }
    5575
    56            // Export the metadata
    57            response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
    58            try {
    59                    // The export functionality needs a assaySample-tag list, but it
    60                    // should be empty when only exporting metadata
    61                    def tags = [];
    62                    assaySamples.unique().each { assaySample ->
    63                            tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
    64                    }
    65                    sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() );
    66                    response.outputStream.flush();
    67            } catch( Exception e ) {
    68                    log.error( "Exception occurred during export of metadata. Probably the user has cancelled the download." );
    69                    e.printStackTrace();
    70            }
    71    }
    72                
    73        
    74        
    75                
     76
    7677        /**
    7778         * Parse the given parameters and try to extract assaysamples using ids and tokens of samples
     
    115116        }
    116117
    117        
     118
    118119}
  • trunk/grails-app/controllers/nl/tno/massSequencing/StudyController.groovy

    r59 r70  
    4444                }
    4545
    46                 // Export the sequences and quality scores
    47                 response.setHeader "Content-disposition", "attachment; filename=" + name.trim() + ".zip"
    48                 try {
    49                         fastaService.export( assaySamples.unique(), response.getOutputStream() );
    50                         response.outputStream.flush();
    51                 } catch( Exception e ) {
    52                         log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
    53                         e.printStackTrace();
    54                 }
     46                // Start the export in the background
     47                def returnUrl = params.url ? params.url.toString() : createLink( controller: "study" ).toString()
     48                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
     49                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
     50               
     51                // Show a waiting screen
     52                redirect( url: url );
    5553        }
    5654
     
    7977                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
    8078                        }
     79
     80                        sampleExcelService.sessionToken = session.sessionToken
     81                       
    8182                        sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() );
    8283                        response.outputStream.flush();
  • trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy

    r63 r70  
    1212        def classificationService
    1313        def sessionFactory
     14        def workerService
    1415       
    1516        /**************************************************************************
     
    2425        def parseUploadedFiles = {
    2526                def entityType = params.entityType
     27                def entityId = params.id
    2628               
    2729                // Check whether files are given
    28                 def names = params.list( 'sequencefiles' )
     30                def names = [] + params.list( 'sequencefiles' )
    2931
    3032                if( !names ) {
     
    3840                }
    3941               
    40                 // Create a unique process identifier
    41                 String processId = UUID.randomUUID().toString();
    42                                        
    43                 // Save filenames in session
    44                 if( !session.process )
    45                         session.process = [:]
    46                        
    47                 if( !session.process[ processId ] )
    48                         session.process[ processId ] = [:]
    49                          
    50                 session.process[ processId ].filenames = names;
    51                 session.process[ processId ].entityId = params.id;
    52                 session.process[ processId ].entityType = entityType
    53                
    5442                // Check for total size of the files in order to be able
    5543                // to show a progress bar
     
    5846                        filesize += fileService.get( it )?.length()
    5947                }
    60                
    61                 if( !session.progress )
    62                         session.progress = [:]
    63                
    64                 session.progress[ processId ] = [
    65                         stepNum: 1,
    66                         numSteps: 2,
    67                         stepDescription: 'Parsing files',       // Second step is Store classification
    68                        
    69                         stepProgress: 0,
    70                         stepTotal: filesize
    71                 ]
     48
     49                // Create a unique process identifier
     50                String processId = workerService.initProcess( session, "Parsing files", 2, filesize );
    7251                                       
    73                 render( view: 'showProcessScreen', model: [
    74                         processUrl: createLink( controller: "import", action: "processUploadedFiles" ),
    75                         processParameters: [ processId: processId, entityId: params.id, entityType: params.entityType ],
    76                         progressUrl: createLink( controller: "import", action: "getProgress", params: [ processId: processId ] ),
    77                         finishUrl: createLink( controller: "import", action: 'parseUploadResult', params: [ processId: processId, id: params.id, entityType: entityType] ),
    78                         errorUrl: createLink( controller: entityType, action: "show", id: params.id ),
    79                         entityId: params.id, entityType: params.entityType] );
    80         }
    81        
    82         /**
    83          * Processes uploaded files and tries to combine them with samples
    84          */
    85         def processUploadedFiles = {
    86                 def processId = params.processId
    87                 def entity
    88                
    89                 switch( params.entityType ) {
    90                         case "run":
    91                                 entity = getRun( params.entityId );
    92                                 break;
    93                         case "assay":
    94                                 entity = getAssay( params.entityId );
    95                                 break;
    96                         default:
    97                                 response.setStatus( 404, "No controller found" );
    98                                 render "";
    99                                 return;
    100                 }
    101                
    102                 def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
    103                
    104                 if (!entity) {
    105                         response.setStatus( 404, flash.error )
    106                         render "";
    107                         return
    108                 }
    109 
    110                 // Check whether files are given
    111                 def names = session.process[ processId ]?.filenames
    112 
    113                 if( !names ) {
    114                         println "Process ID: " + processId
    115                         session.process.each {
    116                                 println it.key + " = " + it.value;
    117                         }
    118                         response.setStatus( 500, "No files uploaded for processing" )
    119                         render "";
    120                         return
    121                 }
    122 
    123                 // If only 1 file is uploaded, it is given as String
    124                 ArrayList filenames = []
    125                 if( names instanceof String )
    126                         filenames << names
    127                 else
    128                         names.each { filenames << it }
    129 
     52                session.process[ processId ].filenames = names;
     53                session.process[ processId ].entityId = entityId;
     54                session.process[ processId ].entityType = entityType
     55               
     56                // Retrieve worker URL
     57                def finishUrl = createLink( controller: "import", action: 'parseUploadResult', params: [ processId: processId ] ).toString();
     58                def returnUrl = createLink( controller: entityType, action: "show", id: entityId ).toString();
     59               
     60                def url = workerService.startProcess( session, processId, finishUrl, returnUrl )
     61               
     62                //
     63                // Initiate work
     64                //
     65               
    13066                /* Parses uploaded files, discards files we can not handle
    131                  *
    132                  * [
    133                  *              success: [
    134                  *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
    135                  *                      [filename: 'cde.fasta', type: FASTA, numSequences: 140]
    136                  *                      [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
    137                  *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    138                  *              ],
    139                  *              failure: [
    140                  *                      [filename: 'testing.doc', message: 'Type not recognized']
    141                  *              ]
    142                  * ]
    143                  *
    144                  * The second parameter is a callback function to update progress indicators
    145                  */
    146                 def httpSession = session;
    147                 def onProgress = { progress, total ->
    148                         // Update progress
    149                         httpSession.progress[ processId ].stepTotal = total;
    150                         httpSession.progress[ processId ].stepProgress = progress;
    151                 }
    152                 def newStep = { total, description ->
    153                         // Start a new step
    154                         httpSession.progress[ processId ].stepTotal = total;
    155                         httpSession.progress[ processId ].stepProgress = 0;
    156                        
    157                         httpSession.progress[ processId ].stepDescription = description;
    158                         httpSession.progress[ processId ].stepNum++;
    159                 }
    160 
    161                 def parsedFiles = importService.parseFiles( filenames, onProgress, [progress: 0, total: httpSession.progress[ processId ].stepTotal ], newStep );
    162                
    163                 // Determine excel matches from the uploaded files
    164                 parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success );
    165                
    166                 // Match files with samples in the database
    167                 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );
    168 
    169                 // Sort files on filename
    170                 matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }
    171 
    172                 // Retrieve all files that have not been matched
    173                 def notMatchedFiles = parsedFiles.success.findAll {
    174                         switch( it.type ) {
    175                                 case "fasta":
    176                                         return !matchedFiles*.fasta*.filename.contains( it.filename );
    177                                 case "qual":
    178                                         return !matchedFiles*.feasibleQuals.flatten().filename.contains( it.filename );
    179                                 case "taxonomy":
    180                                         return !matchedFiles*.feasibleClassifications.flatten().filename.contains( it.filename );
    181                         }
    182                         return false;
    183                 }
    184                
    185                 // Saved file matches in session to use them later on
    186                 session.process[ processId ].processedFiles = [ parsed: parsedFiles,  matched: matchedFiles, notMatched: notMatchedFiles ];
    187 
    188                 render ""
    189         }
    190 
    191         def getProgress = {
    192                 def processId = params.processId;
    193                 if( !processId || !session.progress?.getAt( processId ) ) {
    194                         response.setStatus( 500, "No progress information found" );
    195                         render ""
    196                         return
    197                 }
    198                
    199                 render session.progress[ processId ] as JSON
     67                *
     68                * [
     69                *               success: [
     70                *                       [filename: 'abc.fasta', type: FASTA, numSequences: 190]
     71                *                       [filename: 'cde.fasta', type: FASTA, numSequences: 140]
     72                *                       [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
     73                *                       [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
     74                *               ],
     75                *               failure: [
     76                *                       [filename: 'testing.doc', message: 'Type not recognized']
     77                *               ]
     78                * ]
     79                *
     80                * The second parameter is a callback function to update progress indicators
     81                */
     82           def httpSession = session;
     83           def onProgress = { progress, total ->
     84                   // Update progress
     85                   httpSession.progress[ processId ].stepTotal = total;
     86                   httpSession.progress[ processId ].stepProgress = progress;
     87           }
     88           def newStep = { total, description ->
     89                   // Start a new step
     90                   httpSession.progress[ processId ].stepTotal = total;
     91                   httpSession.progress[ processId ].stepProgress = 0;
     92                   
     93                   httpSession.progress[ processId ].stepDescription = description;
     94                   httpSession.progress[ processId ].stepNum++;
     95           }
     96
     97           // Perform the actual computations asynchronously
     98           runAsync {
     99                   def entity
     100                   
     101                   // Determine entity and assaysamples
     102                   switch( httpSession.process[ processId ].entityType ) {
     103                           case "run":
     104                                   entity = getRun( httpSession.process[ processId ].entityId );
     105                                   break;
     106                           case "assay":
     107                                   entity = getAssay( httpSession.process[ processId ].entityId );
     108                                   break;
     109                           default:
     110                                   httpSession.progress[ processId ].error = true;
     111                                   httpSession.progress[ processId ].finished = true;
     112                                   return;
     113                   }
     114                   
     115                   if (!entity) {
     116                           httpSession.progress[ processId ].error = true;
     117                           httpSession.progress[ processId ].finished = true;
     118                           return;
     119                   }
     120   
     121                   def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( httpSession.user ) };
     122                   
     123                   def parsedFiles = importService.parseFiles( names, onProgress, [progress: 0, total: httpSession.progress[ processId ].stepTotal ], newStep );
     124                   
     125                   // Determine excel matches from the uploaded files
     126                   parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success );
     127                   
     128                   // Match files with samples in the database
     129                   def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );
     130   
     131                   // Sort files on filename
     132                   matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }
     133   
     134                   // Retrieve all files that have not been matched
     135                   def notMatchedFiles = parsedFiles.success.findAll {
     136                           switch( it.type ) {
     137                                   case "fasta":
     138                                           return !matchedFiles*.fasta*.filename.contains( it.filename );
     139                                   case "qual":
     140                                           return !matchedFiles*.feasibleQuals.flatten().filename.contains( it.filename );
     141                                   case "taxonomy":
     142                                           return !matchedFiles*.feasibleClassifications.flatten().filename.contains( it.filename );
     143                           }
     144                           return false;
     145                   }
     146                   
     147                   // Saved file matches in session to use them later on
     148                   httpSession.process[ processId ].processedFiles = [ parsed: parsedFiles,  matched: matchedFiles, notMatched: notMatchedFiles ];
     149                   
     150                   // Tell the frontend we are finished
     151                   httpSession.progress[ processId ].finished = true;
     152           }
     153               
     154                redirect( url: url );
    200155        }
    201156       
     
    208163                def entity
    209164               
    210                 switch( params.entityType ) {
     165                switch( session.process[ processId ].entityType ) {
    211166                        case "run":
    212                                 entity = getRun( params.id )
     167                                entity = getRun( session.process[ processId ].entityId )
    213168                                break;
    214169                        case "assay":
    215                                 entity = getAssay( params.id )
     170                                entity = getAssay( session.process[ processId ].entityId )
    216171                                break;
    217172                        default:
     
    234189                        return
    235190                }
    236                
    237191               
    238192                // Find matching sequenceData objects for taxonomyfiles that have not been matched
     
    245199                }
    246200               
    247                 [       entityType: params.entityType, processId: processId, entity: entity, id: params.id,
     201                [       entityType: session.process[ processId ].entityType, processId: processId, entity: entity,
    248202                        parsedFiles: session.process[ processId ].processedFiles.parsed,
    249203                        matchedFiles: session.process[ processId ].processedFiles.matched,
     
    257211        def returnWithoutSaving = {
    258212                def processId = params.processId;
    259 
     213                def entityType = session.process[ processId ].entityType;
     214                def entityId = session.process[ processId ].entityId;
     215               
    260216                // Delete all uploaded files from disk
    261217                session.process[ processId ]?.processedFiles?.parsed?.success?.each {
    262218                        fileService.delete( it.filename );
    263219                }
     220               
     221                // Clear process from session
     222                workerService.clearProcess( session, processId );
    264223
    265224                // Redirect to the correct controller           
    266                 switch( params.entityType ) {
     225                switch( entityType ) {
    267226                        case "run":
    268227                        case "assay":
    269                                 redirect( controller: params.entityType, action: "show", id: params.id );
     228                                redirect( controller: entityType, action: "show", id: entityId );
    270229                                return;
    271230                        default:
     
    282241         */
    283242        def saveMatchedFiles = {
    284                 def entityType = params.entityType
    285243                def processId = params.processId
     244
     245                def entityType = session.process[ processId ].entityType
     246                def entityId = session.process[ processId ].entityId
    286247               
    287248                session.process[ processId ].matchedFiles = params.file
     
    320281                }
    321282
    322                 if( !session.progress )
    323                         session.progress = [:]
    324                        
    325                 session.progress[ processId ] = [
    326                         stepNum: 2,
    327                         numSteps: 2,
    328                         stepDescription: 'Store sequence data and classification',
    329                        
    330                         stepProgress: 0,
    331                         stepTotal: filesize
    332                 ]
    333                                        
    334                 render( view: 'showProcessScreen', model: [
    335                         processUrl: createLink( controller: "import", action: "processMatchedFiles" ),
    336                         processParameters: [ processId: processId, entityId: params.id, entityType: params.entityType ],
    337                         progressUrl: createLink( controller: "import", action: "getProgress", params: [ processId: processId ] ),
    338                         finishUrl: createLink( controller: "import", action: 'saveMatchedResult', params: [ processId: processId, id: params.id, entityType: entityType] ),
    339                         errorUrl: createLink( controller: entityType, action: "show", id: params.id ),
    340                         entityId: params.id, entityType: params.entityType] );
    341         }
    342        
    343         /**
    344          * Saves processed files to the database, based on the selections made by the user
    345          */
    346         def processMatchedFiles = {
    347                 // load entity with id specified by param.id
    348                 def processId = params.processId;
    349 
     283                // Clear old process, but save useful data
     284                def processInfo = session.process[ processId ]
     285                workerService.clearProcess( session, processId );
     286               
     287                // Create a new unique process identifier
     288                processId = workerService.initProcess( session, "Store sequence data and classification", 2, filesize );
     289               
     290                session.progress[ processId ].stepNum = 2;
     291                session.process[ processId ] = processInfo;
     292               
     293                // Retrieve worker URL
     294                def finishUrl = createLink( controller: "import", action: 'saveMatchedResult', params: [ processId: processId ] ).toString();
     295                def returnUrl = createLink( controller: entityType, action: "show", entityId ).toString();
     296               
     297                def url = workerService.startProcess( session, processId, finishUrl, returnUrl )
     298               
     299                //
     300                // Initiate work
     301                //
    350302                // Check whether files are given
    351                 def files = session.process[ processId ].matchedFiles 
     303                def files = session.process[ processId ].matchedFiles
    352304                def remainingClassification = session.process[ processId ].matchedRemainingClassification;
    353305               
    354306                if( !files && !remainingClassification ) {
    355307                        flash.message = "No files were selected for import."
    356                         redirect( controller: params.entityType, action: 'show', 'id': params.entityId)
     308                        redirect( controller: session.process[ processId ].entityType, action: 'show', 'id': session.process[ processId ].entityId)
    357309                        return
    358310                }
     
    367319                }
    368320               
    369                 // Loop through all FASTA files. Those are the numeric elements in the 'files' array
    370                 def fastaReturn = saveMatchedFastaFiles( files, session.process[ processId ]?.processedFiles, onProgress );
    371                 def classificationReturn = saveRemainingClassificationFiles( remainingClassification, onProgress );
    372                
    373                 // Update classification (summary) for updated samples
    374                 def samplesClassified = [] + fastaReturn.samplesClassified + classificationReturn.samplesClassified;
    375                 classificationService.updateClassificationForAssaySamples( samplesClassified.findAll { it }.unique() )
    376                
    377                 def returnStructure = [
    378                         numSequenceFiles: fastaReturn.numSequenceFiles,
    379                         numQualFiles: fastaReturn.numQualFiles,
    380                         numClassificationFiles: fastaReturn.numClassificationFiles,
    381                         numExtraClassificationFiles: classificationReturn.numExtraClassifications,
    382                         numTotal: fastaReturn.numSequenceFiles + classificationReturn.numExtraClassifications,
    383                         errors: [] + fastaReturn.errors + classificationReturn.errors
    384                 ]
    385 
    386                 // Return all files that have not been moved
    387                 session.process[ processId ]?.processedFiles?.parsed?.success?.each {
    388                         fileService.delete( it.filename );
    389                 }
    390                
    391                 session.process[ processId ].result = returnStructure;
    392                
    393                 response.contentType = "text/plain"
    394                 render "";
     321                // Run the computations asynchronously, since it takes a lot of time
     322                runAsync {
     323                        // Loop through all FASTA files. Those are the numeric elements in the 'files' array
     324                        def fastaReturn = saveMatchedFastaFiles( files, httpSession.process[ processId ]?.processedFiles, onProgress );
     325                        def classificationReturn = saveRemainingClassificationFiles( remainingClassification, onProgress );
     326                       
     327                        // Update classification (summary) for updated samples
     328                        def samplesClassified = [] + fastaReturn.samplesClassified + classificationReturn.samplesClassified;
     329                        classificationService.updateClassificationForAssaySamples( samplesClassified.findAll { it }.unique() )
     330                       
     331                        def returnStructure = [
     332                                numSequenceFiles: fastaReturn.numSequenceFiles,
     333                                numQualFiles: fastaReturn.numQualFiles,
     334                                numClassificationFiles: fastaReturn.numClassificationFiles,
     335                                numExtraClassificationFiles: classificationReturn.numExtraClassifications,
     336                                numTotal: fastaReturn.numSequenceFiles + classificationReturn.numExtraClassifications,
     337                                errors: [] + fastaReturn.errors + classificationReturn.errors
     338                        ]
     339                       
     340                        // Return all files that have not been moved
     341                        httpSession.process[ processId ]?.processedFiles?.parsed?.success?.each {
     342                                fileService.delete( it.filename );
     343                        }
     344                       
     345                        httpSession.process[ processId ].result = returnStructure;
     346                       
     347                        // Tell the frontend we are finished
     348                        httpSession.progress[ processId ].finished = true;
     349       
     350                }
     351               
     352                redirect( url: url );
    395353        }
    396354       
     
    566524                        }
    567525                }
    568                
     526
     527                // Determine where to redirect the user to
     528                def entityType = session.process[ processId ].entityType;
     529                def entityId = session.process[ processId ].entityId;
     530                               
    569531                // Clear session
    570                 session.process?.remove( processId );
    571                 session.progress?.remove( processId );
     532                workerService.clearProcess( session, processId );
    572533               
    573534                // Redirect user
    574                 redirect( controller: params.entityType, action: "show", id: params.id )
    575         }
    576        
    577         def deleteData = {
    578                 // load study with id specified by param.id
    579                 def sequenceData
    580                
    581                 try {
    582                         sequenceData = SequenceData.get(params.id as Long)
    583                 } catch( Exception e ) {}
    584 
    585                 if (!sequenceData) {
    586                         flash.error = "No sequencedata found with id: $params.id"
    587                         redirect( controller: 'study' )
    588                         return
    589                 }
    590 
    591                 def entityId
    592                 def entityType
    593                
    594                 switch( params.entityType ) {
    595                         case "run":
    596                                 entityId = sequenceData.sample.run?.id;
    597                                 entityType = "run"
    598                                 break;
    599                         case "assay":
    600                         default:
    601                                 entityType = "assay";
    602                                 entityId = sequenceData.sample.assay.id;
    603                                 break;
    604                 }
    605                  
    606                 def numFiles = sequenceData.numFiles();
    607                 def sample = sequenceData.sample;
    608                  
    609                 // Set flushmode to auto, since otherwise the sequencedata will
    610                 // not be removed
    611                 sessionFactory.getCurrentSession().setFlushMode( org.hibernate.FlushMode.AUTO );
    612                
    613                 sample.removeFromSequenceData( sequenceData );
    614                 sequenceData.delete(flush:true);
    615                 sample.resetStats();
    616                 sample.save();
    617                
    618                 flash.message = numFiles + " file" + (numFiles != 1 ? "s have" : " has" ) + " been deleted from this sample"
    619 
    620                 redirect( controller: entityType, action: 'show', id: entityId )
    621         }
    622        
     535                redirect( controller: entityType, action: "show", id: entityId )
     536        }
     537
    623538        protected Assay getAssay(def assayId) {
    624539                // load assay with id specified by param.id
  • trunk/grails-app/domain/nl/tno/massSequencing/auth/User.groovy

    r53 r70  
    1818                table 'gscfuser'
    1919                auth cascade: "all-delete-orphan"
     20        }
     21       
     22        public String toString() {
     23                return ( this.username ?: "" )
    2024        }
    2125       
  • trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy

    r67 r70  
    195195                        leafPath = leaf.givePathNames();
    196196                       
    197                         println "Path for leaf " + leaf.name + " (" + leaf.level + "): " + leafPath
    198197                        numLeafs = leafPath.size();
    199198                       
  • trunk/grails-app/services/nl/tno/massSequencing/DataTablesService.groovy

    r69 r70  
    22
    33class DataTablesService {
    4 
     4        static transactional = false
     5       
    56        /**
    67         * Retrieves data from the database for showing in a datatables table
  • trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy

    r68 r70  
    1515        def classificationService
    1616        def excelService
     17        def workerService
    1718
    1819        static transactional = false
     20       
     21        // After exporting sequence and qual files, the GSCF data has to be retrieved. However,
     22        // we don't know how long that takes, but is it proportional to the number of samples
     23        // For that reason, we append a number to the total size, so the progress bar will keep
     24        // some space open while fetching GSCF data
     25        def AMOUNT_PER_ASSAYSAMPLE = 100000;
    1926
    2027        /**
     
    523530                return lengthList;
    524531        }
     532       
     533        /**
     534         * Start the process of exporting fasta data in the background
     535         *
     536         * @param assaySamples  List of assaysamples to be exported
     537         * @param httpSession   Reference to the HTTP session
     538         * @param name                  Name of the exported filename
     539         * @return                              URL to redirect the user to, that shows information about the progress of the download
     540         */
     541        public String startExportProcess( def assaySamples, def httpSession, String name, String returnUrl, String finishUrl ) {
     542                // Determine the total filesize to be processed.
     543                def permanentDir = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir );
     544                def filesize = 0
     545                assaySamples.each { assaySample ->
     546                        assaySample.sequenceData.each {
     547                                filesize += fileService.get( it.sequenceFile, permanentDir )?.size() ?: 0;
     548                                if( it.qualityFile )
     549                                        filesize += fileService.get( it.qualityFile, permanentDir )?.size() ?: 0;
     550                        }
     551                };
     552       
     553                // After the files are concatenated, the system has to retrieve data from GSCF. However,
     554                // we don't know how long that takes, but is it proportional to the number of samples
     555                // For that reason, we append a number to the total size, so the progress bar will keep
     556                // some space open while fetching GSCF data
     557                filesize += assaySamples.size() * AMOUNT_PER_ASSAYSAMPLE;
     558       
     559                // Empty the assaySample list, since the objects should be retrieved from the database again by the
     560                // initiated thread. Otherwise, hibernate session errors occur, since the other thread has a different
     561                // hibernate session
     562                def assaySampleIds = assaySamples.collect { it.id }
     563                assaySamples = null;
     564               
     565                // Create a worker screen to create the fasta file in the background
     566                def processId = workerService.initProcess( httpSession, "Creating your download", 1, filesize )
     567                httpSession.process[ processId ][ "returnUrl" ] = returnUrl
     568                httpSession.process[ processId ].name = name;
     569               
     570                // Retrieve worker URL; returnUrl is the same as the errorUrl
     571                finishUrl = sprintf( finishUrl, processId );
     572                def url = workerService.startProcess( httpSession, processId, finishUrl, returnUrl, returnUrl )
     573               
     574                // Make sure the background process can send the progress to the HTTP session.
     575                def onProgress = { progress ->
     576                        // Update progress
     577                        httpSession.progress[ processId ].stepProgress = progress;
     578                }
     579               
     580                // Perform the real work in a background thread
     581                runAsync {
     582                        def samples = assaySampleIds.collect { AssaySample.get( it ) }
     583                       
     584                        def filename = fileService.getUniqueFilename( "fastadownload" );
     585                        def file = fileService.get( filename );
     586
     587                        try {
     588                                export( samples, new FileOutputStream( file ), httpSession.sessionToken, onProgress );
     589                        } catch( Exception e ) {
     590                                log.error( "Exception occurred during export of sequences: " );
     591                                e.printStackTrace();
     592
     593                                httpSession.progress[ processId ].error = true;
     594                                httpSession.progress[ processId ].errorMessage += e.getMessage();
     595                        }
     596                       
     597                        // Store the filename for later download
     598                        httpSession.process[ processId ].filename = filename;
     599                       
     600                        // Tell the frontend we are finished
     601                        httpSession.progress[ processId ].finished = true;
     602                }
     603               
     604                return url;
     605        }
    525606
    526607        /**
     
    530611         * @return
    531612         */
    532         public def export( List assaySamples, OutputStream outStream, String name = null ) {
     613        public def export( List assaySamples, OutputStream outStream, def sessionToken, Closure onProgress, String name = null ) {
    533614                if( !assaySamples || assaySamples.size() == 0 )
    534615                        return false;
     
    573654                BufferedWriter zipWriter = new BufferedWriter( new OutputStreamWriter( zipFile ) );
    574655
     656                // Initialize the progress. This is the combined filesize of the sequence and quality files
     657                def progress = 0;
     658               
     659                // Create a reference to the assaySample we're processing here, in order to have it set if
     660                // an error occurs
     661                def assaySample
     662
    575663                // We have to loop twice through the sequenceData, since we can't write part of the sequence
    576664                // file and part of the qual files mixed. We have to write the full sequence file first.
     
    578666                        zipFile.putNextEntry( new ZipEntry( name + ".fna" ) );
    579667
    580                         assaySamples.each { assaySample ->
     668                        for( def i = 0; i < assaySamples.size(); i++ ) {
     669                                assaySample = assaySamples[ i ];
     670                               
    581671                                if( assaySample.numSequences() > 0 ) {
    582672                                        def currentTag = tags.find { it.assaySampleId == assaySample.id };
    583673
    584674                                        assaySample.sequenceData.each { sequenceData ->
    585                                                 if( sequenceData && sequenceData.sequenceFile )
    586                                                         copyFastaFileForExport( fileService.get( sequenceData.sequenceFile, permanentDirectory ), currentTag.tag, zipWriter)
     675                                                if( sequenceData && sequenceData.sequenceFile ) {
     676                                                        def file = fileService.get( sequenceData.sequenceFile, permanentDirectory );
     677                                                       
     678                                                        def begin = System.currentTimeMillis();
     679                                                       
     680                                                        copyFastaFileForExport( file, currentTag.tag, zipWriter)
     681                                                       
     682                                                        log.trace "Exported FASTA: " + file + ": " + ( file.size() / 1000 ) + "kb / " + (System.currentTimeMillis() - begin ) + " ms";
     683
     684                                                        // Update progress
     685                                                        progress += file.size();
     686                                                        onProgress( progress );
     687                                                }
    587688                                        }
    588689                                }
     
    594695                                zipFile.putNextEntry( new ZipEntry( name + ".qual" ) );
    595696
    596                                 assaySamples.each { assaySample ->
     697                                for( def i = 0; i < assaySamples.size(); i++ ) {
     698                                        assaySample = assaySamples[ i ];
     699                                       
    597700                                        if( assaySample.numSequences() > 0 ) {
    598701                                                def currentTag = tags.find { it.assaySampleId == assaySample.id };
    599702
    600703                                                assaySample.sequenceData.each { sequenceData ->
    601                                                         if( sequenceData && sequenceData.sequenceFile && sequenceData.qualityFile )
     704                                                        if( sequenceData && sequenceData.sequenceFile && sequenceData.qualityFile ) {
     705                                                                def file = fileService.get( sequenceData.qualityFile, permanentDirectory );
     706
     707                                                                def begin = System.currentTimeMillis();
     708                                                       
    602709                                                                copyQualFileForExport( fileService.get( sequenceData.qualityFile, permanentDirectory ), currentTag.tag, zipWriter)
     710                                                               
     711                                                                log.trace "Exported QUAL: " + file + ": " + Math.round( file.size() / 1000 ) + "kb / " + (System.currentTimeMillis() - begin ) + " ms";
     712                                                               
     713                                                                // Update progress
     714                                                                progress += file.size();
     715                                                                onProgress( progress );
     716                                                        }
    603717                                                }
    604718                                        }
     
    607721                                zipWriter.flush();
    608722                                zipFile.closeEntry();
     723                        } else {
     724                                // Update progress with the filesize of all qual files
     725                                for( def i = 0; i < assaySamples.size(); i++ ) {
     726                                        assaySample = assaySamples[ i ];
     727                                       
     728                                        if( assaySample.numSequences() > 0 ) {
     729                                                assaySample.sequenceData.each { sequenceData ->
     730                                                        if( sequenceData && sequenceData.sequenceFile && sequenceData.qualityFile ) {
     731                                                                def file = fileService.get( sequenceData.qualityFile, permanentDirectory );
     732
     733                                                                // Update progress
     734                                                                progress += file.size();
     735                                                                onProgress( progress );
     736                                                        }
     737                                                }
     738                                        }
     739                                }
    609740                        }
    610741
    611742                } catch( Exception e ) {
    612743                        log.error "Error while writing to fastafile or qualfile: " + e.getMessage();
    613                         e.printStackTrace();
     744
     745                        // We shouldn't continue if anything went wrong
     746                        throw new Exception( "Error while exporting data for sample " + assaySample.sample.name + ": " + e.getCause().getMessage() );
    614747                } finally {
    615748                        // Always close zip entry
     
    620753                        }
    621754                }
    622 
     755               
    623756                // Export a tab delimited file with tags
    624757                zipFile.putNextEntry( new ZipEntry( name + ".tab" ) );
     
    635768                // Export an excel file with information about the samples
    636769                zipFile.putNextEntry( new ZipEntry( name + ".xls" ) );
     770                sampleExcelService.sessionToken = sessionToken
    637771                sampleExcelService.exportExcelSampleData( assaySamples, tags, zipFile );
    638772                zipFile.flush();
    639773                zipFile.closeEntry();
     774               
     775                // After the files are concatenated, the system has to retrieve data from GSCF. However,
     776                // we don't know how long that takes, but is it proportional to the number of samples
     777                // For that reason, we append a number to the total size, so the progress bar will keep
     778                // some space open while fetching GSCF data
     779                onProgress( progress + assaySamples.size() * AMOUNT_PER_ASSAYSAMPLE );
    640780               
    641781                // Export an excel file with information about the classification samples
     
    842982                } catch( Exception e ) {
    843983                        log.error( "An error occurred while copying contents from " + inFile.getName() + ": " + e.getMessage() );
    844                         e.printStackTrace();
    845                         return false;
     984                       
     985                        // Throw the exception, since the calling method should decide whether to continue or not
     986                        throw new Exception( "An error occurred while copying contents from FASTA file " + inFile.getName() + ": " + e.getMessage(), e );
    846987                }
    847988        }
     
    9411082                } catch( Exception e ) {
    9421083                        log.error( "An error occurred while copying contents from " + inFile.getName() + ": " + e.getMessage() );
    943                         e.printStackTrace();
    944                         return false;
     1084                       
     1085                        // Throw the exception, since the calling method should decide whether to continue or not
     1086                        throw new Exception( "An error occurred while copying contents from QUAL file " + inFile.getName() + ": " + e.getMessage(), e );
    9451087                }
    9461088        }
  • trunk/grails-app/services/nl/tno/massSequencing/SampleExcelService.groovy

    r58 r70  
    88        def gscfService
    99       
    10     static transactional = true
     10        def sessionToken
     11       
     12    static transactional = false
    1113
    1214        // Fields to be edited using excel file and manually
     
    297299                // Gather data from GSCF.
    298300                def sampleTokens = assaySamples*.sample.unique()*.sampleToken;
    299                 def sessionToken = RequestContextHolder.currentRequestAttributes().getSession().sessionToken
     301               
    300302                def gscfData
    301303                try {
  • trunk/grails-app/services/nl/tno/massSequencing/imports/FuzzySearchService.groovy

    r67 r70  
    33class FuzzySearchService {
    44
    5     static transactional = true
     5    static transactional = false
    66       
    77        /**
  • trunk/grails-app/services/nl/tno/massSequencing/integration/GscfService.groovy

    r63 r70  
    1414class GscfService {
    1515        def config = ConfigurationHolder.config
    16 
    17         static transactional = true
     16       
     17        static transactional = false
    1818
    1919        /**
  • trunk/grails-app/views/assay/index.gsp

    r60 r70  
    2222                                                <th># samples</th>
    2323                                                <th>avg sequences / sample</th>
     24                                                <th># classified</th>
    2425                                                <th class="nonsortable"></th>
    2526                                                <th class="nonsortable"></th>
  • trunk/grails-app/views/assay/show.gsp

    r62 r70  
    4747        </h1>
    4848       
    49         <label>Study</label>: <a target="_top" href="${ assay.study.viewUrl() }">${assay.study.name}</a><br />
    50         <label>Assay</label>: ${assay.name}<br />
    51         <label># samples</label>: ${assay.assaySamples?.size()}<br />
    52         <label># sequences</label>: <g:formatNumber number="${assay.numSequences()}" format="###,###,##0" /><br />
    53         <label># files</label>: ${assay.numFiles()}<br />
    54 
     49        <div class="blok_data">
     50                <label>Study</label>: <a target="_top" href="${ assay.study.viewUrl() }">${assay.study.name}</a><br />
     51                <label>Assay</label>: ${assay.name}<br />
     52        </div>
     53        <div class="blok_data">
     54                <label># samples</label>: ${assay.assaySamples?.size()}<br />
     55                <label># sequences</label>: <g:formatNumber number="${assay.numSequences()}" format="###,###,##0" /><br />
     56                <label>% classified</label>: <g:formatNumber number="${numClassified / assay.numSequences()}" format="0.0%" /><br />
     57        </div>
    5558        <!-- Samples -->
    5659        <h2>Samples</h2>
  • trunk/grails-app/views/run/index.gsp

    r60 r70  
    3030                                        <tr>
    3131                                                <th width="5" class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    32                                                 <th width="49%" >Run</th>
    33                                                 <th width="15%"># samples</th>
    34                                                 <th width="15%"># sequences</th>
     32                                                <th width="45%" >Run</th>
     33                                                <th width="14%" class="formatted-num"># samples</th>
     34                                                <th width="14%" class="formatted-num"># sequences</th>
     35                                                <th width="14%" class="formatted-num"># classified</th>
    3536                                                <th width="5" class="nonsortable"></th>
    3637                                                <th width="5" class="nonsortable"></th>
  • trunk/grails-app/views/run/show.gsp

    r62 r70  
    8888                <br />
    8989                <label># sequences</label>: <g:formatNumber number="${run.numSequences()}" format="###,###,##0" /><br />
    90                 <label># files</label>: ${run.numFiles()}<br />
     90                <label>% classified</label>: <g:formatNumber number="${numClassified / run.numSequences()}" format="0.0%" /><br />
    9191        </div>
    9292        <p class="options">
  • trunk/grails-app/views/worker/process.gsp

    r69 r70  
    22<head>
    33        <meta name="layout" content="main" />
    4         <title>Processing files | Mass Sequencing | dbNP</title>
     4        <title>Processing | Mass Sequencing | dbNP</title>
    55
    66        <script type="text/javascript">
     
    1111                                height: 200,
    1212                                width: 400,
    13                                 title: 'Please wait while processing files',
     13                                title: 'Please wait while processing',
    1414                                modal: true,
    1515                                autoOpen: true,
     
    2626
    2727                        progressInterval = setTimeout(updateProgress, 250);
    28                                                        
    29                         // Call processing URL
    30                         $.ajax({
    31                           type: 'POST',
    32                           url: "${processUrl.encodeAsJavaScript()}",
    33                           data: "${processParameters*.toString().join("&")}",
    34                           success: function(data) {
    35                                   // Stop update progress bar
    36                                   clearTimeout( progressInterval );
    3728
    38                                   window.location.replace( "${finishUrl.encodeAsJavaScript()}" );
    39                           },
    40                           error: function(xhr, textStatus, errorThrown) {
    41                                        
    42                                   // Stop update progress bar (but update it for the last time)
    43                                   updateProgress()
    44                                   clearTimeout( progressInterval );
    45 
    46                                   alert( "Error " + xhr.status + ": " + xhr.statusText + ". Please contact your system administrator." );
    47                                   window.location.replace( "${errorUrl?.encodeAsJavaScript()}" );
    48                         }
    49                         });
    5029                });
    5130
     
    6544                                        $("#progressbar").progressbar("value", progress.stepProgress / progress.stepTotal * 100 );
    6645
    67                                         // Make sure the next progress will be retrieved in 250 ms
    68                                         progressInterval = setTimeout(updateProgress, 250);                                     
     46                                        // If the executor is finished, we should finish this screen
     47                                        if( progress.finished ) {
     48                                                  // Stop update progress bar
     49                                                  clearTimeout( progressInterval );
     50
     51                                                  // Show a message and a link to the return url, in case the finishUrl results
     52                                                  // in a download.
     53                                                  if( "${returnUrl.encodeAsJavaScript()}" != "" ) {
     54                                                        // Set HTML title to inform the user
     55                                                        document.title = "Finished processing | Mass Sequencing | dbNP";
     56                                                        $( "#wait_dialog" ).dialog( 'option', 'title', "Finished processing" );
     57                                                       
     58                                                          $( '.spinner, #pleasewait, #step, #progressbar' ).hide();
     59                                                          $( '#return' ).show();
     60                                                  }
     61
     62                                                  if( progress.error ) {
     63                                                          showError( progress.errorMessage );
     64                                                  } else {
     65                                                          // Redirect the user to the finish url
     66                                                          window.location.replace( "${finishUrl.encodeAsJavaScript()}" );
     67                                                  }
     68                                        } else {
     69                                                // Make sure the next progress will be retrieved in 250 ms
     70                                                progressInterval = setTimeout(updateProgress, 250);
     71                                        }                                       
     72                                },
     73                                error: function(xhr, textStatus, errorThrown) {
     74                                          // Stop update progress bar (but update it for the last time)
     75                                          clearTimeout( progressInterval );
     76
     77                                          // Show an error message
     78                                          showError( xhr.status + ": " + xhr.statusText + ". Please contact your system administrator." );
    6979                                }
    7080                        });
     81                }
     82
     83                function showError( message ) {
     84                        // Set HTML title to inform the user
     85                        document.title = "Error processing | Mass Sequencing | dbNP";
     86                        $( "#wait_dialog" ).dialog( 'option', 'title', "Error processing" );
     87                       
     88                          $( '.spinner, #pleasewait, #step, #progressbar, #return' ).hide();
     89                          $( '.errorMessage' ).html( message );
     90                          $( '#error' ).show();
    7191                }
    7292        </script>
     
    80100                        margin-top: 10px;
    81101                }
     102               
     103                #return { display: none; margin-top: 30px;}
     104                #error { display: none; color: red; margin-top: 15px; }
    82105        </style>
    83106</head>
     
    85108        <div id="wait_dialog">
    86109                <span class="spinner" style="display: inline-block; zoom: 1; *display: inline;"></span>
    87                 <p>Please wait while processing your files.</p>
     110                <p id="pleasewait">Please wait while processing your data.</p>
    88111                <p id="step" style="display: none;">
    89112                        Step <span id="stepNum"></span> / <span id="numSteps"></span>: <span id="stepDescription"></span>
    90113                </p>
    91        
    92114                <div id="progressbar"></div>
    93        
     115               
     116                <p id="error">
     117                        <span class="errorMessage"></span>
     118                        <br /><br />
     119                        <a href="${errorUrl}">Click here to return</a>
     120                </p>
     121                <p id="return">
     122                        <a href="${returnUrl}">Click here to return</a>
     123                </p>
    94124        </div>
    95125</body>
  • trunk/web-app/css/datatables/demo_table_jui.css

    r59 r70  
    136136.dataTables_processing {
    137137        position: absolute;
    138         top: 0px;
     138        top: 50%;
    139139        left: 50%;
    140140        width: 250px;
    141141        margin-left: -125px;
     142        margin-top: -30px; /* Half the padding + line-height */
     143       
    142144        border: 1px solid #ddd;
    143145        background-color: white;
     
    145147        color: #999;
    146148        font-size: 11px;
    147         padding: 2px 0;
     149       
     150        padding: 20px 0;
     151        line-height: 20px;
     152       
     153        z-index: 100;
    148154}
    149155
Note: See TracChangeset for help on using the changeset viewer.