source: trunk/grails-app/controllers/nl/tno/massSequencing/RunController.groovy @ 70

Last change on this file since 70 was 70, checked in by robert@…, 8 years ago
  • Installed templates (in order to extend session lifetime to 2 hours)
  • Implemented background worker to do work outside the HTTP request
File size: 28.4 KB
RevLine 
[27]1package nl.tno.massSequencing
[2]2
3import java.util.Date;
[59]4import grails.converters.JSON
5import nl.tno.massSequencing.auth.*
[70]6import nl.tno.massSequencing.classification.*;
[2]7
8import org.codehaus.groovy.grails.commons.ConfigurationHolder
9
10class RunController {
11        def fileService
[7]12        def synchronizationService
13        def sampleExcelService
[13]14        def fastaService
[59]15        def dataTablesService
[7]16
17        def index = {
[26]18                [runs: Run.list(), user: session.user]
[7]19        }
[60]20
[59]21        /**
[60]22         * Returns JSON data for the datatable with runs
23         * @see http://www.datatables.net/usage/server-side
24         * @see DataTablesService.retrieveData
25         */
26        def showRunList = {
27                // Determine the total number of assaysamples for this run
28                def ids = Run.executeQuery( "SELECT r.id FROM Run r" );
29                def total = ids.size();
[7]30
[60]31                // Which columns are shown on screen and should be retrieved from the database
32                def columns = [
33                        "r.id",
34                        "r.name",
35                        "COUNT( DISTINCT a )",
[70]36                        "SUM( a.numSequences )",
37                        "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.run = r)"
[60]38                ]
[59]39
[60]40                def groupColumns = columns[0..1];
41                def orderByMapping = null;      // Meaning: order by column 2 on screen = order by column 1 in the table (screen starts at column 0, table starts at column 1 )   
[59]42
[60]43                // Retrieve data from assaySample table
[63]44                def from = "Run r LEFT JOIN r.assaySamples a"
[70]45               
[60]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 ];
[70]52                        def numClassified = it[ 4 ];
[59]53
[60]54                        // Create buttons in the last three columns
55                        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" />' };
56                        def deleteButton = '';
57                        def chartButton = '';
58
59                        if( numSequences > 0 ) {
60                                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" />' }
61                        } else {
62                                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." />'
63                        }
64
65                        if( numSequences > 0 || Run.hasNonWritableAssays( runId, session.user.id ) ) {
66                                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." />'
67                        } else {
68                                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" />' }
69                        }
70
71                        [
72                                g.checkBox( name: "ids", value: runId, checked: false, onClick: "updateCheckAll(this);" ),
73                                g.link( title:"View run", controller:"run", action:"show", id: runId ) { runName },     // it.name
74                                numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-",     // it.numSequences(),
75                                numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(),
[70]76                                numClassified > 0 ? g.formatNumber( number: numClassified, format: "###,###,##0" ) : "-", // it.percentageClassified
[60]77                                editButton,
78                                deleteButton,
79                                chartButton
80                        ]
81                }
82
83                // Send the data to the user
84                render dataTablesService.retrieveData(
85                                params,
86                                Run.class,
87                                convertClosure,
88                                columns,
89                                groupColumns,
90                                from,
91                                total,
92                                ids,
93                                orderByMapping
94                                ) as JSON
95        }
96
[4]97        def show = {
[7]98                // load run with id specified by param.id
99                def run = getRun( params.id );
100
101                if (!run) {
102                        redirect(controller: 'study', action: 'index')
[4]103                        return
104                }
[7]105
[52]106                // Find statistics for all assaySamples in order to improve performance
107                AssaySample.initStats( run.assaySamples?.toList() )
108
[7]109                // Determine runs not used in this assay
[14]110                def otherAssays = Assay.list( sort: "name" ).findAll { !it.runs.contains( run ) && it.study.canRead( session.user ) }
[7]111
[59]112                // Determine several parameters to show on screen
[70]113                def numClassified = Classification.executeQuery( "SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample IN (:assaySamples)", [ "assaySamples": run.assaySamples ] );
[59]114               
[7]115                // Send the assay information to the view
[70]116                [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true, "numClassified": numClassified ? numClassified[ 0 ] : 0 ]
[4]117        }
[59]118       
119        /**
120         * Returns JSON data for the datatable with assaysamples
121         * @see http://www.datatables.net/usage/server-side
122         * @see DataTablesService.retrieveData
123         */
124        def showSampleData = {
125                // load run with id specified by params.id
126                def run = getRun( params.id );
[7]127
[59]128                if (!run) {
129                        response.sendError(404, "Run not found" )
130                        return
131                }
132               
133                // Determine the total number of assaysamples for this run
[60]134                def ids = AssaySample.executeQuery( "SELECT s.id 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  ] );
135                def total = ids.size();
136               
[59]137                // Which columns are shown on screen and should be retrieved from the database
138                def columns = [
[60]139                        "s.id",
[59]140                        "s.sample.name",
141                        "s.assay.study.name",
142                        "s.assay.name",
143                        "s.fwMidName",
144                        "SUM( sd.numSequences )",
145                        "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )",
146                        's.run.id',
147                        's.assay.id',
148                        's.assay.study.id',
149                ]
150               
[60]151                def groupColumns = columns[0..4] + columns[7..9];
152                def orderByMapping = null;      // Meaning: order by column 2 on screen = order by column 1 in the table (screen starts at column 0, table starts at column 1 )
153               
[59]154                // Retrieve data from assaySample table
155                def from = "AssaySample s LEFT JOIN s.sequenceData as sd"
156
157                // And filter by runId
158                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 )"
159                def parameters = [ "runId": run.id, "user": session.user ];
160               
161                // This closure determines what to do with a row that is retrieved from the database.
162                def convertClosure = {
163                        def sampleId = it[ 0 ];
[60]164                        def sampleName = it[ 1 ];
[59]165                        def runId = it[ 7 ];
166                        def assayId = it[ 8 ];
167                        def studyId = it[ 9 ];
168                        def numSequences = it[ 5 ];
169                       
170                        // TODO: Rewrite this authorization part in a more efficient way
171                        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 ] );
172                        def canWrite = auth ? auth[ 0 ] : false;
173                       
174                        // Create buttons in the last three columns
175                        def editButton = '';
176                        def deleteButton = '';
177                        def chartButton = '';
178                       
179                        if( canWrite ) {
180                                editButton = g.link( url: '#', title: "Edit sample", onClick: "showEditSampleDialog(" + sampleId + ", 'run', " + runId + ");"  ) { '<img src="' + fam.icon( name: 'pencil' ) + '" title="Edit sample" />' };
181                                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" />' };
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                                deleteButton = '<img src="' + fam.icon( name: 'application_delete' ) + '" title="You can\'t remove this sample because you don\'t have sufficient privileges." class="disabled" />';
185                        }
186                       
187                        if( numSequences > 0 ) {
188                                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" />' }
189                        } else {
190                                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." />'
191                        }
192                       
193                        [
194                                g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ),
[63]195                                g.link( url: "#", onClick:"showSample( " + sampleId + ", 'run' );", title: "Show sample details" ) { sampleName },      // it.sample.name
[59]196                                it[ 2 ],        // it.assay.study.name
197                                it[ 3 ],        // it.assay.name
198                                it[ 4 ],        // it.fwMidName
199                                it[ 5 ] > 0 ? g.formatNumber( number: it[ 5 ], format: "###,###,##0" ) : "-",   // it.numSequences(),
200                                it[ 6 ] > 0 ? g.formatNumber( number: it[ 6 ], format: "###,###,##0" ) : "-",   // it.numQualScores(),
201                                editButton,
202                                deleteButton,
203                                chartButton
204                        ]
205                }
206               
207                // Send the data to the user
208                render dataTablesService.retrieveData(
209                        params,
210                        AssaySample.class,
211                        convertClosure,
212                        columns,
213                        groupColumns,
214                        from,
215                        total,
[60]216                        ids,
217                        orderByMapping, 
[59]218                        where,
219                        parameters
220                ) as JSON
[60]221       
[59]222        }
223
[4]224        /**
225         * Shows a form to edit the specified run in dialog mode
226         */
227        def editForm = {
[7]228                // load run with id specified by param.id
229                Run run = getRun( params.id );
230
231                if (!run) {
232                        render flash.error
[4]233                        return
234                }
[12]235
[9]236                Assay assay = null
237                if( params.assayId ) {
238                        assay = getAssay( params.assayId )
[12]239
[9]240                        if( !assay ) {
241                                render flash.error;
242                                return
243                        }
[4]244                }
[7]245
[4]246                [assay: assay, run: run]
247        }
[2]248
[7]249        def create = {
[14]250                // Retrieve the assay from the database, but don't exit with an error if no assay is found
[7]251                Assay a = getAssay(params.id);
252                flash.error = "";
253
[2]254                // Create run based on given parameters
255                Run run = new Run();
[7]256
[2]257                run.setPropertiesFromForm( params );
258
[7]259                if( a )
260                        a.addToRuns( run );
[2]261
262                if( !run.save() ) {
263                        flash.message = "Run could not be saved: " + run.getErrors();
264                } else {
265                        flash.message = "Run " + run.name + " has been added to the system."
266                }
267
[7]268                if( a )
269                        redirect( controller: "assay", action: "show", id: a.id )
270                else
271                        redirect( controller: 'run' );
[2]272        }
[7]273
[4]274        def update = {
[7]275                Run run = getRun( params.id );
[4]276
[7]277                if( !run ) {
[4]278                        redirect(controller: 'assay', action: 'show', id: params.assayId)
279                        return
280                }
281
282                // Set properties to the run
283                params.parameterFile = params.editParameterFile
[12]284
[4]285                run.setPropertiesFromForm( params );
286
287                if( run.save() ) {
288                        flash.message = "Run succesfully saved";
289                } else {
290                        flash.error = "Run could not be saved: " + run.getErrors();
291                }
[12]292
[9]293                Assay assay = getAssay(params.assayId);
294                flash.error = "";
[12]295
[9]296                if( assay ) {
297                        redirect( controller: 'assay', action: 'show', id: assay.id)
298                } else {
299                        redirect( controller: 'run', action: 'show', id: run.id )
300                }
[4]301        }
[7]302
[4]303        def delete = {
[7]304                Run run = getRun( params.id );
[4]305
[7]306                if( !run ) {
307                        redirect(controller: 'assay', action: 'show', id: params.assayId)
308                        return
309                }
310
[4]311                // Don't remove runs for which data exists
[26]312                if( run.assaySamples*.sequenceData.flatten().size() ) {
313                        flash.message = "Run could not be deleted because samples with data are associated with it.";
[4]314                        redirect( controller: "assay", action: "show", id: params.assayId )
315                }
[7]316
[14]317                // Check whether the user has sufficient privileges to remove the run from all assays
318                def hasPrivileges = true;
319                run.assay.each {
320                        if( !it.study.canWrite( session.user ) ) 
321                                hasPrivileges = false
322                }
323               
324                if( !hasPrivileges ) {
325                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
326                        redirect( controller: "assay", action: "show", id: params.assayId )
327                }
328               
[4]329                // Remove all associations
[26]330                def a = [] + run.assays
331                a.each {
[4]332                        run.removeFromAssays( it );
333                }
[7]334
[4]335                def name = run.name
336                run.delete();
337                flash.message = "Run " + name + " has been deleted from the system."
338
339                redirect( controller: "assay", action: "show", id: params.assayId )
[7]340        }
341
[26]342        def deleteRun = {
343                Run run = getRun( params.id );
344
345                if( !run ) {
346                        redirect(controller: 'run', action: 'index')
347                        return
348                }
349
350                // Don't remove runs for which data exists
351                if( run.assaySamples*.sequenceData.flatten().size() ) {
352                        flash.message = "Run could not be deleted because samples with data are associated with it.";
353                        redirect(controller: 'run', action: 'index')
354                }
355
356                // Check whether the user has sufficient privileges to remove the run from all assays
357                def hasPrivileges = true;
358                run.assays.each {
359                        if( !it.study.canWrite( session.user ) )
360                                hasPrivileges = false
361                }
362               
363                if( !hasPrivileges ) {
364                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
365                        redirect(controller: 'run', action: 'index')
366                }
367               
368                // Remove all associations
369                def a = [] + run.assays
370                a.each {
371                        run.removeFromAssays( it );
372                }
373
374                def name = run.name
375                run.delete();
376                flash.message = "Run " + name + " has been deleted from the system."
377
378                redirect(controller: 'run', action: 'index')
379        }
380
[7]381        /**************************************************************************
382         *
383         * Methods for handling data about the samples in this run
384         *
385         *************************************************************************/
386
387        /**
388         * Downloads an excel sheet with data about the assay samples, to enter data in excel
389         */
390        def downloadTagsExcel = {
391                Run run = getRun( params.id );
392
393                if( !run ) {
[44]394                        redirect(controller: 'run')
[7]395                        return
396                }
397
398                // Make it only possible to update samples writable by the user
399                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
400
401                def filename = "Run " + run.name + "_tags.xls"
402                def wb = sampleExcelService.downloadSampleExcel( assaySamples, false );
403
404                // Make file downloadable
405                log.trace( "Creation for downloading the file " + filename )
406                sampleExcelService.excelService.downloadFile( wb, filename, response )
407        }
[44]408       
409        /**
410        * Downloads an example excel sheet to describe the format of a file-matching sheet. This
411        * file is used when uploading sequence files.
412        */
413   def downloadMatchExcel = {
414           Run run = getRun( params.id );
[7]415
[44]416           if( !run ) {
417                   redirect(controller: 'run')
418                   return
419           }
[7]420
[44]421           // Make it only possible to update samples writable by the user
422           def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
423
424           def filename = "Run " + run.name + "_filenames.xls"
425           def wb = sampleExcelService.downloadMatchExcel( assaySamples );
426
427           // Make file downloadable
428           log.trace( "Creation for downloading the file " + filename )
429           sampleExcelService.excelService.downloadFile( wb, filename, response )
430   }
431
[7]432        /**
433         * Parses an uploaded excel file and shows a form to match columns
434         */
435        def parseTagExcel = {
436                Run run = getRun( params.id );
437
438                if( !run ) {
439                        redirect(controller: 'study')
440                        return
441                }
442
443                def filename = params.filename
444
445                // Security check to prevent accessing files in other directories
446                if( !filename || filename.contains( '..' ) ) {
447                        response.status = 500;
[50]448                        response.setContentType( "text/plain" );
[7]449                        render "Invalid filename given";
450                        return;
451                }
452
453                // Check for existence and readability
454                File file = new File( fileService.getUploadDir(), filename)
455
456                if( !file.exists() || !file.canRead() ) {
457                        response.status = 404;
[50]458                        response.setContentType( "text/plain" );
[7]459                        render "The uploaded file doesn't exist or doesn't work as expected.";
460                        return;
461                }
462
463                // Save the filename in session for later use
464                session.filename = filename;
465                def excelData;
466                try {
467                        excelData = sampleExcelService.parseTagsExcel( file, false );
468                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
[50]469                        e.printStackTrace()
[7]470                        // Couldn't create a workbook from this file.
471                        response.status = 400 // Bad request
[50]472                        response.setContentType( "text/plain" );
[7]473                        render "Uploaded file is not a valid excel file: " + e.getMessage()
474                        return
475                }
476                session.possibleFields = excelData.possibleFields
477
478                [run: run, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
479        }
480
481        /**
482         * Updates the assay samples based on the given excel file and the column matches
483         */
484        def updateTagsByExcel = {
485                Run run = getRun( params.id );
486
487                if( !run ) {
488                        // Now delete the file, since we don't need it anymore
489                        _deleteUploadedFileFromSession()
490
491                        redirect(controller: 'study')
492                        return
493                }
494
495                if( !session.filename ) {
496                        // Now delete the file, since we don't need it anymore
497                        _deleteUploadedFileFromSession()
498
499                        flash.error = "No excel file found because session timed out. Please try again."
500                        redirect( action: 'show', id: params.id)
501                        return
502                }
503
504                // Determine the match-columns
505                def matchColumns = params[ 'matches'];
506
507                // Now loop through the excel sheet and update all samples with the specified data
508                File file = new File( fileService.getUploadDir(), session.filename );
509
510                if( !file.exists() || !file.canRead() ) {
511                        flash.error = "Excel file has been removed since previous step. Please try again."
512                        redirect( action: 'show', id: params.id)
513                        return
514                }
[12]515
[7]516                // Make it only possible to update samples writable by the user
517                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
[14]518               
[7]519                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
520
521                // Return a message to the user
522                if( !excelData.success ) {
523                        flash.error = excelData.message
524                } else if( excelData.numSuccesful == 0 ) {
525                        flash.error = "None of the " + excelData.failedRows.size() + " row(s) could be imported, because none of the sample names matched or no samples are writable. Have you provided the right excel file?"
526                } else {
527                        flash.message = excelData.numSuccesful + " samples have been updated. "
528
529                        if( excelData.failedRows.size() > 0 )
530                                flash.message += excelData.failedRows.size() + " row(s) could not be imported, because the sample names could not be found in the database or you don't have the proper permissions to change them."
531                }
[14]532
533                // Now delete the file, since we don't need it anymore
534                _deleteUploadedFileFromSession()
535
[7]536                redirect( action: 'show', id: params.id )
[4]537        }
[7]538
539
540        /**
541         * Update the properties of the assay samples manually
542         */
543        def updateTagsManually = {
544                Run run = getRun( params.id );
545
546                if( !run ) {
547                        redirect(controller: 'study')
548                        return
549                }
550
551                // Loop through all assay samples and set data
552                def sampleParams = params.assaySample;
553
554                if( sampleParams ) {
[14]555                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
[7]556                                def assaySampleParams = sampleParams.get( assaySample.id as String );
557                                if( assaySampleParams ) {
[24]558                                        sampleExcelService.variableFields.each { k, v ->
559                                                assaySample[ k ] = assaySampleParams[ k ];
560                                        }
[7]561                                        assaySample.save()
562                                }
563                        }
564                }
565
566                flash.message = "Data about samples is saved."
567                redirect( action: 'show', id: params.id )
568        }
569
570        /**************************************************************************
571         *
572         * Methods for handling data about assays for this run
573         *
574         *************************************************************************/
575
576        /**
[12]577         * Adds existing samples to this run
578         */
579        def addSamples = {
580                Run run = getRun( params.id );
581
582                if( !run ) {
[14]583                        redirect(controller: 'run', action: 'index')
[12]584                        return
585                }
586
587                // Add checked runs to this assay
588                def assaySamples = params.assaySamples
589                if( assaySamples instanceof String ) {
590                        assaySamples = [ assaySamples ]
591                }
592
593                def numAdded = 0;
594                assaySamples.each { assaySampleId ->
595                        try {
596                                def assaySample = AssaySample.findById( assaySampleId as Long )
[50]597                                if( !assaySample.run && assaySample.assay.study.canWrite( session.user ) ) {
[14]598                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
599                                                run.addToAssaySamples( assaySample );
600                                                numAdded++;
601                                        }
[12]602                                }
603                        } catch( Exception e ) {}
604                }
605
606                flash.message = numAdded + " samples are added to this run."
607                redirect( action: 'show', id: params.id)
608        }
609
610        /**
611         * Removes sample from this run
612         */
613        def removeSample = {
614                Run run = getRun( params.id );
615
616                if( !run ) {
617                        redirect(controller: 'study')
618                        return
619                }
620
621                if( !params.assaySampleId ) {
[14]622                        flash.error = "No sample id given"
[12]623                        redirect(action: 'show', id: params.id)
624                        return
625                }
626
627                def assaySample
628
629                try {
630                        assaySample = AssaySample.findById( params.assaySampleId as Long )
631                } catch( Exception e ) {
632                        log.error e
[14]633                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
[12]634                        redirect(action: 'show', id: params.id)
635                        return
636                }
[14]637               
638                if( !assaySample.assay.study.canWrite( session.user ) ) {
639                        flash.error = "You don't have sufficient privileges to remove the specified sample from this run."
640                        redirect(action: 'show', id: params.id)
641                        return
642                }
643               
[12]644                if( run.assaySamples.contains( assaySample ) ) {
645                        run.removeFromAssaySamples( assaySample );
646                        flash.message = "The sample has been removed from this run."
647                } else {
648                        flash.message = "The given sample was not associated with this run."
649                }
650
651                redirect( action: 'show', id: params.id)
652        }
[50]653       
654        /**
655         * Removes samples from this run
656         */
657        def removeSamples = {
658                // Determine the run we are in
659                Run run = getRun( params.runId );
[12]660
[50]661                if( !run ) {
662                        redirect(controller: 'run', action: 'list')
663                        return
664                }
[12]665
[50]666                // Find the selected assaysamples
667                def ids = params.list( 'ids' );
668                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
669                def assaySamples = ids.collect { AssaySample.get( it ) }.findAll { it }
670
671                if( !assaySamples ) {
672                        flash.message = "No samples selected for removal"
673                        redirect( action: 'show', id: run.id );
674                        return;
675                }
676
677                def numRemoved = 0;
678                assaySamples.each { assaySample ->             
679                        if( assaySample.assay.study.canWrite( session.user ) ) {
680                                if( assaySample.run ) {
681                                        assaySample.run.removeFromAssaySamples( assaySample );
682                                        numRemoved++;
683                                }
684                        }
685                }
686               
687                if( numRemoved > 0 ) 
688                        flash.message = numRemoved + " sample(s) have been removed from this run."
689                else
690                        flash.message = "No samples have been removed from this run, because you don't have the right privileges to do so."
691
692                redirect( action: 'show', id: run.id)
693        }
694
695
[12]696        /**
[7]697         * Adds existing assays to this run
698         */
699        def addAssays = {
700                Run run = getRun( params.id );
701
702                if( !run ) {
703                        redirect(controller: 'study')
704                        return
705                }
706
707                // Add checked runs to this assay
708                def assays = params.assays
709                if( assays instanceof String ) {
710                        assays = [ assays ]
711                }
712
713                def numAdded = 0;
714                assays.each { assay_id ->
715                        try {
716                                def assay = Assay.findById( assay_id as Long )
[14]717                                if( assay.study.canWrite( session.user ) ) {
718                                        if( run.assays == null || !run.assays.contains( assay ) ) {
719                                                run.addToAssays( assay );
720                                                numAdded++;
721                                        }
[7]722                                }
723                        } catch( Exception e ) {}
724                }
725
[12]726                flash.message = numAdded + " assays are added to this run."
[7]727                redirect( action: 'show', id: params.id)
728        }
729
730        /**
731         * Removes assay for this run
732         */
733        def removeAssay = {
734                Run run = getRun( params.id );
735
736                if( !run ) {
[49]737                        redirect(controller: 'run', action: 'index')
[7]738                        return
739                }
[12]740
[7]741                if( !params.assay_id ) {
742                        flash.message = "No assay id given"
743                        redirect(action: 'show', id: params.id)
744                        return
745                }
746
747                def assay
748
749                try {
750                        assay = Assay.findById( params.assay_id as Long )
751                } catch( Exception e ) {
752                        throw e
753                        flash.message = "Incorrect assay id given: "
754                        redirect(action: 'show', id: params.id)
755                        return
756                }
757
[14]758                if( !assay.study.canWrite( session.user ) ) {
759                        flash.error = "You don't have sufficient privileges to remove the specified assay from this run."
760                        redirect(action: 'show', id: params.id)
761                        return
762                }
763               
[7]764                if( run.assays.contains( assay ) ) {
765                        run.removeFromAssays( assay );
766                        flash.message = "The assay has been removed from this run."
767                } else {
768                        flash.message = "The given assay was not associated with this run."
769                }
770
771                redirect( action: 'show', id: params.id)
772        }
[49]773       
774        /**
775         * Deletes all sequences for a given run
776         */
777        def deleteSequenceData = {
[50]778                // Determine the run we are in
779                Run run = getRun( params.runId );
780
[49]781                if( !run ) {
782                        redirect(controller: 'run', action: 'index')
783                        return
784                }
[50]785
786                // Find the selected assaysamples
787                def ids = params.list( 'ids' );
788                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
789                def assaySamples = ids.collect { AssaySample.get( it ) }.findAll { it }
790
[49]791                if( !assaySamples ) {
[50]792                        flash.message = "No samples selected"
793                        redirect( action: 'show', id: run.id );
794                        return;
[49]795                }
796               
797                def numFiles = fastaService.deleteSequenceData( assaySamples );
798               
[50]799                flash.message = numFiles + " files have been removed from the run.";
[49]800                redirect( controller: 'run', action: 'show', id: run.id );
801        }
[7]802
[13]803        /**
804         * Exports data about one or more runs in fasta format
805         */
806        def exportAsFasta = {
[14]807                def assaySamples = getAssaySamples( params );
[7]808
[14]809                if( assaySamples == null )
[13]810                        return;
811
812                def name
813
[14]814                if( assaySamples.size() == 0 ) {
815                        flash.error = "No samples found for selected runs";
[50]816                        redirect( action: "index" );
[14]817                        return;
818                } else if( assaySamples*.run.unique().size() == 1 )
819                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
[13]820                else
821                        name = "runs";
822
[70]823                       
824                // Start the export in the background
825                def returnUrl = createLink( controller: "run", action: "index" ).toString()
826                def finishUrl = createLink( controller: "assaySample", action: 'downloadFasta', params: [ processId: '%s' ] ).toString();
827                def url = fastaService.startExportProcess( assaySamples, session, name, returnUrl, finishUrl )
828               
829                // Show a waiting screen
830                redirect( url: url );
[13]831        }
[14]832       
833        /**
834         * Export metadata of selected samples in excel format
835         */
836        def exportMetaData = {
837                def assaySamples = getAssaySamples( params );
838                def name
839               
840                if( assaySamples == null )
841                        return;
842                       
843                if( assaySamples.size() == 0 ) {
844                        flash.error = "No samples found for selected runs";
[50]845                        redirect( action: "index" );
[14]846                        return;
847                } else if( assaySamples*.run.unique().size() == 1 ) {
848                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
849                } else {
850                        name = "runs";
851                }
[13]852
[14]853                // Export the metadata
854                try {
855                        // The export functionality needs a assaysSample-tag list, but it
856                        // should be empty when only exporting metadata
857                        def tags = [];
858                        assaySamples.unique().each { assaySample ->
859                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
860                        }
[19]861                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
[52]862
[70]863                        sampleExcelService.sessionToken = session.sessionToken
864                       
[52]865                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) {
[19]866                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
867                                response.setHeader( "Content-disposition", "" );
868                                redirect( action: "index" );
869                        }
[14]870                        response.outputStream.flush();
871                } catch( Exception e ) {
872                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
[52]873                        e.printStackTrace();
[14]874                }
875        }
876       
[49]877        def sequenceLengthHistogram = {
[59]878                redirect( controller: "assaySample", action: "sequenceLengthHistogramForRun", id: params.id );
[49]879        }
880       
[14]881        protected List getAssaySamples( params ) {
882                def ids = params.list( 'ids' );
883               
884                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
[13]885
[14]886                if( !ids ) {
887                        def message = "No run ids given"
888                        flash.error = message
889                        redirect( action: "index" );
890                        return;
891                }
892
893                def assaySamples = [];
894
895                // Determine which assaySamples to export
896                ids.each { id ->
897                        def run = Run.get( id );
898                        if( run )
899                                assaySamples += run.assaySamples.findAll { it.assay.study.canRead( session.user ) }
900                }
901               
902                return assaySamples;
903        }
904
[7]905        /**
906         * Deletes an uploaded file for which the filename is given in the session.
907         * @return
908         */
909        def _deleteUploadedFileFromSession() {
910                if( !session.filename )
911                        return
912
913                // Now delete the file, since we don't need it anymore
914                fileService.delete( session.filename  )
915                session.filename = ''
916        }
917
918        protected Run getRun(def runId) {
919                // load study with id specified by param.id
920                def run
921                try {
922                        run = Run.get(runId as Long)
923                } catch( Exception e ) {
924                        flash.error = "Incorrect id given: " + runId
925                        return null
926                }
927
928                if (!run) {
929                        flash.error = "No run found with id: " + runId
930                        return null
931                }
932
933                return run
934        }
935
936        protected Assay getAssay(def assayId) {
937                // load study with id specified by param.id
938                def assay
939                try {
940                        assay = Assay.get(assayId as Long)
941                } catch( Exception e ) {
942                        flash.error = "Incorrect id given: " + assayId
943                        return null
944                }
945
946                if (!assay) {
947                        flash.error = "No assay found with id: " + assayId
948                        return null
949                }
950
951                if (!assay.study.canRead( session.user ) ) {
952                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
953                        return null
954                }
955
956                return assay
957        }
[2]958}
Note: See TracBrowser for help on using the repository browser.