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

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

Removed mass sample editing (to prevent the edit tags screen opening very slowly). Also added the possibility to add an excel file which matches sequence files to samples (when uploading) (#13). Finally added some 'return false' to onClick events, when dialogs were opened, to prevent the browser from scrolling to the top.

File size: 18.3 KB
Line 
1package nl.tno.massSequencing
2
3import java.util.Date;
4
5import org.codehaus.groovy.grails.commons.ConfigurationHolder
6
7class RunController {
8        def fileService
9        def synchronizationService
10        def sampleExcelService
11        def fastaService
12
13        def index = {
14                [runs: Run.list(), user: session.user]
15        }
16
17        def show = {
18                // load run with id specified by param.id
19                def run = getRun( params.id );
20
21                if (!run) {
22                        redirect(controller: 'study', action: 'index')
23                        return
24                }
25
26                // Make sure the newest data is available
27                synchronizationService.sessionToken = session.sessionToken
28                synchronizationService.user = session.user
29                try { 
30                        synchronizationService.synchronizeStudies();
31                } catch( Exception e ) {
32                        log.error "Exception occurred during synchronization in " + params.controller + ": " + e.getMessage()
33                        redirect( url: synchronizationService.gscfService.urlAuthRemote(params, session.sessionToken) )
34                }
35
36                // Determine runs not used in this assay
37                def otherAssays = Assay.list( sort: "name" ).findAll { !it.runs.contains( run ) && it.study.canRead( session.user ) }
38
39                // Send the assay information to the view
40                [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true]
41        }
42
43        /**
44         * Shows a form to edit the specified run in dialog mode
45         */
46        def editForm = {
47                // load run with id specified by param.id
48                Run run = getRun( params.id );
49
50                if (!run) {
51                        render flash.error
52                        return
53                }
54
55                Assay assay = null
56                if( params.assayId ) {
57                        assay = getAssay( params.assayId )
58
59                        if( !assay ) {
60                                render flash.error;
61                                return
62                        }
63                }
64
65                [assay: assay, run: run]
66        }
67
68        def create = {
69                // Retrieve the assay from the database, but don't exit with an error if no assay is found
70                Assay a = getAssay(params.id);
71                flash.error = "";
72
73                // Create run based on given parameters
74                Run run = new Run();
75
76                run.setPropertiesFromForm( params );
77
78                if( a )
79                        a.addToRuns( run );
80
81                if( !run.save() ) {
82                        flash.message = "Run could not be saved: " + run.getErrors();
83                } else {
84                        flash.message = "Run " + run.name + " has been added to the system."
85                }
86
87                if( a )
88                        redirect( controller: "assay", action: "show", id: a.id )
89                else
90                        redirect( controller: 'run' );
91        }
92
93        def update = {
94                Run run = getRun( params.id );
95
96                if( !run ) {
97                        redirect(controller: 'assay', action: 'show', id: params.assayId)
98                        return
99                }
100
101                // Set properties to the run
102                params.parameterFile = params.editParameterFile
103
104                run.setPropertiesFromForm( params );
105
106                if( run.save() ) {
107                        flash.message = "Run succesfully saved";
108                } else {
109                        flash.error = "Run could not be saved: " + run.getErrors();
110                }
111
112                Assay assay = getAssay(params.assayId);
113                flash.error = "";
114
115                if( assay ) {
116                        redirect( controller: 'assay', action: 'show', id: assay.id)
117                } else {
118                        redirect( controller: 'run', action: 'show', id: run.id )
119                }
120        }
121
122        def delete = {
123                Run run = getRun( params.id );
124
125                if( !run ) {
126                        redirect(controller: 'assay', action: 'show', id: params.assayId)
127                        return
128                }
129
130                // Don't remove runs for which data exists
131                if( run.assaySamples*.sequenceData.flatten().size() ) {
132                        flash.message = "Run could not be deleted because samples with data are associated with it.";
133                        redirect( controller: "assay", action: "show", id: params.assayId )
134                }
135
136                // Check whether the user has sufficient privileges to remove the run from all assays
137                def hasPrivileges = true;
138                run.assay.each {
139                        if( !it.study.canWrite( session.user ) ) 
140                                hasPrivileges = false
141                }
142               
143                if( !hasPrivileges ) {
144                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
145                        redirect( controller: "assay", action: "show", id: params.assayId )
146                }
147               
148                // Remove all associations
149                def a = [] + run.assays
150                a.each {
151                        run.removeFromAssays( it );
152                }
153
154                def name = run.name
155                run.delete();
156                flash.message = "Run " + name + " has been deleted from the system."
157
158                redirect( controller: "assay", action: "show", id: params.assayId )
159        }
160
161        def deleteRun = {
162                Run run = getRun( params.id );
163
164                if( !run ) {
165                        redirect(controller: 'run', action: 'index')
166                        return
167                }
168
169                // Don't remove runs for which data exists
170                if( run.assaySamples*.sequenceData.flatten().size() ) {
171                        flash.message = "Run could not be deleted because samples with data are associated with it.";
172                        redirect(controller: 'run', action: 'index')
173                }
174
175                // Check whether the user has sufficient privileges to remove the run from all assays
176                def hasPrivileges = true;
177                run.assays.each {
178                        if( !it.study.canWrite( session.user ) )
179                                hasPrivileges = false
180                }
181               
182                if( !hasPrivileges ) {
183                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
184                        redirect(controller: 'run', action: 'index')
185                }
186               
187                // Remove all associations
188                def a = [] + run.assays
189                a.each {
190                        run.removeFromAssays( it );
191                }
192
193                def name = run.name
194                run.delete();
195                flash.message = "Run " + name + " has been deleted from the system."
196
197                redirect(controller: 'run', action: 'index')
198        }
199
200        /**************************************************************************
201         *
202         * Methods for handling data about the samples in this run
203         *
204         *************************************************************************/
205
206        /**
207         * Downloads an excel sheet with data about the assay samples, to enter data in excel
208         */
209        def downloadTagsExcel = {
210                Run run = getRun( params.id );
211
212                if( !run ) {
213                        redirect(controller: 'run')
214                        return
215                }
216
217                // Make it only possible to update samples writable by the user
218                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
219
220                def filename = "Run " + run.name + "_tags.xls"
221                def wb = sampleExcelService.downloadSampleExcel( assaySamples, false );
222
223                // Make file downloadable
224                log.trace( "Creation for downloading the file " + filename )
225                sampleExcelService.excelService.downloadFile( wb, filename, response )
226        }
227       
228        /**
229        * Downloads an example excel sheet to describe the format of a file-matching sheet. This
230        * file is used when uploading sequence files.
231        */
232   def downloadMatchExcel = {
233           Run run = getRun( params.id );
234
235           if( !run ) {
236                   redirect(controller: 'run')
237                   return
238           }
239
240           // Make it only possible to update samples writable by the user
241           def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
242
243           def filename = "Run " + run.name + "_filenames.xls"
244           def wb = sampleExcelService.downloadMatchExcel( assaySamples );
245
246           // Make file downloadable
247           log.trace( "Creation for downloading the file " + filename )
248           sampleExcelService.excelService.downloadFile( wb, filename, response )
249   }
250
251        /**
252         * Parses an uploaded excel file and shows a form to match columns
253         */
254        def parseTagExcel = {
255                Run run = getRun( params.id );
256
257                if( !run ) {
258                        redirect(controller: 'study')
259                        return
260                }
261
262                def filename = params.filename
263
264                // Security check to prevent accessing files in other directories
265                if( !filename || filename.contains( '..' ) ) {
266                        response.status = 500;
267                        render "Invalid filename given";
268                        return;
269                }
270
271                // Check for existence and readability
272                File file = new File( fileService.getUploadDir(), filename)
273
274                if( !file.exists() || !file.canRead() ) {
275                        response.status = 404;
276                        render "The uploaded file doesn't exist or doesn't work as expected.";
277                        return;
278                }
279
280                // Save the filename in session for later use
281                session.filename = filename;
282                def excelData;
283                try {
284                        excelData = sampleExcelService.parseTagsExcel( file, false );
285                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
286                        // Couldn't create a workbook from this file.
287                        response.status = 400 // Bad request
288                        render "Uploaded file is not a valid excel file: " + e.getMessage()
289                        return
290                }
291                session.possibleFields = excelData.possibleFields
292
293                [run: run, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
294        }
295
296        /**
297         * Updates the assay samples based on the given excel file and the column matches
298         */
299        def updateTagsByExcel = {
300                Run run = getRun( params.id );
301
302                if( !run ) {
303                        // Now delete the file, since we don't need it anymore
304                        _deleteUploadedFileFromSession()
305
306                        redirect(controller: 'study')
307                        return
308                }
309
310                if( !session.filename ) {
311                        // Now delete the file, since we don't need it anymore
312                        _deleteUploadedFileFromSession()
313
314                        flash.error = "No excel file found because session timed out. Please try again."
315                        redirect( action: 'show', id: params.id)
316                        return
317                }
318
319                // Determine the match-columns
320                def matchColumns = params[ 'matches'];
321
322                // Now loop through the excel sheet and update all samples with the specified data
323                File file = new File( fileService.getUploadDir(), session.filename );
324
325                if( !file.exists() || !file.canRead() ) {
326                        flash.error = "Excel file has been removed since previous step. Please try again."
327                        redirect( action: 'show', id: params.id)
328                        return
329                }
330
331                // Make it only possible to update samples writable by the user
332                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
333               
334                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
335
336                println excelData
337               
338                // Return a message to the user
339                if( !excelData.success ) {
340                        flash.error = excelData.message
341                } else if( excelData.numSuccesful == 0 ) {
342                        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?"
343                } else {
344                        flash.message = excelData.numSuccesful + " samples have been updated. "
345
346                        if( excelData.failedRows.size() > 0 )
347                                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."
348                }
349
350                // Now delete the file, since we don't need it anymore
351                _deleteUploadedFileFromSession()
352
353                redirect( action: 'show', id: params.id )
354        }
355
356
357        /**
358         * Update the properties of the assay samples manually
359         */
360        def updateTagsManually = {
361                Run run = getRun( params.id );
362
363                if( !run ) {
364                        redirect(controller: 'study')
365                        return
366                }
367
368                // Loop through all assay samples and set data
369                def sampleParams = params.assaySample;
370
371                if( sampleParams ) {
372                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
373                                def assaySampleParams = sampleParams.get( assaySample.id as String );
374                                if( assaySampleParams ) {
375                                        sampleExcelService.variableFields.each { k, v ->
376                                                assaySample[ k ] = assaySampleParams[ k ];
377                                        }
378                                        assaySample.save()
379                                }
380                        }
381                }
382
383                flash.message = "Data about samples is saved."
384                redirect( action: 'show', id: params.id )
385        }
386
387        /**************************************************************************
388         *
389         * Methods for handling data about assays for this run
390         *
391         *************************************************************************/
392
393        /**
394         * Adds existing samples to this run
395         */
396        def addSamples = {
397                Run run = getRun( params.id );
398
399                if( !run ) {
400                        redirect(controller: 'run', action: 'index')
401                        return
402                }
403
404                // Add checked runs to this assay
405                def assaySamples = params.assaySamples
406                if( assaySamples instanceof String ) {
407                        assaySamples = [ assaySamples ]
408                }
409
410                def numAdded = 0;
411                assaySamples.each { assaySampleId ->
412                        try {
413                                def assaySample = AssaySample.findById( assaySampleId as Long )
414                                if( assaySample.assay.study.canWrite( session.user ) ) {
415                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
416                                                run.addToAssaySamples( assaySample );
417                                                numAdded++;
418                                        }
419                                }
420                        } catch( Exception e ) {}
421                }
422
423                flash.message = numAdded + " samples are added to this run."
424                redirect( action: 'show', id: params.id)
425        }
426
427        /**
428         * Removes sample from this run
429         */
430        def removeSample = {
431                Run run = getRun( params.id );
432
433                if( !run ) {
434                        redirect(controller: 'study')
435                        return
436                }
437
438                if( !params.assaySampleId ) {
439                        flash.error = "No sample id given"
440                        redirect(action: 'show', id: params.id)
441                        return
442                }
443
444                def assaySample
445
446                try {
447                        assaySample = AssaySample.findById( params.assaySampleId as Long )
448                } catch( Exception e ) {
449                        log.error e
450                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
451                        redirect(action: 'show', id: params.id)
452                        return
453                }
454               
455                if( !assaySample.assay.study.canWrite( session.user ) ) {
456                        flash.error = "You don't have sufficient privileges to remove the specified sample from this run."
457                        redirect(action: 'show', id: params.id)
458                        return
459                }
460               
461                if( run.assaySamples.contains( assaySample ) ) {
462                        run.removeFromAssaySamples( assaySample );
463                        flash.message = "The sample has been removed from this run."
464                } else {
465                        flash.message = "The given sample was not associated with this run."
466                }
467
468                redirect( action: 'show', id: params.id)
469        }
470
471
472        /**
473         * Adds existing assays to this run
474         */
475        def addAssays = {
476                Run run = getRun( params.id );
477
478                if( !run ) {
479                        redirect(controller: 'study')
480                        return
481                }
482
483                // Add checked runs to this assay
484                def assays = params.assays
485                if( assays instanceof String ) {
486                        assays = [ assays ]
487                }
488
489                def numAdded = 0;
490                assays.each { assay_id ->
491                        try {
492                                def assay = Assay.findById( assay_id as Long )
493                                if( assay.study.canWrite( session.user ) ) {
494                                        if( run.assays == null || !run.assays.contains( assay ) ) {
495                                                run.addToAssays( assay );
496                                                numAdded++;
497                                        }
498                                }
499                        } catch( Exception e ) {}
500                }
501
502                flash.message = numAdded + " assays are added to this run."
503                redirect( action: 'show', id: params.id)
504        }
505
506        /**
507         * Removes assay for this run
508         */
509        def removeAssay = {
510                Run run = getRun( params.id );
511
512                if( !run ) {
513                        redirect(controller: 'study')
514                        return
515                }
516
517                if( !params.assay_id ) {
518                        flash.message = "No assay id given"
519                        redirect(action: 'show', id: params.id)
520                        return
521                }
522
523                def assay
524
525                try {
526                        assay = Assay.findById( params.assay_id as Long )
527                } catch( Exception e ) {
528                        throw e
529                        flash.message = "Incorrect assay id given: "
530                        redirect(action: 'show', id: params.id)
531                        return
532                }
533
534                if( !assay.study.canWrite( session.user ) ) {
535                        flash.error = "You don't have sufficient privileges to remove the specified assay from this run."
536                        redirect(action: 'show', id: params.id)
537                        return
538                }
539               
540                if( run.assays.contains( assay ) ) {
541                        run.removeFromAssays( assay );
542                        flash.message = "The assay has been removed from this run."
543                } else {
544                        flash.message = "The given assay was not associated with this run."
545                }
546
547                redirect( action: 'show', id: params.id)
548        }
549
550        /**
551         * Exports data about one or more runs in fasta format
552         */
553        def exportAsFasta = {
554                def assaySamples = getAssaySamples( params );
555
556                if( assaySamples == null )
557                        return;
558
559                def name
560
561                if( assaySamples.size() == 0 ) {
562                        flash.error = "No samples found for selected runs";
563                        redirect( action: "list" );
564                        return;
565                } else if( assaySamples*.run.unique().size() == 1 )
566                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
567                else
568                        name = "runs";
569
570                // Export the sequences and quality scores
571                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
572                try {
573                        fastaService.export( assaySamples.unique(), response.getOutputStream() );
574                        response.outputStream.flush();
575                } catch( Exception e ) {
576                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
577                }
578        }
579       
580        /**
581         * Export metadata of selected samples in excel format
582         */
583        def exportMetaData = {
584                def assaySamples = getAssaySamples( params );
585                def name
586               
587                if( assaySamples == null )
588                        return;
589                       
590                if( assaySamples.size() == 0 ) {
591                        flash.error = "No samples found for selected runs";
592                        redirect( action: "list" );
593                        return;
594                } else if( assaySamples*.run.unique().size() == 1 ) {
595                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
596                } else {
597                        name = "runs";
598                }
599
600                // Export the metadata
601                try {
602                        // The export functionality needs a assaysSample-tag list, but it
603                        // should be empty when only exporting metadata
604                        def tags = [];
605                        assaySamples.unique().each { assaySample ->
606                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
607                        }
608                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
609                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() ) ) {
610                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
611                                response.setHeader( "Content-disposition", "" );
612                                redirect( action: "index" );
613                        }
614                        response.outputStream.flush();
615                } catch( Exception e ) {
616                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
617                }
618        }
619       
620        protected List getAssaySamples( params ) {
621                def ids = params.list( 'ids' );
622               
623                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
624
625                if( !ids ) {
626                        def message = "No run ids given"
627                        flash.error = message
628                        redirect( action: "index" );
629                        return;
630                }
631
632                def assaySamples = [];
633
634                // Determine which assaySamples to export
635                ids.each { id ->
636                        def run = Run.get( id );
637                        if( run )
638                                assaySamples += run.assaySamples.findAll { it.assay.study.canRead( session.user ) }
639                }
640               
641                return assaySamples;
642        }
643
644        /**
645         * Deletes an uploaded file for which the filename is given in the session.
646         * @return
647         */
648        def _deleteUploadedFileFromSession() {
649                if( !session.filename )
650                        return
651
652                // Now delete the file, since we don't need it anymore
653                fileService.delete( session.filename  )
654                session.filename = ''
655        }
656
657        protected Run getRun(def runId) {
658                // load study with id specified by param.id
659                def run
660                try {
661                        run = Run.get(runId as Long)
662                } catch( Exception e ) {
663                        flash.error = "Incorrect id given: " + runId
664                        return null
665                }
666
667                if (!run) {
668                        flash.error = "No run found with id: " + runId
669                        return null
670                }
671
672                return run
673        }
674
675        protected Assay getAssay(def assayId) {
676                // load study with id specified by param.id
677                def assay
678                try {
679                        assay = Assay.get(assayId as Long)
680                } catch( Exception e ) {
681                        flash.error = "Incorrect id given: " + assayId
682                        return null
683                }
684
685                if (!assay) {
686                        flash.error = "No assay found with id: " + assayId
687                        return null
688                }
689
690                if (!assay.study.canRead( session.user ) ) {
691                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
692                        return null
693                }
694
695                return assay
696        }
697}
Note: See TracBrowser for help on using the repository browser.