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

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