Changeset 70
- Timestamp:
- Jun 17, 2011, 1:54:56 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 28 added
- 20 edited
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/application.properties
r58 r70 1 1 #Grails Metadata file 2 #Wed May 11 12:10:59CEST 20112 #Wed Jun 15 09:48:55 CEST 2011 3 3 app.build.display.info=0 4 app.grails.version=1.3.75 app.name=massSequencing6 app.servlet.version=2.47 4 app.version=0.1 5 plugins.tomcat=1.3.7 8 6 plugins.famfamfam=1.0.1 9 plugins.hibernate=1.3.710 7 plugins.jquery=1.4.4.1 11 8 plugins.jquery-ui=1.8.7 12 plugins.tomcat=1.3.7 9 app.servlet.version=2.4 10 plugins.hibernate=1.3.7 11 app.name=massSequencing 12 app.grails.version=1.3.7 13 plugins.executor=0.2 -
trunk/grails-app/conf/BaseFilters.groovy
r63 r70 198 198 // Also never perform synchronization when files are uploaded. That could lead to concurrent modification 199 199 // 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" ) { 201 201 return true; 202 202 } -
trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy
r63 r70 3 3 import java.util.List; 4 4 import grails.converters.JSON 5 import nl.tno.massSequencing.classification.*; 5 6 6 7 import org.codehaus.groovy.grails.commons.ConfigurationHolder … … 41 42 "COUNT( DISTINCT s )", 42 43 "SUM( s.numSequences ) / COUNT( DISTINCT s )", 44 "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.assay = a)", 43 45 "a.study.studyToken" 44 46 ] 45 47 46 def groupColumns = columns[0..2] + columns[ 5];48 def groupColumns = columns[0..2] + columns[ 6 ]; 47 49 def orderByMapping = null; 48 50 … … 59 61 def numSamples = it[ 3 ]; 60 62 def numSequences = it[ 4 ]; 61 def studyToken = it[ 5 ]; 63 def numClassification = it[ 5 ]; 64 def studyToken = it[ 6 ]; 62 65 63 66 // Create buttons in the last three columns … … 77 80 numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-", // it.numSequences(), 78 81 numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(), 82 numClassification > 0 ? g.formatNumber( number: numClassification, format: "###,###,##0" ) : "-", // it.numClassification(), 79 83 editButton, 80 84 chartButton … … 119 123 def otherRuns = Run.list( sort: "name" ).findAll { !it.assays.contains( assay ) } 120 124 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 121 128 // 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] 123 130 } 124 131 … … 556 563 */ 557 564 def exportAsFasta = { 558 def assaySamples = getAssaySamples( params ) ;565 def assaySamples = getAssaySamples( params ).unique(); 559 566 def name 560 567 … … 567 574 } 568 575 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 ); 578 583 } 579 584 … … 602 607 tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"] 603 608 } 609 sampleExcelService.sessionToken = session.sessionToken 604 610 sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() ); 605 611 response.outputStream.flush(); -
trunk/grails-app/controllers/nl/tno/massSequencing/AssaySampleController.groovy
r67 r70 2 2 3 3 import java.util.List; 4 import org.codehaus.groovy.grails.commons.ConfigurationHolder 4 5 5 6 class AssaySampleController { 6 7 def fastaService 7 8 def sampleExcelService 9 def fileService 10 def workerService 8 11 9 12 /** … … 99 102 */ 100 103 def exportAsFasta = { 101 def assaySamples = getAssaySamples( params ); 102 def name 104 def assaySamples = getAssaySamples( params )?.unique(); 103 105 104 106 if( assaySamples?.size() == 0 ) { … … 108 110 } 109 111 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 112 125 // 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" 114 127 try { 115 fastaService.export( assaySamples.unique(), response.getOutputStream());128 response.outputStream << fileService.get( session.process[ processId ].filename ).newInputStream(); 116 129 response.outputStream.flush(); 117 130 } catch( Exception e ) { 118 131 log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." ); 119 132 e.printStackTrace(); 133 } finally { 134 // Delete the file since it has to be downloaded only once 135 fileService.delete( session.process[ processId ].filename ); 120 136 } 121 137 } … … 145 161 } 146 162 response.setHeader "Content-disposition", "attachment; filename=${name}.xls" 147 163 164 sampleExcelService.sessionToken = session.sessionToken 165 148 166 if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) { 149 167 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 4 4 import grails.converters.JSON 5 5 import nl.tno.massSequencing.auth.* 6 import nl.tno.massSequencing.classification.*; 6 7 7 8 import org.codehaus.groovy.grails.commons.ConfigurationHolder … … 33 34 "r.name", 34 35 "COUNT( DISTINCT a )", 35 "SUM( a.numSequences )" 36 "SUM( a.numSequences )", 37 "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.run = r)" 36 38 ] 37 39 … … 41 43 // Retrieve data from assaySample table 42 44 def from = "Run r LEFT JOIN r.assaySamples a" 43 45 44 46 // This closure determines what to do with a row that is retrieved from the database. 45 47 def convertClosure = { … … 48 50 def numSamples = it[ 2 ]; 49 51 def numSequences = it[ 3 ]; 52 def numClassified = it[ 4 ]; 50 53 51 54 // Create buttons in the last three columns … … 71 74 numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-", // it.numSequences(), 72 75 numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(), 76 numClassified > 0 ? g.formatNumber( number: numClassified, format: "###,###,##0" ) : "-", // it.percentageClassified 73 77 editButton, 74 78 deleteButton, … … 107 111 108 112 // 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 ] ); 110 114 111 115 // 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 ] 113 117 } 114 118 … … 817 821 name = "runs"; 818 822 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 ); 828 831 } 829 832 … … 858 861 response.setHeader "Content-disposition", "attachment; filename=${name}.xls" 859 862 863 sampleExcelService.sessionToken = session.sessionToken 864 860 865 if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) { 861 866 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 14 14 15 15 /** 16 * Exports data about one or more studies in fasta format17 */18 19 20 16 * Exports data about one or more studies in fasta format 17 */ 18 def exportAsFasta = { 19 def assaySamples = getAssaySamples( params ); 20 def name 21 21 22 23 24 25 26 27 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 } 29 29 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." ); 37 70 e.printStackTrace(); 38 39 71 } 72 } 40 73 41 /**42 * Export metadata of one or more studies in excel format43 */44 def exportMetaData = {45 def assaySamples = getAssaySamples( params );46 def name47 74 48 if( assaySamples == null ) {49 return50 } else if( assaySamples*.sample.unique().size() == 1 ) {51 name = "Sample_" + assaySamples[0].sample.name?.replace( ' ', '_' );52 } else {53 name = "samples";54 }55 75 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 76 77 /** 77 78 * Parse the given parameters and try to extract assaysamples using ids and tokens of samples … … 115 116 } 116 117 117 118 118 119 } -
trunk/grails-app/controllers/nl/tno/massSequencing/StudyController.groovy
r59 r70 44 44 } 45 45 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 ); 55 53 } 56 54 … … 79 77 tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"] 80 78 } 79 80 sampleExcelService.sessionToken = session.sessionToken 81 81 82 sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() ); 82 83 response.outputStream.flush(); -
trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy
r63 r70 12 12 def classificationService 13 13 def sessionFactory 14 def workerService 14 15 15 16 /************************************************************************** … … 24 25 def parseUploadedFiles = { 25 26 def entityType = params.entityType 27 def entityId = params.id 26 28 27 29 // Check whether files are given 28 def names = params.list( 'sequencefiles' )30 def names = [] + params.list( 'sequencefiles' ) 29 31 30 32 if( !names ) { … … 38 40 } 39 41 40 // Create a unique process identifier41 String processId = UUID.randomUUID().toString();42 43 // Save filenames in session44 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 = entityType53 54 42 // Check for total size of the files in order to be able 55 43 // to show a progress bar … … 58 46 filesize += fileService.get( it )?.length() 59 47 } 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 ); 72 51 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 130 66 /* 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 ); 200 155 } 201 156 … … 208 163 def entity 209 164 210 switch( params.entityType ) {165 switch( session.process[ processId ].entityType ) { 211 166 case "run": 212 entity = getRun( params.id )167 entity = getRun( session.process[ processId ].entityId ) 213 168 break; 214 169 case "assay": 215 entity = getAssay( params.id )170 entity = getAssay( session.process[ processId ].entityId ) 216 171 break; 217 172 default: … … 234 189 return 235 190 } 236 237 191 238 192 // Find matching sequenceData objects for taxonomyfiles that have not been matched … … 245 199 } 246 200 247 [ entityType: params.entityType, processId: processId, entity: entity, id: params.id,201 [ entityType: session.process[ processId ].entityType, processId: processId, entity: entity, 248 202 parsedFiles: session.process[ processId ].processedFiles.parsed, 249 203 matchedFiles: session.process[ processId ].processedFiles.matched, … … 257 211 def returnWithoutSaving = { 258 212 def processId = params.processId; 259 213 def entityType = session.process[ processId ].entityType; 214 def entityId = session.process[ processId ].entityId; 215 260 216 // Delete all uploaded files from disk 261 217 session.process[ processId ]?.processedFiles?.parsed?.success?.each { 262 218 fileService.delete( it.filename ); 263 219 } 220 221 // Clear process from session 222 workerService.clearProcess( session, processId ); 264 223 265 224 // Redirect to the correct controller 266 switch( params.entityType ) {225 switch( entityType ) { 267 226 case "run": 268 227 case "assay": 269 redirect( controller: params.entityType, action: "show", id: params.id );228 redirect( controller: entityType, action: "show", id: entityId ); 270 229 return; 271 230 default: … … 282 241 */ 283 242 def saveMatchedFiles = { 284 def entityType = params.entityType285 243 def processId = params.processId 244 245 def entityType = session.process[ processId ].entityType 246 def entityId = session.process[ processId ].entityId 286 247 287 248 session.process[ processId ].matchedFiles = params.file … … 320 281 } 321 282 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 // 350 302 // Check whether files are given 351 def files = session.process[ processId ].matchedFiles 303 def files = session.process[ processId ].matchedFiles 352 304 def remainingClassification = session.process[ processId ].matchedRemainingClassification; 353 305 354 306 if( !files && !remainingClassification ) { 355 307 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) 357 309 return 358 310 } … … 367 319 } 368 320 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 ); 395 353 } 396 354 … … 566 524 } 567 525 } 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 569 531 // Clear session 570 session.process?.remove( processId ); 571 session.progress?.remove( processId ); 532 workerService.clearProcess( session, processId ); 572 533 573 534 // 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 623 538 protected Assay getAssay(def assayId) { 624 539 // load assay with id specified by param.id -
trunk/grails-app/domain/nl/tno/massSequencing/auth/User.groovy
r53 r70 18 18 table 'gscfuser' 19 19 auth cascade: "all-delete-orphan" 20 } 21 22 public String toString() { 23 return ( this.username ?: "" ) 20 24 } 21 25 -
trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy
r67 r70 195 195 leafPath = leaf.givePathNames(); 196 196 197 println "Path for leaf " + leaf.name + " (" + leaf.level + "): " + leafPath198 197 numLeafs = leafPath.size(); 199 198 -
trunk/grails-app/services/nl/tno/massSequencing/DataTablesService.groovy
r69 r70 2 2 3 3 class DataTablesService { 4 4 static transactional = false 5 5 6 /** 6 7 * Retrieves data from the database for showing in a datatables table -
trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy
r68 r70 15 15 def classificationService 16 16 def excelService 17 def workerService 17 18 18 19 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; 19 26 20 27 /** … … 523 530 return lengthList; 524 531 } 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 } 525 606 526 607 /** … … 530 611 * @return 531 612 */ 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 ) { 533 614 if( !assaySamples || assaySamples.size() == 0 ) 534 615 return false; … … 573 654 BufferedWriter zipWriter = new BufferedWriter( new OutputStreamWriter( zipFile ) ); 574 655 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 575 663 // We have to loop twice through the sequenceData, since we can't write part of the sequence 576 664 // file and part of the qual files mixed. We have to write the full sequence file first. … … 578 666 zipFile.putNextEntry( new ZipEntry( name + ".fna" ) ); 579 667 580 assaySamples.each { assaySample -> 668 for( def i = 0; i < assaySamples.size(); i++ ) { 669 assaySample = assaySamples[ i ]; 670 581 671 if( assaySample.numSequences() > 0 ) { 582 672 def currentTag = tags.find { it.assaySampleId == assaySample.id }; 583 673 584 674 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 } 587 688 } 588 689 } … … 594 695 zipFile.putNextEntry( new ZipEntry( name + ".qual" ) ); 595 696 596 assaySamples.each { assaySample -> 697 for( def i = 0; i < assaySamples.size(); i++ ) { 698 assaySample = assaySamples[ i ]; 699 597 700 if( assaySample.numSequences() > 0 ) { 598 701 def currentTag = tags.find { it.assaySampleId == assaySample.id }; 599 702 600 703 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 602 709 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 } 603 717 } 604 718 } … … 607 721 zipWriter.flush(); 608 722 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 } 609 740 } 610 741 611 742 } catch( Exception e ) { 612 743 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() ); 614 747 } finally { 615 748 // Always close zip entry … … 620 753 } 621 754 } 622 755 623 756 // Export a tab delimited file with tags 624 757 zipFile.putNextEntry( new ZipEntry( name + ".tab" ) ); … … 635 768 // Export an excel file with information about the samples 636 769 zipFile.putNextEntry( new ZipEntry( name + ".xls" ) ); 770 sampleExcelService.sessionToken = sessionToken 637 771 sampleExcelService.exportExcelSampleData( assaySamples, tags, zipFile ); 638 772 zipFile.flush(); 639 773 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 ); 640 780 641 781 // Export an excel file with information about the classification samples … … 842 982 } catch( Exception e ) { 843 983 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 ); 846 987 } 847 988 } … … 941 1082 } catch( Exception e ) { 942 1083 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 ); 945 1087 } 946 1088 } -
trunk/grails-app/services/nl/tno/massSequencing/SampleExcelService.groovy
r58 r70 8 8 def gscfService 9 9 10 static transactional = true 10 def sessionToken 11 12 static transactional = false 11 13 12 14 // Fields to be edited using excel file and manually … … 297 299 // Gather data from GSCF. 298 300 def sampleTokens = assaySamples*.sample.unique()*.sampleToken; 299 def sessionToken = RequestContextHolder.currentRequestAttributes().getSession().sessionToken301 300 302 def gscfData 301 303 try { -
trunk/grails-app/services/nl/tno/massSequencing/imports/FuzzySearchService.groovy
r67 r70 3 3 class FuzzySearchService { 4 4 5 static transactional = true5 static transactional = false 6 6 7 7 /** -
trunk/grails-app/services/nl/tno/massSequencing/integration/GscfService.groovy
r63 r70 14 14 class GscfService { 15 15 def config = ConfigurationHolder.config 16 17 static transactional = true16 17 static transactional = false 18 18 19 19 /** -
trunk/grails-app/views/assay/index.gsp
r60 r70 22 22 <th># samples</th> 23 23 <th>avg sequences / sample</th> 24 <th># classified</th> 24 25 <th class="nonsortable"></th> 25 26 <th class="nonsortable"></th> -
trunk/grails-app/views/assay/show.gsp
r62 r70 47 47 </h1> 48 48 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> 55 58 <!-- Samples --> 56 59 <h2>Samples</h2> -
trunk/grails-app/views/run/index.gsp
r60 r70 30 30 <tr> 31 31 <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> 35 36 <th width="5" class="nonsortable"></th> 36 37 <th width="5" class="nonsortable"></th> -
trunk/grails-app/views/run/show.gsp
r62 r70 88 88 <br /> 89 89 <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 /> 91 91 </div> 92 92 <p class="options"> -
trunk/grails-app/views/worker/process.gsp
r69 r70 2 2 <head> 3 3 <meta name="layout" content="main" /> 4 <title>Processing files| Mass Sequencing | dbNP</title>4 <title>Processing | Mass Sequencing | dbNP</title> 5 5 6 6 <script type="text/javascript"> … … 11 11 height: 200, 12 12 width: 400, 13 title: 'Please wait while processing files',13 title: 'Please wait while processing', 14 14 modal: true, 15 15 autoOpen: true, … … 26 26 27 27 progressInterval = setTimeout(updateProgress, 250); 28 29 // Call processing URL30 $.ajax({31 type: 'POST',32 url: "${processUrl.encodeAsJavaScript()}",33 data: "${processParameters*.toString().join("&")}",34 success: function(data) {35 // Stop update progress bar36 clearTimeout( progressInterval );37 28 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 });50 29 }); 51 30 … … 65 44 $("#progressbar").progressbar("value", progress.stepProgress / progress.stepTotal * 100 ); 66 45 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." ); 69 79 } 70 80 }); 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(); 71 91 } 72 92 </script> … … 80 100 margin-top: 10px; 81 101 } 102 103 #return { display: none; margin-top: 30px;} 104 #error { display: none; color: red; margin-top: 15px; } 82 105 </style> 83 106 </head> … … 85 108 <div id="wait_dialog"> 86 109 <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> 88 111 <p id="step" style="display: none;"> 89 112 Step <span id="stepNum"></span> / <span id="numSteps"></span>: <span id="stepDescription"></span> 90 113 </p> 91 92 114 <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> 94 124 </div> 95 125 </body> -
trunk/web-app/css/datatables/demo_table_jui.css
r59 r70 136 136 .dataTables_processing { 137 137 position: absolute; 138 top: 0px;138 top: 50%; 139 139 left: 50%; 140 140 width: 250px; 141 141 margin-left: -125px; 142 margin-top: -30px; /* Half the padding + line-height */ 143 142 144 border: 1px solid #ddd; 143 145 background-color: white; … … 145 147 color: #999; 146 148 font-size: 11px; 147 padding: 2px 0; 149 150 padding: 20px 0; 151 line-height: 20px; 152 153 z-index: 100; 148 154 } 149 155
Note: See TracChangeset
for help on using the changeset viewer.