Changeset 58
- Timestamp:
- May 17, 2011, 1:44:07 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 18 added
- 13 edited
- 1 copied
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/application.properties
r52 r58 1 1 #Grails Metadata file 2 # Fri Apr 15 14:24:03CEST 20112 #Wed May 11 12:10:59 CEST 2011 3 3 app.build.display.info=0 4 app.grails.version=1.3.7 5 app.name=massSequencing 6 app.servlet.version=2.4 4 7 app.version=0.1 8 plugins.famfamfam=1.0.1 9 plugins.hibernate=1.3.7 10 plugins.jquery=1.4.4.1 11 plugins.jquery-ui=1.8.7 5 12 plugins.tomcat=1.3.7 6 plugins.jquery=1.4.4.17 plugins.famfamfam=1.0.18 plugins.jquery-ui=1.8.79 app.servlet.version=2.410 plugins.hibernate=1.3.711 app.name=massSequencing12 app.grails.version=1.3.7 -
trunk/grails-app/conf/DataSource.groovy
r52 r58 7 7 } 8 8 hibernate { 9 jdbc.batch_size = 50 9 10 cache.use_second_level_cache = true 10 11 cache.use_query_cache = true -
trunk/grails-app/controllers/nl/tno/massSequencing/FastaController.groovy
r49 r58 9 9 def fastaService 10 10 def sessionFactory 11 12 /**************************************************************************13 *14 * Methods for handling uploaded sequence and quality files15 *16 *************************************************************************/17 18 /**19 * Shows a screen that processing is done20 */21 def showProcessScreen = {22 def entityType = params.entityType23 24 // Check whether files are given25 def names = params.sequencefiles26 27 if( !names ) {28 flash.message = "No files uploaded for processing"29 if( params.entityType && params.id)30 redirect( controller: params.entityType, action: 'show', 'id': params.id)31 else32 redirect( url: "" )33 34 return35 }36 37 // If only 1 file is uploaded, it is given as String38 ArrayList filenames = []39 if( names instanceof String )40 filenames << names41 else42 names.each { filenames << it }43 44 // Save filenames in session45 session.processFilenames = names;46 47 // Check for total size of the files in order to be able48 // to show a progress bar49 long filesize = 0;50 names.each {51 filesize += fileService.get( it )?.length()52 }53 session.processProgress = [54 numFiles: names.size(),55 numBytes: filesize,56 filesProcessed: 0,57 bytesProcessed: 058 ]59 60 [entityId: params.id, entityType: params.entityType, filenames: names, url: createLink( action: 'showProcessResult', id: params.id, params: [entityType: entityType] ) ]61 }62 63 /**64 * Processes uploaded files and tries to combine them with samples65 */66 def process = {67 def entity68 def assaySamples69 70 switch( params.entityType ) {71 case "run":72 entity = getRun( params.id );73 assaySamples = entity.assaySamples.findAll { it.assay.study.canRead( session.user ) };74 break;75 case "assay":76 entity = getAssay( params.id );77 assaySamples = entity.assaySamples;78 break;79 default:80 response.setStatus( 404, "No controller found" );81 render "";82 return;83 }84 85 if (!entity) {86 response.setStatus( 404, flash.error )87 render "";88 return89 }90 91 // Check whether files are given92 def names = session.processFilenames93 94 if( !names ) {95 response.setStatus( 500, "No files uploaded for processing" )96 render "";97 return98 }99 100 // If only 1 file is uploaded, it is given as String101 ArrayList filenames = []102 if( names instanceof String )103 filenames << names104 else105 names.each { filenames << it }106 107 /* Parses uploaded files, discards files we can not handle108 *109 * [110 * success: [111 * [filename: 'abc.fasta', type: FASTA, numSequences: 190]112 * [filename: 'cde.fasta', type: FASTA, numSequences: 140]113 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]114 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]115 * ],116 * failure: [117 * [filename: 'testing.xls', message: 'Type not recognized']118 * ]119 * ]120 *121 * The second parameter is a callback function to update progress indicators122 */123 def httpSession = session;124 def parsedFiles = fastaService.parseFiles( filenames, { files, bytes, totalFiles, totalBytes ->125 httpSession.processProgress.numFiles += totalFiles;126 httpSession.processProgress.numBytes += totalBytes;127 httpSession.processProgress.filesProcessed = files;128 httpSession.processProgress.bytesProcessed = bytes;129 } );130 131 // Check which assaySamples to use (only the ones visible to the user)132 assaySamples = assaySamples.findAll { it.assay.study.canWrite( session.user ) }133 134 // Match files with samples in the database135 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );136 137 // Sort files on filename138 matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }139 140 // Saved file matches in session to use them later on141 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ];142 143 render ""144 }145 146 def getProgress = {147 if( !session.processProgress ) {148 response.setStatus( 500, "No progress information found" );149 render ""150 return151 }152 153 render session.processProgress as JSON154 }155 156 /**157 * Show result of processing158 */159 def showProcessResult = {160 // load study with id specified by param.id161 def entity162 163 switch( params.entityType ) {164 case "run":165 entity = getRun( params.id )166 break;167 case "assay":168 entity = getAssay( params.id )169 break;170 default:171 response.setStatus( 404, "No entity found" );172 render "";173 return;174 }175 176 if (!entity) {177 response.setStatus( 404, flash.error )178 render "";179 return180 }181 182 if( !session.processedFiles ) {183 flash.error = "Processing of files failed. Maybe the session timed out."184 redirect( controller: 'assay', action: 'show', 'id': params.id)185 return186 }187 188 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]189 }190 191 /**192 * Returns from the upload wizard without saving the data. The uploaded files are removed193 */194 def returnWithoutSaving = {195 // Delete all uploaded files from disk196 session.processedFiles?.parsed?.success?.each {197 fileService.delete( it.filename );198 }199 200 // Redirect to the correct controller201 switch( params.entityType ) {202 case "run":203 case "assay":204 redirect( controller: params.entityType, action: "show", id: params.id );205 return;206 default:207 response.setStatus( 404, "No entity found" );208 render "";209 return;210 }211 212 213 }214 215 /**216 * Saves processed files to the database, based on the selections made by the user217 */218 def saveProcessedFiles = {219 // load entity with id specified by param.id220 def entity221 222 switch( params.entityType ) {223 case "run":224 entity = getRun( params.id );225 break;226 case "assay":227 entity = getAssay( params.id );228 break;229 default:230 response.setStatus( 404, "No entity found" );231 render "";232 return;233 }234 235 if (!entity) {236 response.setStatus( 404, flash.error )237 render "";238 return239 }240 241 // Check whether files are given242 def files = params.file243 244 if( !files ) {245 flash.message = "No files were selected."246 redirect( controller: params.entityType, action: 'show', 'id': params.id)247 return248 }249 250 File permanentDir = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir )251 int numSuccesful = 0;252 def errors = [];253 254 // Loop through all files Those are the numeric elements in the 'files' array255 def digitRE = ~/^\d+$/;256 files.findAll { it.key.matches( digitRE ) }.each { file ->257 def filevalue = file.value;258 259 // Check if the file is selected260 if( filevalue.include == "on" ) {261 if( fileService.fileExists( filevalue.fasta ) ) {262 try {263 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, session.processedFiles );264 265 // Save the data into the database266 SequenceData sd = new SequenceData();267 268 sd.sequenceFile = permanent.fasta269 sd.qualityFile = permanent.qual270 sd.numSequences = permanent.numSequences271 sd.averageQuality = permanent.avgQuality272 273 def sample = AssaySample.get( filevalue.assaySample );274 if( sample )275 sample.addToSequenceData( sd );276 277 if( !sd.validate() ) {278 errors << "an error occurred while saving " + filevalue.fasta + ": validation of SequenceData failed.";279 } else {280 sd.save(flush:true);281 }282 283 numSuccesful++;284 } catch( Exception e ) {285 errors << "an error occurred while saving " + filevalue.fasta + ": " + e.getMessage()286 }287 }288 } else {289 // File doesn't need to be included in the system. Delete it also from disk290 fileService.delete( filevalue.fasta );291 }292 }293 294 // Return all files that have not been moved295 session.processedFiles?.parsed?.success?.each {296 fileService.delete( it.filename );297 }298 299 // Return a message to the user300 if( numSuccesful == 0 ) {301 flash.error = "None of the files were imported, because "302 303 if( errors.size() > 0 ) {304 errors.each {305 flash.error += "<br />- " + it306 }307 } else {308 flash.error = "none of the files were selected for import."309 }310 } else {311 flash.message = numSuccesful + " files have been added to the system. "312 313 if( errors.size() > 0 ) {314 flash.error += errors.size() + " errors occurred during import: "315 errors.each {316 flash.error += "<br />- " + it317 }318 }319 }320 321 redirect( controller: params.entityType, action: "show", id: params.id )322 }323 11 324 12 def deleteData = { -
trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy
r57 r58 1 package nl.tno.massSequencing 1 package nl.tno.massSequencing.files 2 2 3 3 import org.codehaus.groovy.grails.commons.ConfigurationHolder 4 4 import org.hibernate.SessionFactory 5 5 import grails.converters.*; 6 7 class FastaController { 6 import nl.tno.massSequencing.* 7 8 class ImportController { 8 9 def fileService 9 10 def fastaService 11 def importService 12 def classificationService 10 13 def sessionFactory 11 14 12 15 /************************************************************************** 13 16 * 14 * Methods for handling uploaded sequence and qualityfiles17 * Methods for handling uploaded sequence, quality and classification files 15 18 * 16 19 *************************************************************************/ … … 23 26 24 27 // Check whether files are given 25 def names = params. sequencefiles28 def names = params.list( 'sequencefiles' ) 26 29 27 30 if( !names ) { … … 34 37 return 35 38 } 36 37 // If only 1 file is uploaded, it is given as String38 ArrayList filenames = []39 if( names instanceof String )40 filenames << names41 else42 names.each { filenames << it }43 39 44 40 // Save filenames in session … … 51 47 filesize += fileService.get( it )?.length() 52 48 } 49 53 50 session.processProgress = [ 54 numFiles: names.size(), 55 numBytes: filesize, 56 filesProcessed: 0, 57 bytesProcessed: 0 51 stepNum: 1, 52 numSteps: 2, 53 stepDescription: 'Parsing files', // Second step is Store classification 54 55 stepProgress: 0, 56 stepTotal: filesize 58 57 ] 59 58 … … 71 70 case "run": 72 71 entity = getRun( params.id ); 73 assaySamples = entity.assaySamples.findAll { it.assay.study.can Read( session.user ) };72 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 74 73 break; 75 74 case "assay": 76 75 entity = getAssay( params.id ); 77 assaySamples = entity.assaySamples ;76 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 78 77 break; 79 78 default: … … 115 114 * ], 116 115 * failure: [ 117 * [filename: 'testing. xls', message: 'Type not recognized']116 * [filename: 'testing.doc', message: 'Type not recognized'] 118 117 * ] 119 118 * ] … … 122 121 */ 123 122 def httpSession = session; 124 def parsedFiles = fastaService.parseFiles( filenames, { files, bytes, totalFiles, totalBytes -> 125 httpSession.processProgress.numFiles += totalFiles; 126 httpSession.processProgress.numBytes += totalBytes; 127 httpSession.processProgress.filesProcessed = files; 128 httpSession.processProgress.bytesProcessed = bytes; 129 } ); 130 131 // Check which assaySamples to use (only the ones visible to the user) 132 assaySamples = assaySamples.findAll { it.assay.study.canWrite( session.user ) } 133 123 def onProgress = { progress, total -> 124 // Update progress 125 httpSession.processProgress.stepTotal = total; 126 httpSession.processProgress.stepProgress = progress; 127 } 128 def newStep = { total, description -> 129 // Start a new step 130 httpSession.processProgress.stepTotal = total; 131 httpSession.processProgress.stepProgress = 0; 132 133 httpSession.processProgress.stepDescription = description; 134 httpSession.processProgress.stepNum++; 135 } 136 137 def parsedFiles = importService.parseFiles( filenames, onProgress, [progress: 0, total: httpSession.processProgress.stepTotal ], newStep ); 138 139 println "Parsed files success: " + parsedFiles.success 140 141 // Determine excel matches from the uploaded files 142 parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success ); 143 144 // Now check whether a taxonomy and groups file are uploaded. If so send them to the classificationService for 145 // parsing 146 def classificationFiles = parsedFiles.success.findAll { it.type == 'groups' || it.type == 'taxonomy' }; 147 def excelFiles = parsedFiles.success.findAll { it.type == 'excel' } 148 def excelMatches = [:] 149 150 // Find a match between input (mothur) samples and the samples in the system 151 excelFiles.each { excelFile -> 152 if( excelFile.matches && excelFile.matches[ 'mothursamples' ] ) { 153 excelFile.matches[ 'mothursamples' ].each { excelMatch -> 154 def foundSample = assaySamples.find { it.sample.name == excelMatch.samplename }; 155 if( foundSample ) 156 excelMatches[ excelMatch.mothurname ] = foundSample 157 } 158 } 159 } 160 161 if( classificationFiles ) { 162 parsedFiles.success -= classificationFiles; 163 164 // If no excel matches are found, no classifications can be stored 165 if( !excelMatches ) { 166 classificationFiles.each { 167 it.success = false 168 it.message = "An excel file which maps the input samples with samples in the system must be provided." 169 170 parsedFiles.failure << it; 171 } 172 } else { 173 // Set progress to a new step, so the user knows that it will take a while 174 long totalSize = classificationFiles.collect { it.filesize }.sum(); 175 newStep( totalSize, "Storing classification" ) 176 println "Total size: " + totalSize; 177 178 classificationFiles = classificationService.storeClassification( classificationFiles, excelMatches, { bytesProcessed -> httpSession.processProgress.stepProgress += bytesProcessed } ); 179 180 classificationFiles.each { 181 if( it.success ) { 182 parsedFiles.success << it; 183 } else { 184 parsedFiles.failure << it; 185 } 186 } 187 } 188 } 189 134 190 // Match files with samples in the database 135 191 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples ); … … 139 195 140 196 // Saved file matches in session to use them later on 141 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ];197 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ]; 142 198 143 199 render "" … … 186 242 } 187 243 188 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]244 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, classificationFiles: session.processedFiles.parsed.success.findAll { it.type == 'taxonomy' }, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ] 189 245 } 190 246 … … 252 308 def errors = []; 253 309 254 // Loop through all files Those are the numeric elements in the 'files' array310 // Loop through all files. Those are the numeric elements in the 'files' array 255 311 def digitRE = ~/^\d+$/; 256 312 files.findAll { it.key.matches( digitRE ) }.each { file -> -
trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy
r52 r58 70 70 } 71 71 72 /** 73 * Returns the number of sequences that have been classified for this sample 74 * @return 75 */ 76 public int numClassifiedSequences() { 77 return Sequence.countByAssaySample( this ); 78 } 79 72 80 /** 73 81 * Returns the number of sequence files in the system, belonging to this … … 260 268 return fwOligo || fwMidName || fwTotalSeq || fwMidSeq || fwPrimerSeq || 261 269 revOligo || revMidName || revTotalSeq || revMidSeq || revPrimerSeq || 262 numFiles() > 0 ;270 numFiles() > 0 || numClassifiedSequences() > 0; 263 271 } 264 272 … … 306 314 } 307 315 } 308 316 317 // Copy all sequence classifications to the new assaysample 318 Sequence.executeUpdate( "UPDATE Sequence s SET s.assaySample = :new WHERE s.assaySample = :old ", [ 'old': this, 'new': otherAssaySample ] ) 319 309 320 // Copy run properties 310 321 if( otherAssaySample.run ) { … … 340 351 } 341 352 353 // Remove all sequence objects referencing this sequenceData object 354 Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.assaySample = ?", [this]) 355 342 356 resetStats(); 343 357 save(); … … 346 360 } 347 361 362 363 /** 364 * Remove data from associations before delete the assaySample itself 365 */ 366 def beforeDelete = { 367 deleteSequenceData(); 368 369 Classification.executeUpdate( "DELETE FROM Classification c WHERE c.assaySample = ?", [this]) 370 } 348 371 } -
trunk/grails-app/domain/nl/tno/massSequencing/Study.groovy
r53 r58 1 import java.io.StringWriter;2 3 1 package nl.tno.massSequencing 4 2 -
trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy
r55 r58 9 9 import java.util.zip.* 10 10 11 class FastaService {11 class FastaService implements nl.tno.massSequencing.imports.Importer { 12 12 def fileService 13 13 def fuzzySearchService … … 15 15 def excelService 16 16 17 static transactional = true 18 19 /** 20 * Parses uploaded files and checks them for FASTA and QUAL files 21 * @param filenames List of filenames currently existing in the upload directory 22 * @param onProgress Closure to execute when progress indicators should be updated. 23 * Has 4 parameters: numFilesProcessed, numBytesProcessed that indicate the number 24 * of files and bytes that have been processed in total. totalFiles, totalBytes indicate the change 25 * in total number of files and bytes (e.g. should take 1 if a new file is added to the list) 26 * @param directory Directory to move the files to 27 * @return Structure with information about the parsed files. The 'success' files are 28 * moved to the given directory 29 * 30 * [ 31 * success: [ 32 * [filename: 'abc.fasta', type: FASTA, numSequences: 190] 33 * [filename: 'cde.fasta', type: FASTA, numSequences: 140] 34 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38] 35 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29] 36 * ], 37 * failure: [ 38 * [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized'] 39 * ] 40 * ] 41 * 42 */ 43 def parseFiles( ArrayList filenames, Closure onProgress, File directory = null ) { 44 if( filenames.size() == 0 ) { 45 return [ success: [], failure: [] ]; 46 } 47 48 if( !directory ) { 49 directory = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir ) 50 } 51 52 def success = []; 53 def failure = []; 54 55 long filesProcessed = 0; 56 long bytesProcessed = 0; 57 58 // Loop through all filenames 59 for( int i = 0; i < filenames.size(); i++ ) { 60 def filename = filenames[ i ]; 61 62 if( fileService.isZipFile( filename ) ) { 63 // ZIP files are extracted and appended to the filenames list. 64 def newfiles = fileService.extractZipFile( filename, { files, bytes, totalFiles, totalBytes -> 65 filesProcessed += files; 66 bytesProcessed += bytes; 67 68 onProgress( filesProcessed, bytesProcessed, totalFiles, totalBytes ); 69 } ); 70 if( newfiles ) { 71 newfiles.each { 72 filenames.add( it ); 73 } 74 75 // Delete the zip file itself 76 fileService.delete( filename ); 77 } else { 78 failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: "zip", message: 'Zip file could not be extracted' ]; 79 } 80 } else { 81 def file = fileService.get( filename ); 82 String filetype = fileService.determineFileType( file ); 83 84 if( !fileTypeValid( filetype ) ) { 85 // If files are not valid for parsing, delete them and return a message to the user 86 fileService.delete(filename); 87 failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: 'File type not accepted' ]; 88 } else { 89 try { 90 def contents = parseFile( file, filetype, { files, bytes -> 91 filesProcessed += files; 92 bytesProcessed += bytes; 93 94 onProgress( filesProcessed, bytesProcessed, 0, 0 ); 95 } ); 96 97 contents.filename = file.getName(); 98 contents.originalfilename = fileService.originalFilename( contents.filename ) 99 100 if( contents.success ) { 101 success << contents; 102 } else { 103 fileService.delete(filename); 104 failure << contents; 105 } 106 } catch( Exception e ) { 107 // If anything fails during parsing, return an error message 108 fileService.delete(filename); 109 failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: e.getMessage() ]; 110 } 111 } 112 } 113 } 114 115 return [ 'success': success, 'failure': failure ]; 116 } 117 118 /** 119 * Matches uploaded fasta and qual files and combines them with the samples they probably belong to 120 * @param parsedFiles Parsed files 121 * [ 122 * [filename: 'abc.fasta', type: FASTA, numSequences: 190] 123 * [filename: 'cde.fasta', type: FASTA, numSequences: 140] 124 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38] 125 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29] 126 * [filename: 'match.xls', type: EXCEL, matches: [ [ filename: 'abc.fasta', basename: 'abc', sample: 's1' ] ] 127 * ] 128 * @param samples AssaySample objects to which the files should be matched. 129 * @return Structure with information about the matching. 130 * [ 131 * [ 132 * fasta: [filename: 'abc.fasta', type: FASTA, numSequences: 190], 133 * qual: [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38], 134 * feasibleQuals: [ 135 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38], 136 * [filename: 'def.qual', type: QUAL, numSequences: 190, avgQuality: 21] 137 * ] 138 * sample: AssaySample object 139 */ 140 def matchFiles( def parsedFiles, def samples ) { 141 def fastas = parsedFiles.findAll { it.type == "fasta" } 142 def quals = parsedFiles.findAll { it.type == "qual" } 143 def excels = parsedFiles.findAll { it.type == "excel" } 144 145 samples = samples.toList() 146 147 // Collect matches from all files 148 def matches = []; 149 excels.each { m -> 150 if( m.matches ) { 151 m.matches.each { matches << it } 152 } 153 } 154 155 def files = []; 156 157 fastas.each { fastaFile -> 158 // Remove extension 159 def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) ) 160 161 // Determine feasible quals (based on number of sequences ) 162 def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences } 163 164 // Best matching qual file 165 def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename ); 166 167 def qual = null 168 if( qualIdx != null ) 169 qual = feasibleQuals[ qualIdx ]; 170 171 // Best matching sample 172 def assaySample = null 173 def checked = true; 174 if( matches ) { 175 // Match with files from excelsheet 176 177 // First find the best matching filename in the list of matches. 178 def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename, 0.8 ); 179 180 // If one is found, use the sample name associated with it to do the matching with samples 181 if( sampleNameIdx != null ) { 182 matchWith = matches[ sampleNameIdx ].sample; 183 } else { 184 // If no match is found in the excel sheet, this sample is unchecked by default 185 checked = false; 186 } 187 } 188 189 // Match on filenames 190 def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name ); 191 if( sampleIdx != null ) { 192 assaySample = samples[ sampleIdx ]; 193 } 194 195 files << [ 196 fasta: fastaFile, 197 feasibleQuals: feasibleQuals, 198 qual: qual, 199 assaySample: assaySample, 200 checked: checked 201 ] 202 } 203 204 return files; 205 206 207 } 17 static transactional = false 208 18 209 19 /** … … 213 23 * @return 214 24 */ 215 p rotected boolean fileTypeValid( String filetype ) {25 public boolean canParseFileType( String filetype ) { 216 26 switch( filetype ) { 217 27 case "fasta": 218 28 case "qual": 219 case "excel":220 29 return true; 221 30 default: … … 225 34 226 35 /** 227 * Parses the given file 36 * Parses the given FASTA or QUAL file 37 * 228 38 * @param file File to parse 229 39 * @param filetype Type of the given file 230 40 * @param onProgress Closure to execute when progress indicators should be updated. 231 * Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number 232 * of files and bytes that have been processed in this file (so the first parameter should 233 * only be 1 when the file is finished) 41 * Has 2 parameters: progress that indicates the number of bytes that have been processed. The second parameter determines 42 * the number of bytes that has to be processed extra after this action (e.g. when a zip file is extracted, the extracted 43 * files should be added to the total number of bytes to be processed) 44 * @return Structure with information about the parsed files. The 'success' files are 45 * moved to the given directory 234 46 * 235 * @return List structure. Examples: 47 * Examples: 48 * [filename: 'abc.fasta', type: FASTA, numSequences: 190] 49 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29] 236 50 * 237 * [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ] 238 * [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 36 ] 239 * [ success: false, filename: 'abc.txt', type: 'txt', message: 'Filetype could not be parsed.' ] 240 */ 241 protected def parseFile( File file, String filetype, Closure onProgress ) { 51 * [filename: 'test.zip', type: ZIP, extraFiles: [newfile1.xls, newfile2.xls, newfile3.xls]] 52 * 53 * [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized'] 54 * 55 */ 56 57 public Map parseFile( File file, String filetype, Closure onProgress ) { 242 58 switch( filetype ) { 243 59 case "fasta": … … 245 61 case "qual": 246 62 return parseQual( file, onProgress ); 247 case "excel":248 return parseExcelMatch( file, onProgress );249 63 default: 250 onProgress( 1, file.length() );251 64 return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ] 252 65 } … … 290 103 bytesProcessed += line.size(); 291 104 if( bytesProcessed > 1000000 ) { 292 onProgress( 0, bytesProcessed);105 onProgress( bytesProcessed, 0 ); 293 106 bytesProcessed = 0; 294 107 } … … 298 111 299 112 // Update progress and say we're finished 300 onProgress( 1, bytesProcessed);113 onProgress( bytesProcessed, 0 ); 301 114 302 115 log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L … … 388 201 bytesProcessed += line.size(); 389 202 if( bytesProcessed > 1000000 ) { 390 onProgress( 0, bytesProcessed);203 onProgress( bytesProcessed, 0 ); 391 204 bytesProcessed = 0; 392 205 } … … 396 209 397 210 // Update progress and say we're finished 398 onProgress( 1, bytesProcessed);211 onProgress( bytesProcessed, 0 ); 399 212 400 213 log.trace "Finished parsing QUAL " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L … … 403 216 } 404 217 218 219 /** 220 * Matches uploaded fasta and qual files and combines them with the samples they probably belong to 221 * @param parsedFiles Parsed files 222 * [ 223 * [filename: 'abc.fasta', type: FASTA, numSequences: 190] 224 * [filename: 'cde.fasta', type: FASTA, numSequences: 140] 225 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38] 226 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29] 227 * [filename: 'match.xls', type: EXCEL, matches: [ [ filename: 'abc.fasta', basename: 'abc', sample: 's1' ] ] 228 * ] 229 * @param samples AssaySample objects to which the files should be matched. 230 * @return Structure with information about the matching. 231 * [ 232 * [ 233 * fasta: [filename: 'abc.fasta', type: FASTA, numSequences: 190], 234 * qual: [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38], 235 * feasibleQuals: [ 236 * [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38], 237 * [filename: 'def.qual', type: QUAL, numSequences: 190, avgQuality: 21] 238 * ] 239 * sample: AssaySample object 240 */ 241 def matchFiles( def parsedFiles, def samples ) { 242 def fastas = parsedFiles.findAll { it.type == "fasta" } 243 def quals = parsedFiles.findAll { it.type == "qual" } 244 def excels = parsedFiles.findAll { it.type == "excel" } 245 246 samples = samples.toList() 247 248 // Collect (filename) matches from all excel files 249 def matches = []; 250 excels.each { m -> 251 if( m.matches && m.matches.filenames ) { 252 // [ filename: filename, samplename: GSCFSample, basename: basename ] 253 m.matches.filenames.each { matches << it } 254 } 255 } 256 257 def files = []; 258 259 fastas.each { fastaFile -> 260 // Remove extension 261 def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) ) 262 263 // Determine feasible quals (based on number of sequences ) 264 def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences } 265 266 // Best matching qual file 267 def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename ); 268 269 def qual = null 270 if( qualIdx != null ) 271 qual = feasibleQuals[ qualIdx ]; 272 273 // Best matching sample 274 def assaySample = null 275 def checked = true; 276 if( matches ) { 277 // Match with files from excelsheet 278 279 // First find the best matching filename in the list of matches. 280 def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename, 0.8 ); 281 282 // If one is found, use the sample name associated with it to do the matching with samples 283 if( sampleNameIdx != null ) { 284 matchWith = matches[ sampleNameIdx ].samplename; 285 } else { 286 // If no match is found in the excel sheet, this sample is unchecked by default 287 checked = false; 288 } 289 } 290 291 // Match on filenames 292 def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name ); 293 if( sampleIdx != null ) { 294 assaySample = samples[ sampleIdx ]; 295 } 296 297 files << [ 298 fasta: fastaFile, 299 feasibleQuals: feasibleQuals, 300 qual: qual, 301 assaySample: assaySample, 302 checked: checked 303 ] 304 } 305 306 return files; 307 308 309 } 310 311 405 312 /** 406 313 * Parses a given excel file with a match between filenames and samples … … 417 324 * [ success: false, filename: 'def.xls', type: 'excel', message: 'File is not a valid XLS file' ] 418 325 */ 419 protected def parseExcelMatch( File file, Closure onProgress ) { 420 long startTime = System.nanoTime(); 421 log.trace "Start parsing XLS " + file.getName() 422 423 def matches = [] 424 425 // Read excel file 426 def wb; 427 try { 428 wb = excelService.open( file ); 429 } catch( Exception e ) { 430 // If an exception occurs, the file can't be opened. Return the error message 431 return [ success: false, type: "excel", filename: file.getName(), message: "Excel file could not be opened or parsed."] 432 } 433 434 // Read all data into an array, and the header in a separate array 435 def header = excelService.readRow( wb, 0, 0 ); 436 def data = excelService.readData( wb, 0, 1 ); 437 438 // Check whether (at least) 2 columns are present 439 if( header.size() < 2 ) { 440 return [ success: false, type: "excel", filename: file.getName(), message: "Excel file must contain at least 2 columns, one with filenames and one with sample names"] 326 public def inferExcelMatches( parsedFiles ) { 327 // Loop through all excel files, and determine which (usable) columns are in the file 328 for( parsedFile in parsedFiles ) { 329 if( parsedFile.type == "excel" ) { 330 331 def header = parsedFile.header; 332 def data = parsedFile.data 333 334 // Check whether (at least) 2 columns are present 335 if( header.size() < 2 ) { 336 parsedFile.success = false 337 parsedFile.exceldata = [] 338 parsedFile.message = "Excel file must contain at least 2 columns, one with filenames and one with sample names" 339 continue; 340 } 341 342 // Check the headers to see whether any columns are found 343 def columns = [:] 344 def matches = [ "filenames": [], "mothursamples": [] ] 345 346 header.eachWithIndex { cell, i -> 347 switch( cell?.toLowerCase() ) { 348 case "gscfsample": 349 case "sample": 350 columns[ "GSCFSample" ] = i; break; 351 case "file": 352 case "filename": 353 columns[ "filename" ] = i; break; 354 case "mothursample": 355 columns[ "MothurSample" ] = i; break; 356 } 357 } 358 359 // Loop through the data and find matches 360 data.each { row -> 361 def filename = columns[ "filename" ] != null ? row[ columns[ "filename" ] ] : null; 362 def GSCFSample = columns[ "GSCFSample" ] != null ? row[ columns[ "GSCFSample" ] ] : null; 363 def mothurSample = columns[ "MothurSample" ] != null ? row[ columns[ "MothurSample" ] ] : null; 364 365 // Matches only exist if GSCF sample is entered in this row 366 if( GSCFSample && GSCFSample != "null" ) { 367 if( filename && filename != "null" ) { 368 // Remove extension from the filename, but only if it is 369 // .fna, .fasta, .qual, .fqa, .xls or .xlsx. Otherwise useful parts of the filename would be removed. 370 def basename = ( filename =~ /\.(fna|fasta|qual|fqa|xls|xlsx)$/ ).replaceAll( "" ); 371 372 matches[ "filenames" ] << [ filename: filename, samplename: GSCFSample, basename: basename ]; 373 } 374 375 if( mothurSample && mothurSample != "null" ) { 376 matches[ "mothursamples" ] << [ samplename: GSCFSample, mothurname: mothurSample ]; 377 } 378 } 379 } 380 381 parsedFile[ 'columns' ] = columns; 382 parsedFile[ 'matches' ] = matches; 383 } 441 384 } 442 385 443 // Check the headers to see whether the default columns are switched 444 def filenameColumn = 0; 445 def sampleColumn = 1; 446 447 header.eachWithIndex { cell, i -> 448 switch( cell?.toLowerCase() ) { 449 case "sample": 450 sampleColumn = i; break; 451 case "file": 452 case "filename": 453 filenameColumn = i; break; 454 } 455 } 456 457 // If both filenames and samples are found in the same column (happens if only one of the headers is given) 458 // an error is given 459 if( filenameColumn == sampleColumn ) 460 return [ success: false, type: "excel", filename: file.getName(), message: "Excel file must contain 'Sample' and 'Filename' headers."] 461 462 // Loop through the data and create a match 463 def maxColumn = Math.max( filenameColumn, sampleColumn ); 464 data.each { row -> 465 if( row.size() >= maxColumn ) { 466 def filename = row[ filenameColumn ]; 467 def sample = row[ sampleColumn ]; 468 469 if( sample && sample != "null" && filename && filename != "null" ) { 470 // Remove extension from the filename, but only if it is 471 // .fna, .fasta, .qual, .fqa, .xls or .xlsx. Otherwise useful parts of the filename would be removed. 472 def basename = ( filename =~ /\.(fna|fasta|qual|fqa|xls|xlsx)$/ ).replaceAll( "" ); 473 474 matches << [ filename: filename, sample: sample, basename: basename ]; 475 } 476 } 477 } 478 479 // Update progress and say we're finished 480 onProgress( 1, file.size() ); 481 482 log.trace "Finished parsing XLS " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L 483 484 return [ success: true, type: "excel", filename: file.getName(), matches: matches ]; 386 return parsedFiles 485 387 } 486 388 -
trunk/grails-app/services/nl/tno/massSequencing/SampleExcelService.groovy
r53 r58 93 93 94 94 // Put the headers on the first row 95 excelService.writeHeader( wb, [ "Filename", " Sample" ], sheetIndex );95 excelService.writeHeader( wb, [ "Filename", "MothurSample", "GSCFSample" ], sheetIndex ); 96 96 97 97 // Adding the next lines 98 98 ArrayList data = []; 99 99 sortedSamples.each { assaySample -> 100 def rowData = [ "", assaySample.sample.name ];100 def rowData = [ "", "", assaySample.sample.name ]; 101 101 102 102 data << rowData; -
trunk/grails-app/services/nl/tno/massSequencing/files/ExcelService.groovy
r52 r58 1 1 package nl.tno.massSequencing.files 2 2 3 import groovy.lang.Closure; 4 5 import java.io.File; 3 6 import java.text.DecimalFormat 4 7 import java.text.Format 5 8 import java.text.NumberFormat 9 import java.util.Map; 10 6 11 import org.apache.poi.xssf.usermodel.XSSFWorkbook; 7 12 import org.apache.poi.hssf.usermodel.HSSFWorkbook; … … 14 19 * 15 20 */ 16 class ExcelService {17 static transactional = true21 class ExcelService implements nl.tno.massSequencing.imports.Importer { 22 static transactional = false 18 23 19 24 /** … … 254 259 255 260 } 261 262 263 /** 264 * Determines whether a file can be processed. 265 * @param filetype Filetype of the file 266 * @see determineFileType() 267 * @return 268 */ 269 public boolean canParseFileType( String filetype ) { 270 switch( filetype ) { 271 case "excel": 272 return true; 273 default: 274 return false; 275 } 276 } 277 278 /** 279 * Parses the given excel file 280 * @param file File to parse 281 * @param filetype Type of the given file (only excel is accepted) 282 * @param onProgress Closure to execute when progress indicators should be updated. 283 * Has 2 parameters: progress that indicates the number of bytes that have been processed. The second parameter determines 284 * the number of bytes that has to be processed extra after this action (e.g. when a zip file is extracted, the extracted 285 * files should be added to the total number of bytes to be processed) 286 * @return Structure with information about the parsed files. The 'success' files are 287 * moved to the given directory 288 * 289 * Examples: 290 * [filename: 'abc.fasta', type: FASTA, numSequences: 190] 291 * [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29] 292 * 293 * [filename: 'test.zip', type: ZIP, extraFiles: [newfile1.xls, newfile2.xls, newfile3.xls]] 294 * 295 * [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized'] 296 * 297 */ 298 public Map parseFile( File file, String filetype, Closure onProgress ) { 299 switch( filetype ) { 300 case "excel": 301 return parseExcel( file, onProgress ); 302 default: 303 return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ] 304 } 305 } 306 307 /** 308 * Parses a given excel file with a match between filenames and samples 309 * @param file File to parse 310 * @param onProgress Closure to execute when progress indicators should be updated. 311 * Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number 312 * of files and bytes that have been processed in this file (so the first parameter should 313 * only be 1 when the file is finished) 314 * @return List structure. The matches array contains an array of matches between filenames and sample(name)s. 315 * The extension for all files are removed in the 'basename' parameter, in order to improve matching. 316 * Examples: 317 * 318 * [ success: true, filename: 'abc.xls', type: 'excel', matches: [ [ filename: 's1.qual', basename: 's1', sample: 'sample a' ], [ filename: 's9.fna', basename: 's9', sample: 'sample b' ] ] 319 * [ success: false, filename: 'def.xls', type: 'excel', message: 'File is not a valid XLS file' ] 320 */ 321 protected def parseExcel( File file, Closure onProgress ) { 322 long startTime = System.nanoTime(); 323 log.trace "Start parsing XLS " + file.getName() 324 325 def matches = [] 326 327 // Read excel file 328 def wb; 329 try { 330 wb = open( file ); 331 } catch( Exception e ) { 332 // If an exception occurs, the file can't be opened. Return the error message 333 return [ success: false, type: "excel", filename: file.getName(), message: "Excel file could not be opened or parsed."] 334 } 335 336 // Read all data into an array, and the header in a separate array 337 def header = readRow( wb, 0, 0 ); 338 def data = readData( wb, 0, 1 ); 339 340 // Update progress and return 341 onProgress( file.size(), 0 ); 342 return [ success: true, type: "excel", filename: file.getName(), header: header, data: data ]; 343 } 344 345 256 346 } -
trunk/grails-app/services/nl/tno/massSequencing/files/FileService.groovy
r53 r58 363 363 case "fasta": 364 364 case "fna": 365 return "fasta"; 365 return "fasta"; // FASTA format files (see http://en.wikipedia.org/wiki/FASTA_format) 366 366 case "qual": 367 case "fqa": 368 return "qual"; 367 case "fqa": 368 return "qual"; // QUAL format files for FASTA (see http://sequence.otago.ac.nz/download/ManualPartC.pdf) 369 369 case "xls": 370 370 case "xlsx": 371 return "excel"; 372 case "zip": 373 return "zip"; 371 return "excel"; // Excel files for metadata 372 case "taxonomy": 373 return "taxonomy"; // Taxonomy files as created by Mothur (www.mothur.org) for classification of sequences 374 case "groups": 375 return "groups"; // Mothur file to associate a specific sequence to a sample (www.mothur.org) 376 case "zip": 377 return "zip"; // Zip files combining all other files 374 378 case "gz": 375 return "gzip"; 379 return "gzip"; // GZip files combining all other files 376 380 default: 377 381 return ""; // meaning 'unknown' … … 395 399 case "zip": 396 400 case "gzip": 397 return true;401 return true; 398 402 } 399 403 … … 419 423 switch( this.determineFileType( f ) ) { 420 424 case "zip": 421 return extractZip( f, onProgress );425 return extractZip( f, onProgress ); 422 426 case "gzip": 423 return extractGzip( f, onProgress );427 return extractGzip( f, onProgress ); 424 428 default: 425 log.error "File given for extracting files is not a valid zip file."429 log.error "File given for extracting files is not a valid zip file." 426 430 427 431 return []; … … 498 502 499 503 // Update progress 500 onProgress( 0, entry.getCompressedSize(), 1, outpath.length())504 onProgress( entry.getCompressedSize(), outpath.length() ) 501 505 } 502 506 } … … 555 559 556 560 // Update progress 557 onProgress( 0, f.length(), 1, outpath.length())561 onProgress( f.length(), outpath.length()) 558 562 } catch( Exception e ) { 559 563 // Delete the file if it exists -
trunk/grails-app/views/assay/_addFilesDialog.gsp
r49 r58 2 2 <h2>Upload sequence files</h2> 3 3 4 <g:form name="addFiles" controller=" fasta" action="showProcessScreen" id="${assay.id}">4 <g:form name="addFiles" controller="import" action="showProcessScreen" id="${assay.id}"> 5 5 <input type="hidden" name="entityType" value="assay" /> 6 6 <p> -
trunk/grails-app/views/import/showProcessResult.gsp
r49 r58 25 25 </g:else> 26 26 </h1> 27 28 <h2>Sequence files</h2> 27 29 <g:if test="${matchedFiles.size() > 0}"> 28 30 <p> … … 96 98 </g:if> 97 99 <g:else> 98 <h2>Uploaded files</h2>99 100 <p> 100 101 ${parsedFiles.success?.size()} files were successfully uploaded, but no FASTA file could be determined. Please 101 upload at least one FASTA file .102 upload at least one FASTA file if sequences should be added. 102 103 </p> 103 104 </g:else> 105 106 <h2>Classification files</h2> 107 <g:if test="${classificationFiles.size() > 0}"> 108 <p> 109 The following files containing sequence classification have been imported. 110 </p> 111 <table> 112 <thead> 113 <tr> 114 <th>File</th> 115 <th># sequences imported</th> 116 <th># sequences discarded</th> 117 </tr> 118 </thead> 119 <g:each in="${classificationFiles}" var="file" status="i"> 120 <tr> 121 <td>${file.filename}</td> 122 <td>${file.importedSequences}</td> 123 <td>${file.discardedSequences}</td> 124 </tr> 125 </g:each> 126 </table> 127 </g:if> 128 <g:else> 129 <p> 130 No classification files found in uploaded data. 131 </p> 132 </g:else> 133 134 104 135 105 136 <g:if test="${parsedFiles.failure?.size()}"> 106 137 <h2>Failure</h2> 107 138 <p> 108 The following files could not be correctly imported. The system can only interpret FASTA and QUALfiles.<br />139 The following files could not be correctly imported. The system can only interpret FASTA, QUAL, GROUPS and TAXONOMY files.<br /> 109 140 If these files were intended to be included into the system, please check the messages below. 110 141 </p> … … 120 151 121 152 <p> 122 <g:link controller=" fasta" action="returnWithoutSaving" params="[entityType: entityType, id: entity.id]">Return to ${entityType}</g:link>153 <g:link controller="import" action="returnWithoutSaving" params="[entityType: entityType, id: entity.id]">Return to ${entityType}</g:link> 123 154 </p> 124 155 </body> -
trunk/grails-app/views/import/showProcessScreen.gsp
r34 r58 1 1 <html> 2 2 <head> 3 <meta name="layout" content="main" />4 <title>Processing files | Mass Sequencing | dbNP</title>3 <meta name="layout" content="main" /> 4 <title>Processing files | Mass Sequencing | dbNP</title> 5 5 6 <script type="text/javascript"> 7 $(function() { 8 $( "#wait_dialog" ).dialog({ 9 height: 140, 10 width: 400, 11 title: 'Please wait while processing files', 12 modal: true, 13 autoOpen: true, 14 buttons: {}, 15 closeOnEscape: false, 16 open: function(event, ui) { $(".ui-dialog-titlebar-close" ).hide(); }, 17 resizable: false 18 }); 19 20 // Show the progressbar and update the value every second 21 $( "#progressbar" ).progressbar({ 22 value: 0 23 }); 24 25 var progressInterval = setInterval(updateProgress, 250); 26 27 // Call processing URL 28 $.ajax({ 29 type: 'POST', 30 url: "<g:createLink controller="fasta" action="process" id="${entityId}" />", 31 data: "entityType=${entityType}", 32 success: function(data) { 33 34 // Stop update progress bar 35 clearInterval( progressInterval ); 36 37 window.location.replace( "${url.encodeAsJavaScript()}" ); 38 }, 39 error: function(xhr, textStatus, errorThrown) { 40 41 // Stop update progress bar (but update it for the last time) 42 updateProgress() 43 clearInterval( progressInterval ); 44 45 alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() ); 46 //window.location.replace( "<g:createLink controller="${entityType}" action="show" id="${entityId}" />" ); 47 } 48 }); 6 <script type="text/javascript"> 7 $(function() { 8 $( "#wait_dialog" ).dialog({ 9 height: 200, 10 width: 400, 11 title: 'Please wait while processing files', 12 modal: true, 13 autoOpen: true, 14 buttons: {}, 15 closeOnEscape: false, 16 open: function(event, ui) { $(".ui-dialog-titlebar-close" ).hide(); }, 17 resizable: false 49 18 }); 50 19 51 function updateProgress() { 52 $.ajax({ 53 type: "GET", 54 url: "<g:createLink controller="fasta" action="getProgress" />", 55 dataType: "json", 56 success: function(progress) { 57 $("#progressbar").progressbar("value", progress.bytesProcessed / progress.numBytes * 100 ); 58 } 59 }); 20 // Show the progressbar and update the value every second 21 $( "#progressbar" ).progressbar({ 22 value: 0 23 }); 24 25 var progressInterval = setInterval(updateProgress, 250); 26 27 // Call processing URL 28 $.ajax({ 29 type: 'POST', 30 url: "<g:createLink controller="import" action="process" id="${entityId}" />", 31 data: "entityType=${entityType}", 32 success: function(data) { 33 34 // Stop update progress bar 35 clearInterval( progressInterval ); 36 37 window.location.replace( "${url.encodeAsJavaScript()}" ); 38 }, 39 error: function(xhr, textStatus, errorThrown) { 40 41 // Stop update progress bar (but update it for the last time) 42 updateProgress() 43 clearInterval( progressInterval ); 44 45 alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() ); 46 //window.location.replace( "<g:createLink controller="${entityType}" action="show" id="${entityId}" />" ); 60 47 } 61 </script> 62 <style type="text/css"> 63 #wait_dialog { 64 padding: 20px; 65 text-align: center; 66 } 67 68 #progressbar { margin-top: 10px; } 69 </style> 48 }); 49 }); 50 51 function updateProgress() { 52 var stepDiv = $( '#step' ); 53 $.ajax({ 54 type: "GET", 55 url: "<g:createLink controller="import" action="getProgress" />", 56 dataType: "json", 57 success: function(progress) { 58 $( '#stepNum', stepDiv ).text( progress.stepNum ); 59 $( '#numSteps', stepDiv ).text( progress.numSteps ); 60 $( '#stepDescription', stepDiv ).text( progress.stepDescription ); 61 62 stepDiv.show(); 63 64 $("#progressbar").progressbar("value", progress.stepProgress / progress.stepTotal * 100 ); 65 } 66 }); 67 } 68 </script> 69 <style type="text/css"> 70 #wait_dialog { 71 padding: 20px; 72 text-align: center; 73 } 74 75 #progressbar { 76 margin-top: 10px; 77 } 78 </style> 70 79 </head> 71 80 <body> 72 <div id="wait_dialog"> 73 74 <span class="spinner" style="display: inline-block; zoom: 1; *display: inline;"></span> 75 Please wait while processing your files. 81 <div id="wait_dialog"> 82 <span class="spinner" style="display: inline-block; zoom: 1; *display: inline;"></span> 83 <p>Please wait while processing your files.</p> 84 <p id="step" style="display: none;"> 85 Step <span id="stepNum"></span> / <span id="numSteps"></span>: <span id="stepDescription"></span> 86 </p> 76 87 77 <div id="progressbar"></div>88 <div id="progressbar"></div> 78 89 79 </div>90 </div> 80 91 </body> 81 92 </html> -
trunk/grails-app/views/run/_addFilesDialog.gsp
r49 r58 2 2 <h2>Upload sequence files</h2> 3 3 4 <g:form name="addFiles" controller=" fasta" action="showProcessScreen" id="${run.id}">4 <g:form name="addFiles" controller="import" action="showProcessScreen" id="${run.id}"> 5 5 <input type="hidden" name="entityType" value="run" /> 6 6 <p> 7 Select sequence (.fna, .fasta) and quality (.fqa, .qual) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed.7 Select sequence (.fna, .fasta), quality (.fqa, .qual) and classification (.groups, .taxonomy) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed. 8 8 </p> 9 9 <p> 10 The filenames should match the sample names of the samples they belong to. It is also possible to provide an10 The filenames should match the sample names of the samples they belong to. You should also provide an 11 11 excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="run" action="downloadMatchExcel" id="${run.id}">example</g:link>, with each fasta file appearing once. 12 12 </p>
Note: See TracChangeset
for help on using the changeset viewer.