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
Line 
1package nl.tno.massSequencing
2
3import java.util.Date;
4import grails.converters.JSON
5import nl.tno.massSequencing.auth.*
6import nl.tno.massSequencing.classification.*;
7
8import org.codehaus.groovy.grails.commons.ConfigurationHolder
9
10class RunController {
11        def fileService
12        def synchronizationService
13        def sampleExcelService
14        def fastaService
15        def dataTablesService
16
17        def index = {
18                [runs: Run.list(), user: session.user]
19        }
20
21        /**
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();
30
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 )",
36                        "SUM( a.numSequences )",
37                        "(SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample.run = r)"
38                ]
39
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 )   
42
43                // Retrieve data from assaySample table
44                def from = "Run r LEFT JOIN r.assaySamples a"
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                        def numClassified = it[ 4 ];
53
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(),
76                                numClassified > 0 ? g.formatNumber( number: numClassified, format: "###,###,##0" ) : "-", // it.percentageClassified
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
97        def show = {
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')
103                        return
104                }
105
106                // Find statistics for all assaySamples in order to improve performance
107                AssaySample.initStats( run.assaySamples?.toList() )
108
109                // Determine runs not used in this assay
110                def otherAssays = Assay.list( sort: "name" ).findAll { !it.runs.contains( run ) && it.study.canRead( session.user ) }
111
112                // Determine several parameters to show on screen
113                def numClassified = Classification.executeQuery( "SELECT SUM( c.unclassified ) FROM Classification c WHERE c.assaySample IN (:assaySamples)", [ "assaySamples": run.assaySamples ] );
114               
115                // Send the assay information to the view
116                [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true, "numClassified": numClassified ? numClassified[ 0 ] : 0 ]
117        }
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 );
127
128                if (!run) {
129                        response.sendError(404, "Run not found" )
130                        return
131                }
132               
133                // Determine the total number of assaysamples for this run
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               
137                // Which columns are shown on screen and should be retrieved from the database
138                def columns = [
139                        "s.id",
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               
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               
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 ];
164                        def sampleName = it[ 1 ];
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);" ),
195                                g.link( url: "#", onClick:"showSample( " + sampleId + ", 'run' );", title: "Show sample details" ) { sampleName },      // it.sample.name
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,
216                        ids,
217                        orderByMapping, 
218                        where,
219                        parameters
220                ) as JSON
221       
222        }
223
224        /**
225         * Shows a form to edit the specified run in dialog mode
226         */
227        def editForm = {
228                // load run with id specified by param.id
229                Run run = getRun( params.id );
230
231                if (!run) {
232                        render flash.error
233                        return
234                }
235
236                Assay assay = null
237                if( params.assayId ) {
238                        assay = getAssay( params.assayId )
239
240                        if( !assay ) {
241                                render flash.error;
242                                return
243                        }
244                }
245
246                [assay: assay, run: run]
247        }
248
249        def create = {
250                // Retrieve the assay from the database, but don't exit with an error if no assay is found
251                Assay a = getAssay(params.id);
252                flash.error = "";
253
254                // Create run based on given parameters
255                Run run = new Run();
256
257                run.setPropertiesFromForm( params );
258
259                if( a )
260                        a.addToRuns( run );
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
268                if( a )
269                        redirect( controller: "assay", action: "show", id: a.id )
270                else
271                        redirect( controller: 'run' );
272        }
273
274        def update = {
275                Run run = getRun( params.id );
276
277                if( !run ) {
278                        redirect(controller: 'assay', action: 'show', id: params.assayId)
279                        return
280                }
281
282                // Set properties to the run
283                params.parameterFile = params.editParameterFile
284
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                }
292
293                Assay assay = getAssay(params.assayId);
294                flash.error = "";
295
296                if( assay ) {
297                        redirect( controller: 'assay', action: 'show', id: assay.id)
298                } else {
299                        redirect( controller: 'run', action: 'show', id: run.id )
300                }
301        }
302
303        def delete = {
304                Run run = getRun( params.id );
305
306                if( !run ) {
307                        redirect(controller: 'assay', action: 'show', id: params.assayId)
308                        return
309                }
310
311                // Don't remove runs for which data exists
312                if( run.assaySamples*.sequenceData.flatten().size() ) {
313                        flash.message = "Run could not be deleted because samples with data are associated with it.";
314                        redirect( controller: "assay", action: "show", id: params.assayId )
315                }
316
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               
329                // Remove all associations
330                def a = [] + run.assays
331                a.each {
332                        run.removeFromAssays( it );
333                }
334
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 )
340        }
341
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
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 ) {
394                        redirect(controller: 'run')
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        }
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 );
415
416           if( !run ) {
417                   redirect(controller: 'run')
418                   return
419           }
420
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
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;
448                        response.setContentType( "text/plain" );
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;
458                        response.setContentType( "text/plain" );
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
469                        e.printStackTrace()
470                        // Couldn't create a workbook from this file.
471                        response.status = 400 // Bad request
472                        response.setContentType( "text/plain" );
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                }
515
516                // Make it only possible to update samples writable by the user
517                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
518               
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                }
532
533                // Now delete the file, since we don't need it anymore
534                _deleteUploadedFileFromSession()
535
536                redirect( action: 'show', id: params.id )
537        }
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 ) {
555                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
556                                def assaySampleParams = sampleParams.get( assaySample.id as String );
557                                if( assaySampleParams ) {
558                                        sampleExcelService.variableFields.each { k, v ->
559                                                assaySample[ k ] = assaySampleParams[ k ];
560                                        }
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        /**
577         * Adds existing samples to this run
578         */
579        def addSamples = {
580                Run run = getRun( params.id );
581
582                if( !run ) {
583                        redirect(controller: 'run', action: 'index')
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 )
597                                if( !assaySample.run && assaySample.assay.study.canWrite( session.user ) ) {
598                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
599                                                run.addToAssaySamples( assaySample );
600                                                numAdded++;
601                                        }
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 ) {
622                        flash.error = "No sample id given"
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
633                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
634                        redirect(action: 'show', id: params.id)
635                        return
636                }
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               
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        }
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 );
660
661                if( !run ) {
662                        redirect(controller: 'run', action: 'list')
663                        return
664                }
665
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
696        /**
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 )
717                                if( assay.study.canWrite( session.user ) ) {
718                                        if( run.assays == null || !run.assays.contains( assay ) ) {
719                                                run.addToAssays( assay );
720                                                numAdded++;
721                                        }
722                                }
723                        } catch( Exception e ) {}
724                }
725
726                flash.message = numAdded + " assays are added to this run."
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 ) {
737                        redirect(controller: 'run', action: 'index')
738                        return
739                }
740
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
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               
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        }
773       
774        /**
775         * Deletes all sequences for a given run
776         */
777        def deleteSequenceData = {
778                // Determine the run we are in
779                Run run = getRun( params.runId );
780
781                if( !run ) {
782                        redirect(controller: 'run', action: 'index')
783                        return
784                }
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
791                if( !assaySamples ) {
792                        flash.message = "No samples selected"
793                        redirect( action: 'show', id: run.id );
794                        return;
795                }
796               
797                def numFiles = fastaService.deleteSequenceData( assaySamples );
798               
799                flash.message = numFiles + " files have been removed from the run.";
800                redirect( controller: 'run', action: 'show', id: run.id );
801        }
802
803        /**
804         * Exports data about one or more runs in fasta format
805         */
806        def exportAsFasta = {
807                def assaySamples = getAssaySamples( params );
808
809                if( assaySamples == null )
810                        return;
811
812                def name
813
814                if( assaySamples.size() == 0 ) {
815                        flash.error = "No samples found for selected runs";
816                        redirect( action: "index" );
817                        return;
818                } else if( assaySamples*.run.unique().size() == 1 )
819                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
820                else
821                        name = "runs";
822
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 );
831        }
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";
845                        redirect( action: "index" );
846                        return;
847                } else if( assaySamples*.run.unique().size() == 1 ) {
848                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
849                } else {
850                        name = "runs";
851                }
852
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                        }
861                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
862
863                        sampleExcelService.sessionToken = session.sessionToken
864                       
865                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.outputStream ) ) {
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                        }
870                        response.outputStream.flush();
871                } catch( Exception e ) {
872                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
873                        e.printStackTrace();
874                }
875        }
876       
877        def sequenceLengthHistogram = {
878                redirect( controller: "assaySample", action: "sequenceLengthHistogramForRun", id: params.id );
879        }
880       
881        protected List getAssaySamples( params ) {
882                def ids = params.list( 'ids' );
883               
884                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
885
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
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        }
958}
Note: See TracBrowser for help on using the repository browser.