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

Last change on this file since 71 was 71, checked in by robert@…, 8 years ago

Made some textual changes

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