Changeset 59
- Timestamp:
- May 23, 2011, 5:52:20 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 3 added
- 3 deleted
- 27 edited
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/conf/Config.groovy
r52 r59 117 117 } 118 118 119 trace 'grails.app' 119 trace 'grails.app', 'org.hibernate.SQL' 120 120 121 121 error 'org.codehaus.groovy.grails.web.servlet', // controllers -
trunk/grails-app/controllers/masssequencing/SandboxController.groovy
r58 r59 3 3 import nl.tno.massSequencing.* 4 4 import nl.tno.massSequencing.classification.* 5 import nl.tno.massSequencing.auth.* 5 6 import org.hibernate.StatelessSession 7 import grails.converters.* 6 8 7 9 class SandboxController { 8 10 def sessionFactory 11 def classificationService 12 def dataTablesService; 9 13 10 14 def importTax = { … … 12 16 hibernateSession.clear(); 13 17 14 def filename = "/home/robert/tmp/test.taxonomy"15 def reader = new File( filename ).newReader();16 17 def parts18 def sequenceName19 def classification20 def taxa21 def taxon22 def line23 24 def classificationSplitRegex = /(\(\d+\))?;/25 def startLevel = 0 // The first level that is present in the file. Sometimes, the root element (level 0) is not mentioned in the file, in that case, this values should be set to 126 27 def i = 0;28 18 def start = System.currentTimeMillis(); 29 def lapTime = start; 30 19 20 def filename = "test.taxonomy" 31 21 def sequenceData = SequenceData.list(); 32 33 // Create a stateless session in order to speed up inserts 34 StatelessSession statelessSession = sessionFactory.openStatelessSession(); 35 statelessSession.beginTransaction(); 36 37 println "Starting at " + start 38 39 while( ( line = reader.readLine() ) ) { 40 // Find the taxon for this line 41 parts = line.tokenize( "\t" ); 42 43 if( parts.size() != 2 ) { 44 // Skip this line because it is incorrect 45 continue; 46 } 47 48 sequenceName = parts[ 0 ]; 49 classification = parts[ 1 ]; 50 51 // Split classification to check whether the taxon already exists 52 taxa = classification.split( classificationSplitRegex ) 53 54 // Find taxon or create one if needed 55 taxon = Taxon.findOrCreateTaxonByPath( taxa, startLevel ); 56 57 // Determine assaySample 58 Collections.shuffle( sequenceData ); 59 60 // Create a new sequence record 61 def s = new Sequence() 62 s.name = sequenceName 63 s.classification = taxon 64 s.sequenceData = sequenceData[ 0 ] 65 66 statelessSession.insert(s); 67 68 if( i % 40 == 0 ) { 69 } 70 71 if( i % 256 == 0 ) { 72 print "" + ( System.currentTimeMillis() - lapTime ) + " " 73 lapTime = System.currentTimeMillis(); 74 } 75 76 if( i % 2048 == 0 ) { 77 println "" 78 print i + ": " 79 } 80 81 i++; 82 } 83 84 reader.close(); 22 Collections.shuffle( sequenceData ); 23 24 classificationService.storeClassification( filename, sequenceData[ 0 ], { bytes -> println "Bytes processed: " + bytes } ) 85 25 86 26 def end = System.currentTimeMillis(); … … 89 29 println "------------------------------------------" 90 30 println "Duration: " + ( end - start ) + " ms" 91 println "Lines: " + i92 println "Average: " + ( ( end - start ) / i ) + "ms / line"93 31 94 32 } … … 179 117 writer.close(); 180 118 } 119 120 def createSummary = { 121 def sequenceData = SequenceData.get( 222 ); 122 def sequenceData2 = SequenceData.get( 221 ); 123 def assaySamples = [ sequenceData.sample, sequenceData2.sample ]; 124 125 classificationService.updateClassificationForAssaySamples( assaySamples ); 126 127 def classifications = Classification.executeQuery( "FROM Classification c WHERE c.assaySample = ? ORDER BY c.taxon.lft", [ assaySamples[0] ] ); 128 129 println classifications.size() + " classifications for " + assaySamples[0].sample.name + " (" + assaySamples[0].id + ")" 130 classifications.each { 131 println " ".multiply( it.taxon.level ) + it.taxon.level + " " + it.taxon.name + " - " + it.occurrences 132 } 133 134 135 classifications = Classification.executeQuery( "FROM Classification c WHERE c.assaySample = ? ORDER BY c.taxon.lft", [ assaySamples[1] ] ); 136 137 println classifications.size() + " classifications for " + assaySamples[1].sample.name + " (" + assaySamples[1].id + ")" 138 classifications.each { 139 println " ".multiply( it.taxon.level ) + it.taxon.level + " " + it.taxon.name + " - " + it.occurrences 140 } 141 142 render ""; 143 } 144 145 def datatables = { 146 long runId = 3; 147 long userId = 2; 148 println "writable assays: " + Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ?", [runId]) 149 println "writable assays: " + Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ? AND NOT EXISTS( FROM a.study.auth auth WHERE auth.canWrite = true AND auth.user.id = ?)", [runId, userId ]) 150 151 //println "total assaysamples: " + Run.executeQuery( 'SELECT r.id, r.name, COUNT( a ), SUM( sd.numSequences ) FROM Run r LEFT JOIN r.assaySamples a LEFT JOIN a.sequenceData sd GROUP BY r.id, r.name' ); 152 // println "total assaysamples: " + Run.executeQuery( 'SELECT r.id, r.name, SUM( sd.numSequences ) FROM Run r LEFT JOIN SequenceData sd WITH EXISTS( FROM r.assaySamples a WHERE a = sd.assaySample )' ); 153 } 154 155 /** 156 * Returns data for datatables as JSON 157 * @see http://www.datatables.net/usage/server-side 158 * Input: 159 * 160 int iDisplayStart Display start point 161 int iDisplayLength Number of records to display 162 int iColumns Number of columns being displayed (useful for getting individual column search info) 163 string sSearch Global search field 164 boolean bEscapeRegex Global search is regex or not 165 boolean bSortable_(int) Indicator for if a column is flagged as sortable or not on the client-side 166 boolean bSearchable_(int) Indicator for if a column is flagged as searchable or not on the client-side 167 string sSearch_(int) Individual column filter 168 boolean bEscapeRegex_(int) Individual column filter is regex or not 169 int iSortingCols Number of columns to sort on 170 int iSortCol_(int) Column being sorted on (you will need to decode this number for your database) 171 string sSortDir_(int) Direction to be sorted - "desc" or "asc". Note that the prefix for this variable is wrong in 1.5.x where iSortDir_(int) was used) 172 string sEcho Information for DataTables to use for rendering 173 * 174 * 175 */ 176 def datatables_data = { 177 def run = Run.get( 3 ); 178 def total = (int) Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run.id = :runId", [ "runId": run.id ] )[ 0 ]; 179 180 def columns = [ 181 null, 182 "s.sample.name", 183 "s.assay.study.name", 184 "s.assay.name", 185 "s.fwMidName", 186 "SUM( sd.numSequences )", 187 "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )", 188 null, 189 null, 190 null, 191 's.run.id', 192 's.assay.id', 193 's.assay.study.id', 194 ] 195 196 def idColumn = 's.id'; 197 def groupColumns = [ idColumn ] + columns[1..4] + columns[10]; 198 199 def from = "AssaySample s LEFT JOIN s.sequenceData as sd" 200 def where = "s.run.id = :runId" 201 def parameters = [ "runId": run.id ]; 202 203 render dataTablesService.retrieveData( 204 params, 205 AssaySample.class, 206 { 207 def sampleId = it[ 0 ]; 208 def runId = it[ 7 ]; 209 def assayId = it[ 8 ]; 210 def studyId = it[ 9 ]; 211 def numSequences = it[ 5 ]; 212 213 // TODO: Rewrite this authorization part in a more efficient way 214 def auth = Auth.executeQuery( "SELECT a.canWrite FROM Auth a WHERE a.study.id = :studyId AND a.user.id = :userId", [ 'studyId': studyId, 'userId': session.user?.id ] ); 215 def canWrite = auth ? auth[ 0 ] : false; 216 217 // How to convert a database row to a datatables row 218 def editButton = ''; 219 def deleteButton = ''; 220 def chartButton = ''; 221 222 if( canWrite ) { 223 editButton = g.link( url: '#', title: "Edit sample", onClick: "showEditSampleDialog(" + sampleId + ", 'run', " + runId + ");" ) { '<img src="' + fam.icon( name: 'pencil' ) + '" title="Edit sample" />' }; 224 deleteButton = g.link( controller: 'run', action: 'removeSample', id: it[ 7 ], params: [ 'assaySampleId': sampleId ], title: "Remove sample from run", onClick: "return confirm( 'Are you sure you want to remove the selected sample from this run?' );" ) { '<img src="' + fam.icon( name: 'application_delete' ) + '" title="Remove sample from run" />' }; 225 } else { 226 editButton = '<img src="' + fam.icon( name: 'pencil' ) + '" title="You can\'t edit this sample because you don\'t have sufficient privileges." class="disabled" />'; 227 deleteButton = '<img src="' + fam.icon( name: 'application_delete' ) + '" title="You can\'t remove this sample because you don\'t have sufficient privileges." class="disabled" />'; 228 } 229 230 if( numSequences > 0 ) { 231 chartButton = g.link( controller: "assaySample", action: "sequenceLengthHistogram", id: sampleId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' } 232 } else { 233 chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />' 234 } 235 236 [ 237 g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ), 238 it[ 1 ], // it.sample.name 239 it[ 2 ], // it.assay.study.name 240 it[ 3 ], // it.assay.name 241 it[ 4 ], // it.fwMidName 242 it[ 5 ], // it.numSequences(), 243 it[ 6 ], // it.numQualScores(), 244 editButton, 245 deleteButton, 246 chartButton 247 ] 248 }, 249 columns, 250 groupColumns, 251 idColumn, 252 from, 253 total, 254 where, 255 parameters 256 ) as JSON 257 } 181 258 } -
trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy
r52 r59 2 2 3 3 import java.util.List; 4 import grails.converters.JSON 4 5 5 6 import org.codehaus.groovy.grails.commons.ConfigurationHolder … … 9 10 def gscfService 10 11 def fuzzySearchService 12 def dataTablesService 11 13 12 14 def fileService … … 21 23 gscfAddUrl: gscfService.urlAddStudy() ] 22 24 } 25 26 /** 27 * Returns JSON data for the datatable with runs 28 * @see http://www.datatables.net/usage/server-side 29 * @see DataTablesService.retrieveData 30 */ 31 def showAssayList = { 32 // Determine the total number of assaysamples for this run 33 int total = Assay.executeQuery( "SELECT COUNT(*) FROM Assay a WHERE a.study.trashcan = false")[ 0 ] 34 35 // Which columns are shown on screen and should be retrieved from the database 36 def columns = [ 37 null, 38 "a.name", 39 "a.study.name", 40 "COUNT( DISTINCT s )", 41 "SUM( sd.numSequences ) / COUNT( DISTINCT s )", 42 null, 43 null, 44 "a.study.studyToken" 45 ] 46 47 def idColumn = 'a.id'; 48 def groupColumns = [ idColumn ] + columns[1..2] + columns[ 7 ]; 49 50 // Retrieve data from assaySample table 51 def from = "Assay a LEFT JOIN a.assaySamples s LEFT JOIN s.sequenceData sd" 52 def where = " EXISTS( FROM Auth auth WHERE auth.study = a.study AND auth.user = :user AND auth.canRead = true )" 53 def parameters = [ "user": session.user ] 54 55 // This closure determines what to do with a row that is retrieved from the database. 56 def convertClosure = { 57 def assayId = it[ 0 ]; 58 def assayName = it[ 1 ]; 59 def studyName = it[ 2 ]; 60 def numSamples = it[ 3 ]; 61 def numSequences = it[ 4 ]; 62 def studyToken = it[ 5 ]; 63 64 // Create buttons in the last three columns 65 def editButton = g.link( controller: "assay", action: "show", id: assayId, "title": "View assay" ) { '<img src="' + fam.icon( name: 'application_form_magnify' ) + '" title="View assay" />' }; 66 def chartButton = ''; 67 68 if( numSequences > 0 ) { 69 chartButton = g.link( controller: "assay", action: "sequenceLengthHistogram", id: assayId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' } 70 } else { 71 chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />' 72 } 73 74 [ 75 g.checkBox( name: "ids", value: assayId, checked: false, onClick: "updateCheckAll(this);" ), 76 g.link( title:"View assay", controller:"assay", action:"show", id: assayId ) { assayName }, // it.name 77 g.link( title:"View study", url: gscfService.urlViewStudy( studyToken )) { studyName }, // it.study.name 78 numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-", // it.numSequences(), 79 numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(), 80 editButton, 81 chartButton 82 ] 83 } 84 85 // Send the data to the user 86 render dataTablesService.retrieveData( 87 params, 88 Assay.class, 89 convertClosure, 90 columns, 91 groupColumns, 92 idColumn, 93 from, 94 total, 95 where, 96 parameters 97 ) as JSON 98 } 99 23 100 24 101 def show = { … … 47 124 } 48 125 126 /** 127 * Returns JSON data for the datatable with assaysamples 128 * @see http://www.datatables.net/usage/server-side 129 * @see DataTablesService.retrieveData 130 */ 131 def showSampleData = { 132 // load run with id specified by params.id 133 def assay = getAssay( params.id ); 134 135 if (!assay) { 136 response.sendError(404, "Run not found" ) 137 return 138 } 139 140 // Determine the total number of assaysamples for this run 141 def total = (int) Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.assay.id = :assayId", [ "assayId": assay.id ] )[ 0 ]; 142 143 // Which columns are shown on screen and should be retrieved from the database 144 def columns = [ 145 null, 146 "s.sample.name", 147 "run.name", 148 "s.fwMidName", 149 "SUM( sd.numSequences )", 150 "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )", 151 null, 152 null, 153 's.assay.id', 154 's.assay.study.id', 155 ] 156 157 def idColumn = 's.id'; 158 def groupColumns = [ idColumn ] + columns[1..3] + columns[8..9]; 159 160 // Retrieve data from assaySample table 161 def from = "AssaySample s LEFT JOIN s.sequenceData as sd LEFT JOIN s.run as run" 162 163 // And filter by runId 164 def where = "s.assay.id = :assayId" 165 def parameters = [ "assayId": assay.id ]; 166 167 def canWrite = assay.study.canWrite( session.user ); 168 169 // This closure determines what to do with a row that is retrieved from the database. 170 def convertClosure = { 171 def sampleId = it[ 0 ]; 172 def assayId = it[ 6 ]; 173 def studyId = it[ 7 ]; 174 def numSequences = it[ 4 ]; 175 176 // Create buttons in the last three columns 177 def editButton = ''; 178 def chartButton = ''; 179 180 if( canWrite ) { 181 editButton = g.link( url: '#', title: "Edit sample", onClick: "showEditSampleDialog(" + sampleId + ", 'assay', " + assayId + ");" ) { '<img src="' + fam.icon( name: 'pencil' ) + '" title="Edit sample" />' }; 182 } else { 183 editButton = '<img src="' + fam.icon( name: 'pencil' ) + '" title="You can\'t edit this sample because you don\'t have sufficient privileges." class="disabled" />'; 184 } 185 186 if( numSequences > 0 ) { 187 chartButton = g.link( controller: "assaySample", action: "sequenceLengthHistogram", id: sampleId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' } 188 } else { 189 chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />' 190 } 191 192 [ 193 g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ), 194 it[ 1 ], // it.sample.name 195 it[ 2 ], // it.run.name 196 it[ 3 ], // it.fwMidName 197 it[ 4 ] > 0 ? g.formatNumber( number: it[ 4 ], format: "###,###,##0" ) : "-", // it.numSequences(), 198 it[ 5 ] > 0 ? g.formatNumber( number: it[ 5 ], format: "###,###,##0" ) : "-", // it.numQualScores(), 199 editButton, 200 chartButton 201 ] 202 } 203 204 // Send the data to the user 205 render dataTablesService.retrieveData( 206 params, 207 AssaySample.class, 208 convertClosure, 209 columns, 210 groupColumns, 211 idColumn, 212 from, 213 total, 214 where, 215 parameters 216 ) as JSON 217 } 218 49 219 def sequenceLengthHistogram = { 50 def id = params.long( 'id' ); 51 def assay = id ? Assay.get( id ) : null 52 53 if( !id || !assay ) { 54 flash.message = "No assay selected"; 55 redirect( action: "index" ); 56 return; 57 } 58 59 [ assay: assay, histogram: fastaService.sequenceLengthHistogram( assay.assaySamples.toList() ) ] 60 } 220 redirect( controller: "assaySample", action: "sequenceLengthHistogramForAssay", id: params.id ); 221 } 222 61 223 62 224 def showByToken = { -
trunk/grails-app/controllers/nl/tno/massSequencing/AssaySampleController.groovy
r57 r59 36 36 return; 37 37 } 38 39 def title = "sample " + assaySample.sample.name + " (" + assaySample.assay.study.name + " / " + assaySample.assay.name + ")" 40 renderSequenceLengthHistogram( title, [assaySample] ) 41 } 42 43 def sequenceLengthHistogramForRun = { 44 def id = params.long( 'id' ); 45 def run = id ? Run.get( id ) : null 38 46 39 [ assaySample: assaySample, histogram: fastaService.sequenceLengthHistogram( [assaySample] ) ] 47 if( !id || !run ) { 48 flash.message = "No run selected"; 49 redirect( controller: 'run', action: "index" ); 50 return; 51 } 52 53 def title = "run " + run.name 54 renderSequenceLengthHistogram( title, run.assaySamples?.toList() ) 55 } 56 57 def sequenceLengthHistogramForAssay = { 58 def id = params.long( 'id' ); 59 def assay = id ? Assay.get( id ) : null 60 61 if( !id || !assay ) { 62 flash.message = "No assay selected"; 63 redirect( controller: 'assay', action: "index" ); 64 return; 65 } 66 67 def title = "assay " + assay.name 68 renderSequenceLengthHistogram( title, assay.assaySamples?.toList() ) 69 } 70 71 def sequenceLengthHistogramForStudy = { 72 def id = params.long( 'id' ); 73 def assay = id ? Study.get( id ) : null 74 75 if( !id || !study ) { 76 flash.message = "No study selected"; 77 redirect( controller: 'study', action: "index" ); 78 return; 79 } 80 81 def title = "study " + study.name 82 renderSequenceLengthHistogram( title, study.assays*.assaySamples.flatten().unique().findAll { it } ) 83 } 84 /** 85 * Renders a histogram for the given assaySamples 86 * @param title 87 * @param assaySamples 88 */ 89 protected void renderSequenceLengthHistogram( String title, def assaySamples ) { 90 def numSequences = assaySamples.collect { it.numSequences() }.sum() 91 render( view: "sequenceLengthHistogram", model: [ title: title, numSequences: numSequences, histogram: fastaService.sequenceLengthHistogram( assaySamples ) ] ); 40 92 } 41 93 -
trunk/grails-app/controllers/nl/tno/massSequencing/RunController.groovy
r52 r59 2 2 3 3 import java.util.Date; 4 import grails.converters.JSON 5 import nl.tno.massSequencing.auth.* 4 6 5 7 import org.codehaus.groovy.grails.commons.ConfigurationHolder … … 10 12 def sampleExcelService 11 13 def fastaService 14 def dataTablesService 12 15 13 16 def index = { 14 17 [runs: Run.list(), user: session.user] 15 18 } 19 20 /** 21 * Returns JSON data for the datatable with runs 22 * @see http://www.datatables.net/usage/server-side 23 * @see DataTablesService.retrieveData 24 */ 25 def showRunList = { 26 // Determine the total number of assaysamples for this run 27 def total = Run.count() 28 29 // Which columns are shown on screen and should be retrieved from the database 30 def columns = [ 31 null, 32 "r.name", 33 "COUNT( a )", 34 "SUM( sd.numSequences )", 35 null, 36 null, 37 null 38 ] 39 40 def idColumn = 'r.id'; 41 def groupColumns = [ idColumn ] + columns[1]; 42 43 // Retrieve data from assaySample table 44 def from = "Run r LEFT JOIN r.assaySamples a LEFT JOIN a.sequenceData sd" 45 46 // This closure determines what to do with a row that is retrieved from the database. 47 def convertClosure = { 48 def runId = it[ 0 ]; 49 def runName = it[ 1 ]; 50 def numSamples = it[ 2 ]; 51 def numSequences = it[ 3 ]; 52 53 // Create buttons in the last three columns 54 def editButton = g.link( controller: "run", action: "show", id: it[ 0 ], "title": "View run" ) { '<img src="' + fam.icon( name: 'application_form_magnify' ) + '" title="View run" />' }; 55 def deleteButton = ''; 56 def chartButton = ''; 57 58 if( numSequences > 0 ) { 59 chartButton = g.link( controller: "run", action: "sequenceLengthHistogram", id: runId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' } 60 } else { 61 chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />' 62 } 63 64 if( numSequences > 0 || Run.hasNonWritableAssays( runId, session.user.id ) ) { 65 deleteButton = '<img src="' + fam.icon( name: 'delete' ) + '" class="disabled" alt="Run can not be deleted because data is associated with it." title="Run can not be deleted because data is associated with it." />' 66 } else { 67 deleteButton = g.link( title:"Delete run", onClick:"return confirm( 'Are you sure you want to delete this run?' );", controller:"run", action:"deleteRun", id: runId ) { '<img src="' + fam.icon( name: 'delete' ) + '" alt="Delete run" title="Delete run" />' } 68 } 69 70 [ 71 g.checkBox( name: "ids", value: runId, checked: false, onClick: "updateCheckAll(this);" ), 72 g.link( title:"View run", controller:"run", action:"show", id: runId ) { runName }, // it.name 73 numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-", // it.numSequences(), 74 numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(), 75 editButton, 76 deleteButton, 77 chartButton 78 ] 79 } 80 81 // Send the data to the user 82 render dataTablesService.retrieveData( 83 params, 84 Run.class, 85 convertClosure, 86 columns, 87 groupColumns, 88 idColumn, 89 from, 90 total, 91 ) as JSON 92 } 93 16 94 17 95 def show = { … … 40 118 def otherAssays = Assay.list( sort: "name" ).findAll { !it.runs.contains( run ) && it.study.canRead( session.user ) } 41 119 120 // Determine several parameters to show on screen 121 122 42 123 // Send the assay information to the view 43 124 [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true] 125 } 126 127 /** 128 * Returns JSON data for the datatable with assaysamples 129 * @see http://www.datatables.net/usage/server-side 130 * @see DataTablesService.retrieveData 131 */ 132 def showSampleData = { 133 // load run with id specified by params.id 134 def run = getRun( params.id ); 135 136 if (!run) { 137 response.sendError(404, "Run not found" ) 138 return 139 } 140 141 // Determine the total number of assaysamples for this run 142 def total = (int) Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run.id = :runId AND EXISTS( FROM Auth a3 WHERE s.assay.study = a3.study AND a3.user = :user AND a3.canRead = true )", [ "runId": run.id, "user": session.user ] )[ 0 ]; 143 144 // Which columns are shown on screen and should be retrieved from the database 145 def columns = [ 146 null, 147 "s.sample.name", 148 "s.assay.study.name", 149 "s.assay.name", 150 "s.fwMidName", 151 "SUM( sd.numSequences )", 152 "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )", 153 null, 154 null, 155 null, 156 's.run.id', 157 's.assay.id', 158 's.assay.study.id', 159 ] 160 161 def idColumn = 's.id'; 162 def groupColumns = [ idColumn ] + columns[1..4] + columns[10..12]; 163 164 // Retrieve data from assaySample table 165 def from = "AssaySample s LEFT JOIN s.sequenceData as sd" 166 167 // And filter by runId 168 def where = "s.run.id = :runId AND EXISTS( FROM Auth a3 WHERE s.assay.study = a3.study AND a3.user = :user AND a3.canRead = true )" 169 def parameters = [ "runId": run.id, "user": session.user ]; 170 171 // This closure determines what to do with a row that is retrieved from the database. 172 def convertClosure = { 173 def sampleId = it[ 0 ]; 174 def runId = it[ 7 ]; 175 def assayId = it[ 8 ]; 176 def studyId = it[ 9 ]; 177 def numSequences = it[ 5 ]; 178 179 // TODO: Rewrite this authorization part in a more efficient way 180 def auth = Auth.executeQuery( "SELECT a.canWrite FROM Auth a WHERE a.study.id = :studyId AND a.user.id = :userId", [ 'studyId': studyId, 'userId': session.user?.id ] ); 181 def canWrite = auth ? auth[ 0 ] : false; 182 183 // Create buttons in the last three columns 184 def editButton = ''; 185 def deleteButton = ''; 186 def chartButton = ''; 187 188 if( canWrite ) { 189 editButton = g.link( url: '#', title: "Edit sample", onClick: "showEditSampleDialog(" + sampleId + ", 'run', " + runId + ");" ) { '<img src="' + fam.icon( name: 'pencil' ) + '" title="Edit sample" />' }; 190 deleteButton = g.link( controller: 'run', action: 'removeSample', id: it[ 7 ], params: [ 'assaySampleId': sampleId ], title: "Remove sample from run", onClick: "return confirm( 'Are you sure you want to remove the selected sample from this run?' );" ) { '<img src="' + fam.icon( name: 'application_delete' ) + '" title="Remove sample from run" />' }; 191 } else { 192 editButton = '<img src="' + fam.icon( name: 'pencil' ) + '" title="You can\'t edit this sample because you don\'t have sufficient privileges." class="disabled" />'; 193 deleteButton = '<img src="' + fam.icon( name: 'application_delete' ) + '" title="You can\'t remove this sample because you don\'t have sufficient privileges." class="disabled" />'; 194 } 195 196 if( numSequences > 0 ) { 197 chartButton = g.link( controller: "assaySample", action: "sequenceLengthHistogram", id: sampleId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' } 198 } else { 199 chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />' 200 } 201 202 [ 203 g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ), 204 it[ 1 ], // it.sample.name 205 it[ 2 ], // it.assay.study.name 206 it[ 3 ], // it.assay.name 207 it[ 4 ], // it.fwMidName 208 it[ 5 ] > 0 ? g.formatNumber( number: it[ 5 ], format: "###,###,##0" ) : "-", // it.numSequences(), 209 it[ 6 ] > 0 ? g.formatNumber( number: it[ 6 ], format: "###,###,##0" ) : "-", // it.numQualScores(), 210 editButton, 211 deleteButton, 212 chartButton 213 ] 214 } 215 216 // Send the data to the user 217 render dataTablesService.retrieveData( 218 params, 219 AssaySample.class, 220 convertClosure, 221 columns, 222 groupColumns, 223 idColumn, 224 from, 225 total, 226 where, 227 parameters 228 ) as JSON 44 229 } 45 230 … … 699 884 700 885 def sequenceLengthHistogram = { 701 def id = params.long( 'id' ); 702 def run = id ? Run.get( id ) : null 703 704 if( !id || !run ) { 705 flash.message = "No run selected"; 706 redirect( action: "index" ); 707 return; 708 } 709 710 [ run: run, histogram: fastaService.sequenceLengthHistogram( run.assaySamples?.toList() ) ] 886 redirect( controller: "assaySample", action: "sequenceLengthHistogramForRun", id: params.id ); 711 887 } 712 888 -
trunk/grails-app/controllers/nl/tno/massSequencing/StudyController.groovy
r52 r59 26 26 27 27 def sequenceLengthHistogram = { 28 def id = params.long( 'id' ); 29 def study = id ? Study.get( id ) : null 30 31 if( !id || !study ) { 32 flash.message = "No study selected"; 33 redirect( action: "index" ); 34 return; 35 } 36 37 [ study: study, histogram: fastaService.sequenceLengthHistogram( study.assays*.assaySamples.flatten().unique().findAll { it } ) ] 28 redirect( controller: "assaySample", action: "sequenceLengthHistogramForStudy", id: params.id ); 38 29 } 39 30 -
trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy
r58 r59 19 19 *************************************************************************/ 20 20 21 /** 22 * Shows a screen t hat processing is done21 /** 22 * Shows a screen to indicate that files are being parsed 23 23 */ 24 def showProcessScreen= {24 def parseUploadedFiles = { 25 25 def entityType = params.entityType 26 26 27 27 // Check whether files are given 28 28 def names = params.list( 'sequencefiles' ) … … 37 37 return 38 38 } 39 39 40 // Create a unique process identifier 41 String processId = UUID.randomUUID().toString(); 42 40 43 // Save filenames in session 41 session.processFilenames = names; 44 if( !session.process ) 45 session.process = [:] 46 47 if( !session.process[ processId ] ) 48 session.process[ processId ] = [:] 49 50 session.process[ processId ].filenames = names; 51 session.process[ processId ].entityId = params.id; 52 session.process[ processId ].entityType = entityType 42 53 43 54 // Check for total size of the files in order to be able … … 47 58 filesize += fileService.get( it )?.length() 48 59 } 49 50 session.processProgress = [ 60 61 if( !session.progress ) 62 session.progress = [:] 63 64 session.progress[ processId ] = [ 51 65 stepNum: 1, 52 66 numSteps: 2, … … 56 70 stepTotal: filesize 57 71 ] 58 59 [entityId: params.id, entityType: params.entityType, filenames: names, url: createLink( action: 'showProcessResult', id: params.id, params: [entityType: entityType] ) ] 72 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] ); 60 80 } 61 81 … … 63 83 * Processes uploaded files and tries to combine them with samples 64 84 */ 65 def process = { 85 def processUploadedFiles = { 86 def processId = params.processId 66 87 def entity 67 def assaySamples68 88 69 89 switch( params.entityType ) { 70 90 case "run": 71 entity = getRun( params.id ); 72 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 91 entity = getRun( params.entityId ); 73 92 break; 74 93 case "assay": 75 entity = getAssay( params.id ); 76 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 94 entity = getAssay( params.entityId ); 77 95 break; 78 96 default: … … 81 99 return; 82 100 } 83 101 102 def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 103 84 104 if (!entity) { 85 105 response.setStatus( 404, flash.error ) … … 89 109 90 110 // Check whether files are given 91 def names = session.process Filenames111 def names = session.process[ processId ]?.filenames 92 112 93 113 if( !names ) { 114 println "Process ID: " + processId 115 session.process.each { 116 println it.key + " = " + it.value; 117 } 94 118 response.setStatus( 500, "No files uploaded for processing" ) 95 119 render ""; … … 123 147 def onProgress = { progress, total -> 124 148 // Update progress 125 httpSession.pro cessProgress.stepTotal = total;126 httpSession.pro cessProgress.stepProgress = progress;149 httpSession.progress[ processId ].stepTotal = total; 150 httpSession.progress[ processId ].stepProgress = progress; 127 151 } 128 152 def newStep = { total, description -> 129 153 // 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 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 ); 140 162 141 163 // Determine excel matches from the uploaded files 142 164 parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success ); 143 165 144 // Now check whether a taxonomy and groups file are uploaded. If so send them to the classificationService for145 // parsing146 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 system151 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 ] = foundSample157 }158 }159 }160 161 if( classificationFiles ) {162 parsedFiles.success -= classificationFiles;163 164 // If no excel matches are found, no classifications can be stored165 if( !excelMatches ) {166 classificationFiles.each {167 it.success = false168 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 while174 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 190 166 // Match files with samples in the database 191 167 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples ); … … 194 170 matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename } 195 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 196 185 // Saved file matches in session to use them later on 197 session.process edFiles = [ parsed: parsedFiles, matched: matchedFiles ];186 session.process[ processId ].processedFiles = [ parsed: parsedFiles, matched: matchedFiles, notMatched: notMatchedFiles ]; 198 187 199 188 render "" 200 189 } 201 190 202 191 def getProgress = { 203 if( !session.processProgress ) { 192 def processId = params.processId; 193 if( !processId || !session.progress?.getAt( processId ) ) { 204 194 response.setStatus( 500, "No progress information found" ); 205 195 render "" … … 207 197 } 208 198 209 render session.pro cessProgressas JSON199 render session.progress[ processId ] as JSON 210 200 } 211 201 212 202 /** 213 * Show result of processing 203 * Show result of processing uploaded files (step 1) 214 204 */ 215 def showProcessResult = { 205 def parseUploadResult = { 206 def processId = params.processId; 216 207 // load study with id specified by param.id 217 208 def entity … … 229 220 return; 230 221 } 231 222 223 def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }; 224 232 225 if (!entity) { 233 226 response.setStatus( 404, flash.error ) … … 236 229 } 237 230 238 if( !session.process edFiles ) {231 if( !session.process[ processId ].processedFiles ) { 239 232 flash.error = "Processing of files failed. Maybe the session timed out." 240 redirect( controller: 'assay', action: 'show', 'id': params.id) 241 return 242 } 243 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 ] 233 redirect( controller: params.entityType, action: 'show', 'id': params.id) 234 return 235 } 236 237 238 // Find matching sequenceData objects for taxonomyfiles that have not been matched 239 def notMatchedFiles = session.process[ processId ].processedFiles.notMatched; 240 def extraClassifications = notMatchedFiles.findAll { it.type == "taxonomy" } 241 extraClassifications.collect { 242 // Find all sequence files that have the correct number of sequences and are in the list of assaySamples 243 it[ 'feasibleSequenceData' ] = SequenceData.findAllByNumSequences( it.numLines ).findAll { assaySamples.contains( it.sample ) } 244 return it 245 } 246 247 [ entityType: params.entityType, processId: processId, entity: entity, id: params.id, 248 parsedFiles: session.process[ processId ].processedFiles.parsed, 249 matchedFiles: session.process[ processId ].processedFiles.matched, 250 remainingClassificationFiles: extraClassifications, 251 selectedRun: params.selectedRun ] 245 252 } 246 253 … … 249 256 */ 250 257 def returnWithoutSaving = { 258 def processId = params.processId; 259 251 260 // Delete all uploaded files from disk 252 session.process edFiles?.parsed?.success?.each {261 session.process[ processId ]?.processedFiles?.parsed?.success?.each { 253 262 fileService.delete( it.filename ); 254 263 } … … 269 278 } 270 279 280 /** 281 * Shows a screen with the progress of saving matched files 282 */ 283 def saveMatchedFiles = { 284 def entityType = params.entityType 285 def processId = params.processId 286 287 session.process[ processId ].matchedFiles = params.file 288 session.process[ processId ].matchedRemainingClassification = params.remainingClassification 289 290 // Check for total size of the classification files in order to be able 291 // to show a progress bar. The handling of classification files is orders 292 // of magnitude bigger than the rest, so we only show progress of those files 293 long filesize = 0; 294 295 // Loop through all files. Those are the numeric elements in the 'files' array 296 def digitRE = ~/^\d+$/; 297 params.file.findAll { it.key.matches( digitRE ) }.each { file -> 298 def filevalue = file.value; 299 300 // Check if the file is selected 301 if( filevalue.include == "on" ) { 302 if( fileService.fileExists( filevalue.fasta ) ) { 303 // Also save classification data for this file, if it is present 304 if( filevalue.classification ) { 305 filesize += fileService.get( filevalue.classification )?.size() 306 } 307 } 308 } 309 } 310 params.remainingClassification.findAll { it.key.matches( digitRE ) }.each { file -> 311 def filevalue = file.value; 312 313 // Check if the file is selected 314 if( filevalue.include == "on" ) { 315 if( fileService.fileExists( filevalue.filename ) ) { 316 // Also save classification data for this file, if it is present 317 filesize += fileService.get( filevalue.filename )?.size() 318 } 319 } 320 } 321 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 271 343 /** 272 344 * Saves processed files to the database, based on the selections made by the user 273 345 */ 274 def saveProcessedFiles = {346 def processMatchedFiles = { 275 347 // load entity with id specified by param.id 276 def entity 277 278 switch( params.entityType ) { 279 case "run": 280 entity = getRun( params.id ); 281 break; 282 case "assay": 283 entity = getAssay( params.id ); 284 break; 285 default: 286 response.setStatus( 404, "No entity found" ); 287 render ""; 288 return; 289 } 290 291 if (!entity) { 292 response.setStatus( 404, flash.error ) 293 render ""; 294 return 295 } 348 def processId = params.processId; 296 349 297 350 // Check whether files are given 298 def files = params.file 299 300 if( !files ) { 301 flash.message = "No files were selected." 302 redirect( controller: params.entityType, action: 'show', 'id': params.id) 351 def files = session.process[ processId ].matchedFiles 352 def remainingClassification = session.process[ processId ].matchedRemainingClassification; 353 354 if( !files && !remainingClassification ) { 355 flash.message = "No files were selected for import." 356 redirect( controller: params.entityType, action: 'show', 'id': params.entityId) 303 357 return 304 358 } 305 359 306 360 File permanentDir = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir ) 361 362 // This closure enables keeping track of the progress 363 def httpSession = session; 364 def onProgress = { progress -> 365 // Update progress 366 httpSession.progress[ processId ].stepProgress += progress; 367 } 368 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 ""; 395 } 396 397 def saveMatchedFastaFiles( def files, processedFiles, Closure onProgress ) { 307 398 int numSuccesful = 0; 399 int numQualFiles = 0; 400 int numClassificationFiles = 0; 401 def samplesClassified = []; 308 402 def errors = []; 309 310 // Loop through all files. Those are the numeric elements in the 'files' array 403 311 404 def digitRE = ~/^\d+$/; 312 405 files.findAll { it.key.matches( digitRE ) }.each { file -> … … 317 410 if( fileService.fileExists( filevalue.fasta ) ) { 318 411 try { 319 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, session.processedFiles );412 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, processedFiles ); 320 413 321 414 // Save the data into the database … … 335 428 } else { 336 429 sd.save(flush:true); 430 431 // Also save classification data for this file, if it is present 432 if( filevalue.classification ) { 433 classificationService.storeClassification( filevalue.classification, sd, onProgress ) 434 samplesClassified << sample 435 436 numClassificationFiles++; 437 } 438 439 if( sd.qualityFile ) 440 numQualFiles++; 441 442 numSuccesful++; 337 443 } 338 339 numSuccesful++;340 444 } catch( Exception e ) { 445 e.printStackTrace(); 341 446 errors << "an error occurred while saving " + filevalue.fasta + ": " + e.getMessage() 342 447 } … … 347 452 } 348 453 } 349 350 // Return all files that have not been moved 351 session.processedFiles?.parsed?.success?.each { 352 fileService.delete( it.filename ); 353 } 354 454 455 return [ numSequenceFiles: numSuccesful, numQualFiles: numQualFiles, numClassificationFiles: numClassificationFiles, errors: errors, samplesClassified: samplesClassified.unique() ] 456 } 457 458 def saveRemainingClassificationFiles( def files, Closure onProgress ) { 459 def digitRE = ~/^\d+$/; 460 def errors = []; 461 def samplesClassified = []; 462 def numSuccesful = 0; 463 464 files.findAll { it.key.matches( digitRE ) }.each { file -> 465 def filevalue = file.value; 466 467 // Check if the file is selected 468 if( filevalue.include == "on" ) { 469 if( fileService.fileExists( filevalue.filename ) ) { 470 def sequenceDataId = filevalue.sequenceData; 471 try { 472 if( sequenceDataId.toString().isLong() ) { 473 // Retrieve sequenceData and sample now, because the session will be cleared during import 474 def sequenceData = SequenceData.get( sequenceDataId.toString().toLong() ); 475 def sample = sequenceData.sample; 476 477 if( sequenceData ) { 478 classificationService.removeClassificationForSequenceData( sequenceData ); 479 classificationService.storeClassification( filevalue.filename, sequenceData, onProgress ) 480 samplesClassified << sample; 481 } 482 483 numSuccesful++; 484 } else { 485 errors << "a wrong ID is entered for classification file " + filevalue.filename; 486 } 487 } catch( Exception e ) { 488 e.printStackTrace(); 489 errors << "an error occurred while saving " + filevalue.filename + ": " + e.getMessage() 490 } 491 } 492 } 493 494 // File doesn't need to be included in the system. Delete it from disk. 495 fileService.delete( filevalue.filename ); 496 } 497 498 return [ numExtraClassifications: numSuccesful, errors: errors, samplesClassified: samplesClassified.unique() ] 499 500 } 501 502 /** 503 * Redirects the user back to the start screen with a message about how things went 504 */ 505 def saveMatchedResult = { 506 def processId = params.processId 507 508 def result = session.process[ processId ].result 509 355 510 // Return a message to the user 356 if( numSuccesful == 0 ) {357 flash.error = "None of the files were imported, because "358 359 if( errors.size() > 0 ) {360 errors.each {511 if( result.numTotal == 0 ) { 512 513 if( result.errors.size() > 0 ) { 514 flash.error = "None of the files were imported, because " 515 result.errors.each { 361 516 flash.error += "<br />- " + it 362 517 } 363 518 } else { 364 flash. error = "none of the files were selected for import."519 flash.message = "None of the files were imported, because none of the files were selected for import." 365 520 } 366 521 } else { 367 flash.message = numSuccesful + " files have been added to the system. " 368 369 if( errors.size() > 0 ) { 370 flash.error += errors.size() + " errors occurred during import: " 371 errors.each { 522 flash.message = "" 523 if( result.numSequenceFiles == 1 ) { 524 flash.message += result.numSequenceFiles + " sequence file has been added to the system" 525 } else if( result.numSequenceFiles > 1 ) { 526 flash.message += result.numSequenceFiles + " sequence files have been added to the system" 527 } 528 529 if( result.numQualFiles > 0 || result.numClassificationFiles > 0 ) { 530 flash.message += ", with"; 531 } 532 533 if( result.numQualFiles == 1 ) { 534 flash.message += " 1 quality file" 535 } else if( result.numQualFiles > 1 ) { 536 flash.message += " " + result.numQualFiles + " quality files" 537 } 538 539 if( result.numQualFiles > 0 && result.numClassificationFiles > 0 ) { 540 flash.message += " and"; 541 } 542 543 if( result.numClassificationFiles == 1 ) { 544 flash.message += " 1 classification file" 545 } else if( result.numClassificationFiles > 1 ) { 546 flash.message += " " + result.numClassificationFiles + " classification files" 547 } 548 549 if( flash.message ) 550 flash.message += "." 551 552 if( result.numExtraClassificationFiles == 1 ) { 553 flash.message += result.numExtraClassificationFiles + " additional classification file has been read. "; 554 } else if( result.numExtraClassificationFiles > 1 ) { 555 flash.message += result.numExtraClassificationFiles + " additional classification files have been read. "; 556 } 557 558 if( result.errors.size() > 0 ) { 559 flash.error = "However, " + result.errors.size() + " errors occurred during import: " 560 result.errors.each { 372 561 flash.error += "<br />- " + it 373 562 } … … 375 564 } 376 565 566 // Clear session 567 session.process?.remove( processId ); 568 session.progress?.remove( processId ); 569 570 // Redirect user 377 571 redirect( controller: params.entityType, action: "show", id: params.id ) 378 572 } -
trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy
r58 r59 308 308 if( sd ) 309 309 otherAssaySample.addToSequenceData( sd ); 310 310 311 // Copy all sequence classifications to the new sequenceData 312 Sequence.executeUpdate( "UPDATE Sequence s SET s.sequenceData = :new WHERE s.sequenceData = :old ", [ 'old': dataList[ j ], 'new': sd ] ) 313 311 314 // Remove the old sequencedata object 312 315 this.removeFromSequenceData( dataList[ j ] ); … … 314 317 } 315 318 } 316 317 // Copy all sequence classifications to the new assaysample318 Sequence.executeUpdate( "UPDATE Sequence s SET s.assaySample = :new WHERE s.assaySample = :old ", [ 'old': this, 'new': otherAssaySample ] )319 319 320 320 // Copy run properties … … 344 344 345 345 def numFiles = 0; 346 sequenceData.each { sequenceData -> 346 def data = [] + sequenceData 347 data.each { sequenceData -> 347 348 numFiles += sequenceData.numFiles(); 348 349 … … 350 351 sequenceData.delete(flush:true); 351 352 } 352 353 // Remove all sequence objects referencing this sequenceData object354 Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.assaySample = ?", [this])355 353 356 354 resetStats(); -
trunk/grails-app/domain/nl/tno/massSequencing/Run.groovy
r52 r59 137 137 return hasPrivileges 138 138 } 139 140 public static hasNonWritableAssays( runId, userId ) { 141 def queryNonWritableAssays = Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ? AND NOT EXISTS( FROM a.study.auth auth WHERE auth.canWrite = true AND auth.user.id = ?)", [runId, userId ]) 142 return queryNonWritableAssays ? queryNonWritableAssays[ 0 ] > 0 : false; 143 } 144 145 public int numAssays() { 146 def num = Run.executeQuery( "SELECT COUNT(*) FROM Assay a WHERE EXISTS( FROM a.runs r WHERE r = ? )", [ this ] ); 147 148 if( num ) 149 return num[ 0 ]; 150 } 151 152 public int numAssaySamples() { 153 def num = Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run = ?", [ this ] ); 154 155 if( num ) 156 return num[ 0 ]; 157 } 158 159 public int numReadableAssays( User u ) { 160 def num = Run.executeQuery( "SELECT COUNT(*) FROM Assay a WHERE EXISTS( FROM a.runs r WHERE r = ? ) AND EXISTS( FROM a.study.auth a WHERE a.user = ? AND a.canRead = true )", [ this, u ] ); 161 162 if( num ) 163 return num[ 0 ]; 164 } 165 166 public int numReadableAssaySamples( User u ) { 167 def num = Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run = ? AND EXISTS( FROM s.assay.study.auth a WHERE a.user = ? AND a.canRead = true )", [ this, u ] ); 168 169 if( num ) 170 return num[ 0 ]; 171 } 172 173 public int numWritableAssays( User u ) { 174 def num = Run.executeQuery( "SELECT COUNT(*) FROM Assay a WHERE EXISTS( FROM a.runs r WHERE r = ? ) AND EXISTS( FROM a.study.auth a WHERE a.user = ? AND a.canWrite = true )", [ this, u ] ); 175 176 if( num ) 177 return num[ 0 ]; 178 } 179 180 public int numWritableAssaySamples( User u ) { 181 def num = Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run = ? AND EXISTS( FROM s.assay.study.auth a WHERE a.user = ? AND a.canWrite = true )", [ this, u ] ); 182 183 if( num ) 184 return num[ 0 ]; 185 } 186 139 187 } -
trunk/grails-app/domain/nl/tno/massSequencing/Sequence.groovy
r58 r59 11 11 12 12 // Which sample contains this sequence 13 AssaySample assaySample13 SequenceData sequenceData 14 14 15 15 // Classification of this sequence -
trunk/grails-app/domain/nl/tno/massSequencing/SequenceData.groovy
r49 r59 56 56 fileService.delete( qualityFile, permanentDir ) 57 57 58 59 // Remove all sequence objects referencing this sequenceData object 60 Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.sequenceData = ?", [this]) 61 58 62 // Reset statistics of the assay sample, to ensure the deleted files are removed from statistics 59 63 sample?.resetStats(); -
trunk/grails-app/domain/nl/tno/massSequencing/classification/Classification.groovy
r58 r59 6 6 AssaySample assaySample 7 7 Taxon taxon 8 int occurrences 8 9 // Number of occurrences of this specific taxon (without more specific classification) 10 long unclassified 11 12 // Number of total occurrences of this taxon (including sequences classified at a more specific level) 13 long total 14 15 // Number of occurrences of this taxon with more specific classification 16 public getClassified() { 17 return total - unclassified; 18 } 9 19 10 20 static constraints = { … … 16 26 assaySample index: 'AssaySample_Idx' 17 27 taxon index:'Search_Idx' 18 occurrencesindex:'Search_Idx'28 total index:'Search_Idx' 19 29 version false 20 30 } -
trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy
r58 r59 166 166 */ 167 167 public static Taxon findTaxonByPath( def path, int startLevel = 0 ) { 168 if( path.size() == 0 ) 169 return null; 170 168 171 def leafLevel = path.size() - 1 + startLevel; 169 172 def leafName = path[ -1 ]; -
trunk/grails-app/services/nl/tno/massSequencing/ClassificationService.groovy
r58 r59 8 8 import org.codehaus.groovy.grails.commons.ConfigurationHolder 9 9 import org.hibernate.StatelessSession 10 import org.hibernate.Transaction 10 11 import java.util.zip.* 11 12 import nl.tno.massSequencing.classification.* … … 85 86 file.eachLine { line -> 86 87 numLines++; 87 88 88 89 // Update progress every time 100kB is processed 89 90 bytesProcessed += line.size(); … … 101 102 return [ numLines: numLines, fileSize: file.size() ]; 102 103 } 103 104 /** 105 * Saves the classification of sequences into the database. 106 * @param inputFiles List of maps with groups- and taxonomy files. 107 * @return List of processed files 108 */ 109 public List storeClassification( List inputFiles, Map excelMatches, Closure onProgress ) { 110 def filesStored = []; 111 112 // In order to store classification, we need a .groups and a .taxonomy file with an 113 // equal number of lines 114 inputFiles.each { inputFile -> 115 // Check whether this file has already been processed as a matching file for another file 116 if( filesStored*.filename.contains( inputFile.filename ) ) 117 return; 118 119 // Search for a matching file 120 def searchForType = inputFile.type == 'groups' ? 'taxonomy' : 'groups'; 121 def matchingFile = findMatchingClassificationFile( inputFiles, searchForType, inputFile.numLines, filesStored*.filename ); 122 123 // If no matching file is found, give a message to the user 124 if( !matchingFile ) { 125 inputFile[ 'success' ] = false 126 inputFile[ 'message' ] = "No matching " + searchForType + " file could be found for this file (or a matching file is already processed)." 127 128 filesStored << inputFile; 129 return; 130 } 131 132 println "Handling files: " + inputFile.filename + " - " + matchingFile.filename 133 println excelMatches; 134 135 // Now parse the two matching files 136 def classificationInfo = [] 137 if( inputFile.type == 'groups' ) { 138 classificationInfo = storeClassificationFromFiles( inputFile.filename, matchingFile.filename, excelMatches, onProgress ); 139 } else { 140 classificationInfo = storeClassificationFromFiles( matchingFile.filename, inputFile.filename, excelMatches, onProgress ); 141 } 142 filesStored += classificationInfo; 143 } 144 145 return filesStored; 146 } 147 104 105 /** 106 * Removes all classification sequence objects for a given SequenceData object 107 * @param sequenceData 108 * @return True if the deletion has succeeded 109 */ 110 public boolean removeClassificationForSequenceData( SequenceData sequenceData ) { 111 if( sequenceData ) { 112 Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.sequenceData = ?", [sequenceData] ); 113 return true; 114 } else { 115 return false; 116 } 117 } 118 148 119 /** 149 120 * Store the classification from the given files … … 152 123 * @return 153 124 */ 154 protected List storeClassificationFromFiles( String groupsFilename, String taxonomyFilename, Map excelMatches, Closure onProgress ) { 155 def returnList = [] 125 public Map storeClassification( String taxonomyFilename, SequenceData sequenceData, Closure onProgress ) { 126 def hibernateSession = sessionFactory.getCurrentSession(); 127 hibernateSession.flush(); 128 hibernateSession.clear(); 156 129 157 File groupsFile = fileService.get( groupsFilename );158 130 File taxonomyFile = fileService.get( taxonomyFilename ); 131 132 if( !taxonomyFile ) { 133 return [ success: false, type: 'taxonomy', filename: groupsFilename, message: 'Taxonomy file doesn\'t exist.' ] 134 } 159 135 160 if( !groupsFile ) {161 returnList << [ success: false, type: 'groups', filename: groupsFilename, message: 'Groups file doesn\'t exist.' ]162 }163 if( !taxonomyFile ) {164 returnList << [ success: false, type: 'taxonomy', filename: groupsFilename, message: 'Taxonomy file doesn\'t exist.' ]165 }166 167 if( !groupsFile || !taxonomyFile )168 return returnList;169 170 136 // Open files for reading and create temporary variables 171 137 def taxonomyReader = taxonomyFile.newReader(); 172 def groupsReader = groupsFile.newReader(); 173 138 174 139 def parts, sequenceName, classification, sampleName, taxa, taxon, line, groupsLine 175 176 def classificationScoreRegex = /(\(\d+\))/140 141 def taxonRegex = /"?([^"(]+)"?(\(\d+\))?/ 177 142 def startLevel = 0 // The first level that is present in the file. Sometimes, the root element (level 0) is not mentioned in the file, in that case, this values should be set to 1 178 143 def unclassified = "unclassified" 144 179 145 def i = 0; 180 146 def start = System.currentTimeMillis(); 181 147 def lapTime = start; 182 148 183 149 // Create a stateless session in order to speed up inserts 184 150 StatelessSession statelessSession = sessionFactory.openStatelessSession(); 185 statelessSession.beginTransaction(); 186 187 println "Starting at " + start 188 189 // For each line in the taxonomy file, also read the groups file. If the sequenceNames don't match, 190 // discard the sequence 151 152 // For each line in the taxonomy file, read the sequence and save it 191 153 def discardedSequences = 0; 192 154 def importedSequences = 0; 193 155 long bytesProcessed = 0; 194 while( ( line = taxonomyReader.readLine() ) ) { 195 // Also read a line from the groupsFile 196 groupsLine = groupsReader.readLine(); 197 198 bytesProcessed += line.size() + groupsLine.size(); 199 200 // Find the taxon for this line 201 parts = line.tokenize( "\t" ); 202 203 if( parts.size() != 2 ) { 204 // Skip this line because it is incorrect 205 continue; 156 157 try { 158 while( ( line = taxonomyReader.readLine() ) ) { 159 bytesProcessed += line.size(); 160 161 // Find the taxon for this line 162 parts = line.tokenize( "\t" ); 163 164 if( parts.size() != 2 ) { 165 // Skip this line because it is incorrect 166 continue; 167 } 168 169 sequenceName = parts[ 0 ]; 170 classification = parts[ 1 ]; 171 172 // Split classification to check whether the taxon already exists 173 taxa = classification.tokenize( ';' ).collect { it.replaceAll( taxonRegex, '$1' ) }.findAll { it && it != unclassified } 174 175 // Find taxon or create one if needed 176 taxon = Taxon.findOrCreateTaxonByPath( taxa, startLevel ); 177 178 if( taxon ) { 179 // Create a new sequence record 180 def s = new Sequence() 181 s.name = sequenceName 182 s.classification = taxon 183 s.sequenceData = sequenceData 184 185 statelessSession.insert(s); 186 } else { 187 log.trace( "" + i + " Sequence " + sequenceName + " not imported because it is unclassified." ) 188 } 189 190 if( i % 80 == 0 ) { 191 onProgress( bytesProcessed ); 192 bytesProcessed = 0; 193 } 194 195 importedSequences++; 196 197 i++; 206 198 } 207 199 208 sequenceName = parts[ 0 ]; 209 classification = parts[ 1 ]; 200 } catch( Exception e ) { 201 throw e; 202 } finally { 203 statelessSession.close(); 204 taxonomyReader.close(); 205 } 206 207 onProgress( bytesProcessed ); 208 209 return [ filename: taxonomyFilename, type: 'taxonomy', success: true, importedSequences: importedSequences, discardedSequences: discardedSequences ] 210 } 211 212 /** 213 * Updates the Classification table with data computed from the raw sequences for the given assay sample 214 * @param assaySample 215 */ 216 public void updateClassificationForAssaySample( AssaySample assaySample ) { 217 if( !assaySample ) 218 return; 210 219 211 if( !groupsLine ) { 212 // Skip this line because the groups file doesn't contain a new line 213 discardedSequences++; 214 continue 215 } 216 217 // Find the sequence name and sample name in the groups file 218 parts = groupsLine.tokenize( "\t" ); 219 if( parts.size() != 2 ) { 220 // Skip this line because the groups file line is incorrect 221 discardedSequences++; 222 continue; 223 } 220 updateClassificationForAssaySamples( [ assaySample ] ); 221 } 222 223 /** 224 * Updates the Classification table with data computed from the raw sequences for the given assay sample 225 * @param assaySample 226 */ 227 public void updateClassificationForAssaySamples( List assaySamples ) { 228 if( !assaySamples ) 229 return; 224 230 225 if( parts[ 0 ] != sequenceName ) { 226 log.trace( "Sequence names in taxonomy and groups files don't match: " + sequenceName + " " + parts[ 0 ] ) 227 discardedSequences++; 228 continue; 229 } 230 sampleName = parts[ 1 ]; 231 232 // Split classification to check whether the taxon already exists 233 taxa = classification.replaceAll( classificationScoreRegex, '' ).tokenize( ';' ).findAll { it } 234 235 // Find taxon or create one if needed 236 taxon = Taxon.findOrCreateTaxonByPath( taxa, startLevel ); 237 238 // Determine assaySample 239 def assaySample = excelMatches[ sampleName ] 240 241 if( !assaySample ) { 242 log.trace( "Sample name from GROUPS file is not found in excel file: " + sampleName ) 243 discardedSequences++; 244 continue; 245 } 246 247 // Create a new sequence record 248 def s = new Sequence() 249 s.name = sequenceName 250 s.classification = taxon 251 s.assaySample = assaySample 252 253 statelessSession.insert(s); 254 255 //println "Sequence " + sequenceName + " / classification " + classification + " / sample: " + assaySample 256 257 if( i % 100 == 0 ) { 258 onProgress( bytesProcessed ); 259 bytesProcessed = 0; 260 } 261 262 importedSequences++; 263 264 i++; 265 } 266 267 onProgress( bytesProcessed ); 268 269 taxonomyReader.close(); 270 groupsReader.close(); 271 272 returnList << [ filename: taxonomyFilename, type: 'taxonomy', success: true, importedSequences: importedSequences, discardedSequences: discardedSequences ] 273 returnList << [ filename: groupsFilename, type: 'groups', success: true, importedSequences: importedSequences, discardedSequences: discardedSequences ] 274 275 return returnList; 276 } 277 231 // Clear hibernate session for otherwise the session will be out of sync with the 232 // database 233 def hibernateSession = sessionFactory.getCurrentSession(); 234 hibernateSession.flush(); 235 hibernateSession.clear(); 236 237 // Remove all Classifications for the given assaySample 238 Classification.executeUpdate( "DELETE FROM Classification WHERE assaySample IN (:assaySamples)", [ 'assaySamples': assaySamples ] ); 239 240 // Update classification table with data from sequences 241 242 // Unfortunately, Hibernate (through HQL) can't handle bulk insertion if a table has a 243 // composite id (Classification). See http://opensource.atlassian.com/projects/hibernate/browse/HHH-3434 244 // For that reason we execute plain SQL 245 def connection = sessionFactory.currentSession.connection() 246 def statement = connection.createStatement(); 247 statement.executeUpdate( connection.nativeSQL( """ 248 INSERT INTO classification (assay_sample_id, taxon_id, unclassified, total) 249 SELECT sa.id, s.classification_id, count( * ), 250 ( 251 SELECT count( * ) 252 FROM sequence s2 253 LEFT JOIN taxonomy t2 ON s2.classification_id = t2.id 254 LEFT JOIN sequence_data sd2 ON s2.sequence_data_id = sd2.id 255 LEFT JOIN assay_sample sa2 ON sd2.sample_id = sa2.id 256 WHERE sa2.id = sa.id AND t2.lft >= t.lft AND t2.rgt <= t.rgt 257 ) 258 FROM sequence s 259 LEFT JOIN taxonomy t ON s.classification_id = t.id 260 LEFT JOIN sequence_data sd ON s.sequence_data_id = sd.id 261 LEFT JOIN assay_sample sa ON sd.sample_id = sa.id 262 WHERE sa.id IN (""" + assaySamples.id.join( ", " ) + """ ) 263 GROUP BY sa.id, s.classification_id 264 """ ) ); 265 } 266 267 278 268 /** 279 269 * Find a file that matches the parameters and is not already stored -
trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy
r58 r59 13 13 def fuzzySearchService 14 14 def sampleExcelService 15 def classificationService 15 16 def excelService 16 17 … … 242 243 def fastas = parsedFiles.findAll { it.type == "fasta" } 243 244 def quals = parsedFiles.findAll { it.type == "qual" } 245 def classifications = parsedFiles.findAll { it.type == "taxonomy" } 244 246 def excels = parsedFiles.findAll { it.type == "excel" } 245 247 … … 263 265 // Determine feasible quals (based on number of sequences ) 264 266 def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences } 267 def feasibleClassifications = classifications.findAll { it.numLines == fastaFile.numSequences } 265 268 266 269 // Best matching qual file … … 271 274 qual = feasibleQuals[ qualIdx ]; 272 275 276 // Best matching classification file 277 def classificationIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.taxonomy', feasibleClassifications.originalfilename ); 278 279 def classification = null 280 if( classificationIdx != null ) 281 classification = feasibleClassifications[ classificationIdx ]; 282 273 283 // Best matching sample 274 284 def assaySample = null … … 298 308 fasta: fastaFile, 299 309 feasibleQuals: feasibleQuals, 310 feasibleClassifications: feasibleClassifications, 300 311 qual: qual, 312 classification: classification, 301 313 assaySample: assaySample, 302 314 checked: checked … … 427 439 if( fileService.fileExists( fastaFile ) ) { 428 440 // Lookup the original filename 429 def fastaData = processedFiles .parsed.success.find { it.filename == fastaFile };441 def fastaData = processedFiles?.parsed?.success?.find { it.filename == fastaFile }; 430 442 if( fastaData ) { 431 443 returnStructure.fasta = fileService.moveFileToUploadDir( fileService.get( fastaFile ), fastaData.originalfilename, permanentDirectory ); … … 444 456 445 457 if( qualData ) { 446 returnStructure.qual = fileService.moveFileToUploadDir( fileService.get(qualFile 458 returnStructure.qual = fileService.moveFileToUploadDir( fileService.get(qualFile), qualData.originalfilename, permanentDirectory ); 447 459 returnStructure.avgQuality = qualData.avgQuality 448 460 } else { … … 689 701 } 690 702 703 // Update classification summary 704 classificationService.updateClassificationForAssaySamples( assaySamples ); 705 691 706 return numFiles; 692 707 } -
trunk/grails-app/views/assay/_addFilesDialog.gsp
r58 r59 2 2 <h2>Upload sequence files</h2> 3 3 4 <g:form name="addFiles" controller="import" action=" showProcessScreen" id="${assay.id}">4 <g:form name="addFiles" controller="import" action="parseUploadedFiles" id="${assay.id}"> 5 5 <input type="hidden" name="entityType" value="assay" /> 6 6 <p> -
trunk/grails-app/views/assay/index.gsp
r52 r59 14 14 <form id="assayForm"> 15 15 </form> 16 <table id="assays" class="paginate ">16 <table id="assays" class="paginate_serverside" rel="<g:createLink controller="assay" action="showAssayList" />"> 17 17 <thead> 18 18 <tr> … … 27 27 </thead> 28 28 <tbody> 29 <g:each in="${studies}" var="study"> 30 <g:if test="${study.assays != null && study.assays.size() > 0}"> 31 <g:each in="${study.assays}" var="assay"> 32 <tr> 33 <td><g:checkBox name="ids" value="${assay.id}" checked="${false}" onClick="updateCheckAll(this);" /></td> 34 35 <td><g:link title="View assay" controller="assay" action="show" id="${assay.id}">${assay.name}</g:link></td> 36 <td><a href="${study.viewUrl()}">${study.name}</a></td> 37 <td>${assay.assaySamples?.size()}</td> 38 <td> 39 <g:if test="${assay.assaySamples?.size()}"> 40 <g:formatNumber number="${assay.numSequences() / assay.assaySamples?.size()}" format="###,###,##0"/> 41 </g:if> 42 <g:else> 43 - 44 </g:else> 45 </td> 46 <td><g:link title="View assay" controller="assay" action="show" id="${assay.id}"><img src="${fam.icon( name: 'application_form_magnify' )}" alt="View assay" title="View assay" /></g:link></td> 47 <td> 48 <g:if test="${assay.numSequences() > 0}"> 49 <g:link title="Sequence length histogram" controller="assay" action="sequenceLengthHistogram" id="${assay.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link> 50 </g:if> 51 <g:else> 52 <img src="${fam.icon( name: 'chart_bar' )}" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." /> 53 </g:else> 54 </td> 55 56 </tr> 57 </g:each> 58 </g:if> 59 </g:each> 29 <tr> 30 <td colspan="7"> 31 Retrieving data from server. 32 </td> 33 </tr> 60 34 </tbody> 61 35 </table> -
trunk/grails-app/views/assay/show.gsp
r57 r59 66 66 <g:else> 67 67 <form id="sampleForm"><input type="hidden" name="assayId" value="${assay.id}" /></form> 68 <table class="paginate " id="samples">68 <table class="paginate_serverside" rel="<g:createLink controller="assay" action="showSampleData" id="${assay.id}" />" id="samples"> 69 69 <thead> 70 70 <tr> … … 80 80 </thead> 81 81 <tbody> 82 <% def assaySamples = assay.assaySamples.toList().sort { it.sample.name } %> 83 <g:each in="${assaySamples}" var="assaySample"> 84 <tr> 85 <td><g:checkBox name="ids" value="${assaySample.id}" checked="${false}" onClick="updateCheckAll(this);" /></td> 86 <td><a title="Show assay" href="#" onClick="showSample(${assaySample.id}, 'assay'); return false;">${assaySample.sample.name}</a></td> 87 <td>${assaySample.run?.name}</td> 88 <td>${assaySample.fwMidName}</td> 89 <td> 90 <g:if test="${assaySample.numSequenceFiles() > 0}"> 91 <g:formatNumber number="${assaySample.numSequences()}" format="###,###,##0" /> 92 </g:if> 93 <g:else> 94 - 95 </g:else> 96 </td> 97 <td> 98 <g:if test="${assaySample.numQualityFiles() > 0}"> 99 <g:formatNumber number="${assaySample.numQualScores()}" format="###,###,##0" /> 100 </g:if> 101 <g:else> 102 - 103 </g:else> 104 </td> 105 <td class="button"> 106 <g:if test="${!assaySample.assay.study.canWrite(session.user)}"> 107 <img src="${fam.icon(name: 'pencil')}" class="disabled" title="You can't edit this sample because you don't have sufficient privileges." /> 108 </g:if> 109 <g:else> 110 <a onClick="showEditSampleDialog(${assaySample.id}, 'assay', ${assay.id}); return false;" href="#" title="Edit sample data"><img title="Edit sample data" src="${fam.icon(name: 'pencil')}" /></a> 111 </g:else> 112 </td> 113 <td class="button"> 114 <g:if test="${assaySample.numSequences() > 0}"> 115 <g:link controller="assaySample" action="sequenceLengthHistogram" id="${assaySample.id}" title="Sequence length histogram"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link> 116 </g:if> 117 <g:else> 118 <img src="${fam.icon( name: 'chart_bar' )}" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." /> 119 </g:else> 120 </td> 121 </tr> 122 </g:each> 82 <tr> 83 <td colspan="8"> 84 Loading data from server 85 </td> 86 </tr> 87 123 88 </tbody> 124 89 </table> -
trunk/grails-app/views/assaySample/sequenceLengthHistogram.gsp
r52 r59 2 2 <head> 3 3 <meta name="layout" content="main" /> 4 <title>Sequence length histogram for sample ${assaySample.sample.name} | Mass Sequencing | dbNP</title>4 <title>Sequence length histogram for ${title} | Mass Sequencing | dbNP</title> 5 5 </head> 6 6 <body> 7 <h1>Sequence length histogram for sample ${assaySample.sample.name} (${assaySample.assay.study.name} / ${assaySample.assay.name})</h1>7 <h1>Sequence length histogram for ${title}</h1> 8 8 <p> 9 Total number of sequences: <g:formatNumber number="${ assaySample.numSequences()}" format="###,###,##0" />9 Total number of sequences: <g:formatNumber number="${numSequences}" format="###,###,##0" /> 10 10 </p> 11 11 <div id="placeholder" style="width:600px;height:300px"></div> -
trunk/grails-app/views/import/parseUploadResult.gsp
r58 r59 27 27 28 28 <h2>Sequence files</h2> 29 <g:if test="${matchedFiles.size() > 0}"> 30 <p> 31 Match each sequencefile with the qualityfile and the sample it belongs to. Samples are already chosen for some files based 32 on the given excelsheet or the filenames. 33 </p> 34 <p> 35 By unchecking a sequence file you can exclude the file from being used. 36 </p> 37 38 <% 39 def sortedSamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.sort() { a,b -> a.sample.name <=> b.sample.name } 40 %> 41 <g:form name="saveProcessedFiles" action="saveProcessedFiles" id="${entity.id}" params="${[controller: controller]}"> 42 <input type="hidden" name="entityType" value="${entityType}" /> 43 <table> 44 <thead> 45 <tr> 46 <th></th> 47 <th>Fasta file</th> 48 <th># sequences</th> 49 <th>Qual file</th> 50 <th>Sample</th> 51 </tr> 52 </thead> 29 <g:form name="saveProcessedFiles" action="saveMatchedFiles" id="${entity.id}" params="${[controller: controller]}"> 30 <input type="hidden" name="entityType" value="${entityType}" /> 31 <input type="hidden" name="processId" value="${processId}" /> 32 33 <g:if test="${matchedFiles.size() > 0}"> 34 <p> 35 Match each sequencefile with the qualityfile, classification file and the sample it belongs to. Samples are already chosen for some files based 36 on the given excelsheet or the filenames. 37 </p> 38 <p> 39 By unchecking a sequence file you can exclude the file from being used. 40 </p> 41 42 <% 43 def sortedSamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.sort() { a,b -> a.sample.name <=> b.sample.name } 44 %> 45 <table class="importProcessedFiles" cellspacing="0"> 46 <thead> 47 <tr> 48 <th></th> 49 <th>Fasta file</th> 50 <th># sequences</th> 51 <th>Sample</th> 52 </tr> 53 </thead> 54 55 <g:each in="${matchedFiles}" var="file" status="i"> 56 <tr class="${ i % 2 == 0 ? 'even' : 'odd' }"> 57 <td> 58 <g:hiddenField name="file.${i}.fasta" value="${file.fasta.filename}" /> 59 <g:checkBox name="file.${i}.include" value="${file.checked}" /> 60 </td> 61 <td class="name" valign="center">${file.fasta.originalfilename}</td> 62 <td class="sequences">${file.fasta.numSequences}</td> 63 <td nowrap> 64 <g:if test="${sortedSamples.size()}"> 65 <select name="file.${i}.assaySample" onChange="showSampleDataWarning(this, ${i});"> 66 <g:each in="${sortedSamples}" var="assaySample"> 67 <option 68 <g:if test="${file.assaySample?.id == assaySample.id}"> 69 <g:set var="selectedAssaySample" value="${assaySample}" /> 70 selected="selected" 71 </g:if> 72 value="${assaySample.id}" class="<g:if test="${assaySample?.sequenceData?.size() > 0}">containsdata</g:if><g:else>nodata</g:else>">${assaySample.sample.name}</option> 73 </g:each> 74 </select> 75 <span id="warningSampleContainsData_${i}" class="warningSampleContainsData" <g:if test="${selectedAssaySample?.sequenceData?.size() > 0}">style="display: inline;"</g:if>> 76 <img src="${fam.icon( name: "exclamation" ) }" title="This sample already contains sequences. Adding sequences doesn't replace the old ones." /> 77 </span> 78 </g:if> 79 <g:else> 80 No samples available. 81 </g:else> 82 </td> 83 </tr> 84 85 <tr class="${ i % 2 == 0 ? 'even' : 'odd' }"> 86 <td></td> 87 <td></td> 88 <td>Quality file</td> 89 <td> 90 <g:if test="${file.feasibleQuals?.size()}"> 91 <g:select name="file.${i}.qual" from="${file.feasibleQuals}" optionKey="filename" optionValue="originalfilename" value="${file.qual?.filename}" noSelection="['': '- No qual file -']"/> 92 </g:if> 93 <g:else> 94 <select name="" disabled="disabled"><option value="">No qual file available</option></select> 95 </g:else> 96 </td> 97 </tr> 98 <tr class="${ i % 2 == 0 ? 'even' : 'odd' }"> 99 <td></td> 100 <td></td> 101 <td>Classification file</td> 102 <td> 103 <g:if test="${file.feasibleClassifications?.size()}"> 104 <g:select name="file.${i}.classification" from="${file.feasibleClassifications}" optionKey="filename" optionValue="originalfilename" value="${file.classification?.filename}" noSelection="['': '- No classification -']"/> 105 </g:if> 106 <g:else> 107 <select name="" disabled="disabled"><option value="">No classification available</option></select> 108 </g:else> 109 </td> 110 </tr> 111 </g:each> 53 112 54 <g:each in="${matchedFiles}" var="file" status="i"> 55 <tr> 56 <td> 57 <g:hiddenField name="file.${i}.fasta" value="${file.fasta.filename}" /> 58 <g:checkBox name="file.${i}.include" value="${file.checked}" /> 59 </td> 60 <td class="name">${file.fasta.originalfilename}</td> 61 <td class="sequences">${file.fasta.numSequences}</td> 62 <td> 63 <g:if test="${file.feasibleQuals?.size()}"> 64 <g:select name="file.${i}.qual" from="${file.feasibleQuals}" optionKey="filename" optionValue="originalfilename" value="${file.qual?.filename}" noSelection="['': '- No qual file -']"/> 65 </g:if> 66 <g:else> 67 No qual file available 68 </g:else> 69 </td> 70 <td nowrap> 71 <g:if test="${sortedSamples.size()}"> 72 <select name="file.${i}.assaySample" onChange="showSampleDataWarning(this, ${i});"> 73 <g:each in="${sortedSamples}" var="assaySample"> 74 <option 75 <g:if test="${file.assaySample?.id == assaySample.id}"> 76 <g:set var="selectedAssaySample" value="${assaySample}" /> 77 selected="selected" 78 </g:if> 79 value="${assaySample.id}" class="<g:if test="${assaySample?.sequenceData?.size() > 0}">containsdata</g:if><g:else>nodata</g:else>">${assaySample.sample.name}</option> 80 </g:each> 81 </select> 82 <span id="warningSampleContainsData_${i}" class="warningSampleContainsData" <g:if test="${selectedAssaySample?.sequenceData?.size() > 0}">style="display: inline;"</g:if>> 83 <img src="${fam.icon( name: "exclamation" ) }" title="This sample already contains sequences. Adding sequences doesn't replace the old ones." /> 84 </span> 85 </g:if> 86 <g:else> 87 No samples available. 88 </g:else> 89 </td> 90 </tr> 91 </g:each> 113 </table> 92 114 93 </table> 115 </g:if> 116 <g:else> 117 <p> 118 ${parsedFiles.success?.size()} files were successfully uploaded, but no FASTA file could be determined. Please 119 upload at least one FASTA file if sequences should be added. 120 </p> 121 </g:else> 94 122 95 <input type="submit" value="Continue"> 96 </g:form> 97 98 </g:if> 99 <g:else> 100 <p> 101 ${parsedFiles.success?.size()} files were successfully uploaded, but no FASTA file could be determined. Please 102 upload at least one FASTA file if sequences should be added. 103 </p> 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 123 <g:if test="${remainingClassificationFiles}"> 124 <h2>Clasification files</h2> 125 <p> 126 The following classification files could not be matched with an uploaded sequence file. Please choose which file 127 belongs to which sample and data file. 128 </p> 129 <p> 130 <b>N.B.</b> Existing classification data wil be removed for the selected file. 131 </p> 132 <table class="importProcessedFiles"> 133 <thead> 134 <tr> 135 <th></th> 136 <th>Classification file</th> 137 <th># sequences</th> 138 <th>Matching existing file</th> 139 </tr> 140 </thead> 141 142 <g:each in="${remainingClassificationFiles}" var="file" status="i"> 143 <tr class="${ i % 2 == 0 ? 'even' : 'odd' }"> 144 <td> 145 <g:hiddenField name="remainingClassification.${i}.filename" value="${file.filename}" /> 146 <g:checkBox name="remainingClassification.${i}.include" value="${file.feasibleSequenceData as Boolean}" disabled="${!file.feasibleSequenceData}" /> 147 </td> 148 <td class="name" valign="center">${file.originalfilename}</td> 149 <td class="sequences">${file.numLines}</td> 150 <td nowrap> 151 <g:if test="${file.feasibleSequenceData}"> 152 <select name="remainingClassification.${i}.sequenceData"> 153 <g:each in="${file.feasibleSequenceData}" var="sequenceData"> 154 <option 155 <g:if test="${file.sequenceData?.id == sequenceData.id}"> 156 selected="selected" 157 </g:if> 158 value="${sequenceData.id}"> 159 ${sequenceData.sample.sample.name} - ${sequenceData.sequenceFile}</option> 160 </g:each> 161 </select> 162 </g:if> 163 <g:else> 164 No samples available for this classification file 165 </g:else> 166 </td> 167 </tr> 168 </g:each> 169 170 </table> 171 </g:if> 172 173 <input type="submit" value="Continue"> 174 </g:form> 175 135 176 136 177 <g:if test="${parsedFiles.failure?.size()}"> … … 151 192 152 193 <p> 153 <g:link controller="import" action="returnWithoutSaving" params="[ entityType: entityType, id: entity.id]">Return to ${entityType}</g:link>194 <g:link controller="import" action="returnWithoutSaving" params="[processId: processId, entityType: entityType, id: entity.id]">Return to ${entityType}</g:link> 154 195 </p> 155 196 </body> -
trunk/grails-app/views/import/showProcessScreen.gsp
r58 r59 5 5 6 6 <script type="text/javascript"> 7 var progressInterval; 8 7 9 $(function() { 8 10 $( "#wait_dialog" ).dialog({ … … 23 25 }); 24 26 25 var progressInterval = setInterval(updateProgress, 250);27 progressInterval = setTimeout(updateProgress, 250); 26 28 27 29 // Call processing URL 28 30 $.ajax({ 29 31 type: 'POST', 30 url: " <g:createLink controller="import" action="process" id="${entityId}" />",31 data: " entityType=${entityType}",32 url: "${processUrl.encodeAsJavaScript()}", 33 data: "${processParameters*.toString().join("&")}", 32 34 success: function(data) { 33 35 34 36 // Stop update progress bar 35 clear Interval( progressInterval );37 clearTimeout( progressInterval ); 36 38 37 window.location.replace( "${ url.encodeAsJavaScript()}" );39 window.location.replace( "${finishUrl.encodeAsJavaScript()}" ); 38 40 }, 39 41 error: function(xhr, textStatus, errorThrown) { … … 41 43 // Stop update progress bar (but update it for the last time) 42 44 updateProgress() 43 clear Interval( progressInterval );45 clearTimeout( progressInterval ); 44 46 45 47 alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() ); 46 //window.location.replace( "<g:createLink controller="${entityType}" action="show" id="${entityId}" />" );48 window.location.replace( "${errorUrl?.encodeAsJavaScript()}" ); 47 49 } 48 50 }); … … 53 55 $.ajax({ 54 56 type: "GET", 55 url: " <g:createLink controller="import" action="getProgress" />",57 url: "${progressUrl?.encodeAsJavaScript()}", 56 58 dataType: "json", 57 59 success: function(progress) { … … 63 65 64 66 $("#progressbar").progressbar("value", progress.stepProgress / progress.stepTotal * 100 ); 67 68 // Make sure the next progress will be retrieved in 250 ms 69 progressInterval = setTimeout(updateProgress, 250); 65 70 } 66 71 }); -
trunk/grails-app/views/layouts/main.gsp
r52 r59 47 47 <!-- Last full synchronization: ${lastSynchronized} --> 48 48 </g:if> 49 <g:if test="${flash.message}"> 50 <p class="message">${flash.message.toString()}</p> 51 </g:if> 49 52 <g:if test="${flash.error}"> 50 <p class="error">${flash.error.toString().encodeAsHTML()}</p> 51 </g:if> 52 <g:if test="${flash.message}"> 53 <p class="message">${flash.message.toString().encodeAsHTML()}</p> 53 <p class="error">${flash.error.toString()}</p> 54 54 </g:if> 55 55 -
trunk/grails-app/views/run/_addFilesDialog.gsp
r58 r59 2 2 <h2>Upload sequence files</h2> 3 3 4 <g:form name="addFiles" controller="import" action=" showProcessScreen" id="${run.id}">4 <g:form name="addFiles" controller="import" action="parseUploadedFiles" id="${run.id}"> 5 5 <input type="hidden" name="entityType" value="run" /> 6 6 <p> -
trunk/grails-app/views/run/index.gsp
r52 r59 18 18 <body> 19 19 <h1>List of mass sequencing runs</h1> 20 <g:if test="${ runs.size()== 0}">20 <g:if test="${numRuns == 0}"> 21 21 <p> 22 22 Currently there are no runs available in the database. Click <a href="#" onClick="showAddRunDialog(); return false;">here</a> to add a new run. … … 26 26 <form id="runForm"> 27 27 </form> 28 <table id="runs" class="paginate ">28 <table id="runs" class="paginate_serverside" rel="<g:createLink controller="run" action="showRunList" />"> 29 29 <thead> 30 30 <tr> … … 39 39 </thead> 40 40 <tbody> 41 <g:each in="${runs}" var="run"> 42 <tr> 43 <td><g:checkBox name="ids" value="${run.id}" checked="${false}" onClick="updateCheckAll(this);" /></td> 44 <td><g:link title="View run" controller="run" action="show" id="${run.id}">${run.name}</g:link></td> 45 <td>${run.assaySamples?.size()}</td> 46 <td><g:formatNumber number="${run.numSequences()}" format="###,###,##0" /></td> 47 <td><g:link title="View run" controller="run" action="show" id="${run.id}"><img src="${fam.icon( name: 'application_form_magnify' )}" alt="View run" title="View run" /></g:link></td> 48 <td> 49 <g:if test="${run.deletable(user)}"> 50 <g:link title="Delete run" onClick="return confirm( 'Are you sure you want to delete this run?' );" controller="run" action="deleteRun" id="${run.id}"><img src="${fam.icon( name: 'delete' )}" alt="Delete run" title="Delete run" /></g:link> 51 </g:if> 52 <g:else> 53 <img src="${fam.icon( name: 'delete' )}" class="disabled" alt="Run can not be deleted because data is associated with it." title="Run can not be deleted because data is associated with it." /> 54 </g:else> 55 </td> 56 <td> 57 <g:if test="${run.numSequences() > 0}"> 58 <g:link controller="run" action="sequenceLengthHistogram" title="Sequence length histogram" id="${run.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link> 59 </g:if> 60 <g:else> 61 <img src="${fam.icon( name: 'chart_bar' )}" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." /> 62 </g:else> 63 </td> 64 </tr> 65 </g:each> 41 <td colspan="7"> 42 Loading data from server 43 </td> 66 44 </tbody> 67 45 </table> -
trunk/grails-app/views/run/show.gsp
r52 r59 61 61 </div> 62 62 <div class="blok_data"> 63 <label># assays</label>: ${run.assays?.size()} 64 <% def numHidden = run.assays?.findAll { !it.study.canRead( session.user ) }.size() ; %> 65 <g:if test="${numHidden}"> 66 (${numHidden} 67 <a href="#" onClick="alert( '${numHidden} assay(s) from this run are hidden because you don\'t have the right permissions to view them.' ); return false;"> 63 <% 64 def numAssays = run.numAssays(); 65 def numReadableAssays = run.numReadableAssays( session.user ); 66 def numHiddenAssays = numAssays - numReadableAssays; 67 %> 68 <label># assays</label>: ${numAssays} 69 <g:if test="${numHiddenAssays}"> 70 (${numHiddenAssays} 71 <a href="#" onClick="alert( '${numHiddenAssays} assay(s) from this run are hidden because you don\'t have the right permissions to view them.' ); return false;"> 68 72 hidden</a>) 69 73 </g:if> 70 74 <br /> 71 75 72 <label># samples</label>: ${run.assaySamples?.size()} 73 <% numHidden = run.assaySamples?.findAll { !it.assay?.study.canRead( session.user ) }.size() ; %> 74 <g:if test="${numHidden}"> 75 (${numHidden} 76 <a href="#" onClick="alert( '${numHidden} sample(s) from this run are hidden because you don\'t have the right permissions to view them.' ); return false;"> 76 <% 77 def numAssaySamples = run.numAssaySamples(); 78 def numReadableAssaySamples = run.numReadableAssaySamples( session.user ); 79 def numWritableAssaySamples = run.numWritableAssaySamples( session.user ); 80 def numHiddenAssaySamples = numAssaySamples - numReadableAssaySamples; 81 %> 82 <label># samples</label>: ${numAssaySamples} 83 <g:if test="${numHiddenAssaySamples}"> 84 (${numHiddenAssaySamples} 85 <a href="#" onClick="alert( '${numHiddenAssaySamples} sample(s) from this run are hidden because you don\'t have the right permissions to view them.' ); return false;"> 77 86 hidden</a>) 78 87 </g:if> … … 87 96 <!-- Samples --> 88 97 <h2>Samples</h2> 89 <% def assaySamples = run.assaySamples ? run.assaySamples.findAll { it.assay?.study.canRead( session.user ) }.toList().sort { it.sample.name } : []; %> 90 <g:if test="${assaySamples.size() == 0}"> 98 <g:if test="${numReadableAssaySamples == 0}"> 91 99 <p>No samples found in run.</p> 92 100 </g:if> 93 101 <g:else> 94 102 <form id="sampleForm"><input type="hidden" name="runId" value="${run.id}" /></form> 95 <table class="paginate " id="samples">103 <table class="paginate_serverside" rel="<g:createLink controller="run" action="showSampleData" id="${run.id}" />" id="samples"> 96 104 <thead> 97 105 <tr> … … 109 117 </thead> 110 118 <tbody> 111 <g:each in="${assaySamples}" var="assaySample"> 112 <tr> 113 <td><g:checkBox name="ids" value="${assaySample.id}" checked="${false}" onClick="updateCheckAll(this);" /></td> 114 <td><a title="Show sample details" href="#" onClick="showSample(${assaySample.id}, 'run'); return false;">${assaySample.sample.name}</a></td> 115 <td>${assaySample.assay.study.name}</td> 116 <td>${assaySample.assay.name}</td> 117 <td>${assaySample.fwMidName}</td> 118 <td> 119 <g:if test="${assaySample.numSequenceFiles() > 0}"> 120 <g:formatNumber number="${assaySample.numSequences()}" format="###,###,##0" /> 121 </g:if> 122 <g:else> 123 - 124 </g:else> 125 </td> 126 <td> 127 <g:if test="${assaySample.numQualityFiles() > 0}"> 128 <g:formatNumber number="${assaySample.numQualScores()}" format="###,###,##0" /> 129 </g:if> 130 <g:else> 131 - 132 </g:else> 133 </td> 134 <td class="button"> 135 <g:if test="${!assaySample.assay.study.canWrite(session.user)}"> 136 <img src="${fam.icon(name: 'pencil')}" class="disabled" title="You can't edit this sample because you don't have sufficient privileges." /> 137 </g:if> 138 <g:else> 139 <a title="Edit sample" onClick="showEditSampleDialog(${assaySample.id}, 'run', ${run.id});" href="#"><img title="Edit sample" src="${fam.icon(name: 'pencil')}" /></a> 140 </g:else> 141 </td> 142 <td class="button"> 143 <g:if test="${!assaySample.assay.study.canWrite(session.user)}"> 144 <img src="${fam.icon(name: 'application_delete')}" class="disabled" title="You can't remove this sample because you don't have sufficient privileges." /> 145 </g:if> 146 <g:else> 147 <g:link title="Remove sample from run" onClick="return confirm( 'Are you sure you want to remove the selected sample from this run?' );" controller="run" action="removeSample" id="${run.id}" params="${[assaySampleId: assaySample.id]}" ><img title="Remove sample from run" src="${fam.icon(name: 'application_delete')}" /></g:link> 148 </g:else> 149 </td> 150 <td class="button"> 151 <g:if test="${assaySample.numSequences() > 0}"> 152 <g:link controller="assaySample" action="sequenceLengthHistogram" id="${assaySample.id}" title="Sequence length histogram"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link> 153 </g:if> 154 <g:else> 155 <img src="${fam.icon( name: 'chart_bar' )}" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." /> 156 </g:else> 157 </td> 158 </tr> 159 </g:each> 119 <tr> 120 <td colspan="10" class="dataTables_empty">Loading data from server</td> 121 </tr> 160 122 </tbody> 161 123 </table> … … 164 126 <g:if test="${editable}"> 165 127 <p class="options multiple"> 166 <% def writableAssaySamples = assaySamples.findAll { it.assay.study.canWrite( session.user ) } %>167 128 <a class="addAssociation" onClick="showAddSamplesDialog(); return false;" href="#">Add samples</a><br /> 168 129 169 <g:if test="${ writableAssaySamples.size()> 0}">130 <g:if test="${numWritableAssaySamples > 0}"> 170 131 <a class="editAssociation" onClick="showEnterTagsDialog(); return false;" href="#">Edit sample data</a> 171 132 </g:if> … … 176 137 <br /> 177 138 178 <g:if test="${ writableAssaySamples.size()> 0}">139 <g:if test="${numWritableAssaySamples > 0}"> 179 140 <a class="removeAssociation" href="#" onClick="if( confirm( 'Are you sure you want to remove selected samples from this run?' ) ) { submitPaginatedForm( $( '#sampleForm' ), '<g:createLink controller="run" action="removeSamples" />', '#samples', 'Please select one or more samples to remove from this run' ); } return false;">Remove selected samples</a> 180 141 </g:if> … … 184 145 </p> 185 146 <p class="options multiple"> 186 <g:if test="${ writableAssaySamples.size() == 0 || !run.assays?.size()}">147 <g:if test="${numWritableAssaySamples == 0 || numWritableAssays == 0 }"> 187 148 <a class="addSequences disabled" onClick="return false;" href="#">Add sequence files</a> 188 149 </g:if> … … 193 154 <br /> 194 155 195 <g:if test="${ writableAssaySamples.size()> 0 && run.numFiles() > 0 }">156 <g:if test="${numWritableAssaySamples > 0 && run.numFiles() > 0 }"> 196 157 <a class="removeSequences" href="#" onClick="if( confirm( 'Are you sure you want to remove all sequence data from this run? Only sequences are removed from samples you have write access to.' ) ) { submitPaginatedForm( $( '#sampleForm' ), '<g:createLink controller="run" action="deleteSequenceData" />', '#samples', 'Please select one or more samples to remove sequences from.' ); } return false;">Delete selected sequences</a> 197 158 </g:if> … … 202 163 203 164 <p class="options multiple"> 204 <g:if test="${ assaySamples.size()> 0}">165 <g:if test="${numReadableAssaySamples > 0}"> 205 166 <a class="fasta" href="#" onClick="submitPaginatedForm( $( '#sampleForm' ), '<g:createLink controller="assaySample" action="exportAsFasta" />', '#samples', 'Please select one or more samples to export' ); return false;">Export as fasta</a><br /> 206 167 </g:if> … … 212 173 <div style="clear: both;"></div> 213 174 214 <g:if test="${ writableAssaySamples.size()> 0}">175 <g:if test="${numWritableAssaySamples > 0}"> 215 176 <g:render template="enterTagsDialog" model="[run: run, writableAssaySamples: writableAssaySamples]" /> 216 177 <g:render template="addFilesDialog" model="[run: run]" /> … … 225 186 <!-- Runs --> 226 187 <h2>Assays</h2> 227 <g:if test="${run.assays == null || run.assays.size()== 0}">188 <g:if test="${run.assays == null || numReadableAssays == 0}"> 228 189 No assay found for this run 229 190 </g:if> -
trunk/web-app/css/datatables/demo_table_jui.css
r26 r59 141 141 margin-left: -125px; 142 142 border: 1px solid #ddd; 143 background-color: white; 143 144 text-align: center; 144 145 color: #999; -
trunk/web-app/css/metagenomics.css
r56 r59 508 508 /* Makes sure the filenames in the dialog don't exceed 200px */ 509 509 .dataTables_wrapper .uploadedFile { display: inline-block; zoom: 1; *display: inline; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 190px; height: 15px; } 510 .blok_data { display: inline-block; zoom: 1; *display: inline; width: 400px; vertical-align: top;} 510 .blok_data { display: inline-block; zoom: 1; *display: inline; width: 400px; vertical-align: top;} 511 512 .importProcessedFiles td { vertical-align: middle; } -
trunk/web-app/js/paginate.js
r47 r59 4 4 } 5 5 6 // Initialize default pagination 6 7 $( selector + ' .paginate').each(function(idx, el) { 7 8 var $el = $(el); … … 19 20 }); 20 21 }); 22 23 // Initialize serverside pagination 24 $( selector + ' .paginate_serverside').each(function(idx, el) { 25 var $el = $(el); 26 27 // Determine data url from rel attribute 28 var dataUrl = $el.attr('rel'); 29 30 $el.dataTable({ 31 "bProcessing": true, 32 "bServerSide": true, 33 "sAjaxSource": dataUrl, 34 sDom: '<"H"lf>rt<"F"ip>', 35 36 bJQueryUI: true, 37 bAutoWidth: false, 38 bFilter: false, 39 bLengthChange: false, 40 iCookieDuration: 86400, // Save cookie one day 41 sPaginationType: 'full_numbers', 42 iDisplayLength: 10, // Number of items shown on one page. 43 aoColumnDefs: [ 44 { "bSortable": false, "aTargets": ["nonsortable"] } // Disable sorting on all columns with th.nonsortable 45 ], 46 47 // Override the fnServerData in order to show/hide the paginated 48 // buttons if data is loaded 49 "fnServerData": function ( sSource, aoData, fnCallback ) { 50 $.ajax( { 51 "dataType": 'json', 52 "type": "POST", 53 "url": sSource, 54 "data": aoData, 55 "success": function( data, textStatus, jqXHR ) { 56 fnCallback( data, textStatus, jqXHR ); 57 showHidePaginatedButtonsForTableWrapper( $el.parent() ); 58 } 59 } ); 60 } 61 }); 62 }); 21 63 64 // Show hide paginated buttons 65 showHidePaginatedButtons( selector ); 66 } 67 68 function showHidePaginatedButtons( selector ) { 22 69 // Remove the top bar of the datatable and hide pagination with only one page 23 70 $( selector + " .dataTables_wrapper").each(function(idx, el) { 24 71 var $el = $(el); 72 showHidePaginatedButtonsForTableWrapper( $el ) 73 }); 74 } 75 76 function showHidePaginatedButtonsForTableWrapper( el ) { 77 // Hide pagination if only one page is present (that is: if no buttons can be clicked) 78 if(el.find('span span.ui-state-default:not(.ui-state-disabled)').size() == 0 ){ 79 el.find('div.fg-toolbar').css( 'display', 'none' ); 80 } else { 81 el.find('div.fg-toolbar').css( 'display', 'block' ); 82 el.find( 'div.ui-toolbar' ).first().hide(); 25 83 26 // Hide pagination if only one page is present (that is: if no buttons can be clicked) 27 if($el.find('span span.ui-state-default:not(.ui-state-disabled)').size() == 0 ){ 28 $el.find('div.fg-toolbar').css( 'display', 'none' ); 29 } else { 30 $el.find('div.fg-toolbar').css( 'display', 'block' ); 31 $el.find( 'div.ui-toolbar' ).first().hide(); 32 33 // Check whether a h1, h2 or h3 is present above the table, and move it into the table 34 /* 35 var $previousElement = $el.prev(); 36 if( $previousElement != undefined && $previousElement.get(0) != undefined ) { 37 var tagName = $previousElement.get(0).tagName.toLowerCase(); 38 if( tagName == "h1" || tagName == "h2" || tagName == "h3" ) { 39 // Put the margin that was on the title onto the table 40 $el.css( "margin-top", $previousElement.css( "margin-top" ) ); 41 $previousElement.css( "margin-top", '4px' ); 42 $previousElement.css( "marginBottom", '4px' ); 84 // Check whether a h1, h2 or h3 is present above the table, and move it into the table 85 /* 86 var $previousElement = $el.prev(); 87 if( $previousElement != undefined && $previousElement.get(0) != undefined ) { 88 var tagName = $previousElement.get(0).tagName.toLowerCase(); 89 if( tagName == "h1" || tagName == "h2" || tagName == "h3" ) { 90 // Put the margin that was on the title onto the table 91 $el.css( "margin-top", $previousElement.css( "margin-top" ) ); 92 $previousElement.css( "margin-top", '4px' ); 93 $previousElement.css( "marginBottom", '4px' ); 43 94 44 // If so, move the element into the table 45 $previousElement.remove(); 46 $el.find( 'div.ui-toolbar' ).first().append( $previousElement ); 47 } 95 // If so, move the element into the table 96 $previousElement.remove(); 97 $el.find( 'div.ui-toolbar' ).first().append( $previousElement ); 48 98 } 49 */50 99 } 51 }); 100 */ 101 } 52 102 } 53 103
Note: See TracChangeset
for help on using the changeset viewer.