root/trunk/grails-app/controllers/dbnp/studycapturing/AssayController.groovy @ 2060

Revision 2060, 18.5 KB (checked in by s.h.sikkema@…, 3 years ago)

- removed default value for samples argument in AssayService?.collectAssayData to comply with default value for remoteUser. Made adjustments where necessary
- updated user guide to new one by Janneke

  • Property svn:keywords set to Rev Author Date
Line 
1package dbnp.studycapturing
2
3import grails.plugins.springsecurity.Secured
4
5@Secured(['IS_AUTHENTICATED_REMEMBERED'])
6class AssayController {
7
8        def assayService
9        def authenticationService
10        def fileService
11
12        static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
13
14        def index = {
15                redirect(action: "list", params: params)
16        }
17
18        def list = {
19                params.max = Math.min(params.max ? params.int('max') : 10, 100)
20                [assayInstanceList: Assay.list(params), assayInstanceTotal: Assay.count()]
21        }
22
23        def create = {
24                def assayInstance = new Assay()
25                assayInstance.properties = params
26                return [assayInstance: assayInstance]
27        }
28
29        def save = {
30                def assayInstance = new Assay(params)
31
32                // The following lines deviate from the generate-all generated code.
33                // See http://jira.codehaus.org/browse/GRAILS-3783 for why we have this shameful workaround...
34                def study = assayInstance.parent
35                study.addToAssays(assayInstance)
36
37                if (assayInstance.save(flush: true)) {
38                        flash.message = "${message(code: 'default.created.message', args: [message(code: 'assay.label', default: 'Assay'), assayInstance.id])}"
39                        redirect(action: "show", id: assayInstance.id)
40                }
41                else {
42                        render(view: "create", model: [assayInstance: assayInstance])
43                }
44        }
45
46        def show = {
47                def assayInstance = Assay.get(params.id)
48                if (!assayInstance) {
49                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
50                        redirect(action: "list")
51                }
52                else {
53                        [assayInstance: assayInstance]
54                }
55        }
56
57        def showByToken = {
58                def assayInstance = Assay.findByAssayUUID(params.id)
59                if (!assayInstance) {
60                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
61                        redirect(action: "list")
62                }
63                else {
64                        redirect(action: "show", id: assayInstance.id)
65                }
66        }
67
68        def edit = {
69                def assayInstance = Assay.get(params.id)
70                if (!assayInstance) {
71                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
72                        redirect(action: "list")
73                }
74                else {
75                        return [assayInstance: assayInstance]
76                }
77        }
78
79        def update = {
80                def assayInstance = Assay.get(params.id)
81                if (assayInstance) {
82                        if (params.version) {
83                                def version = params.version.toLong()
84                                if (assayInstance.version > version) {
85
86                                        assayInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'assay.label', default: 'Assay')] as Object[], "Another user has updated this Assay while you were editing")
87                                        render(view: "edit", model: [assayInstance: assayInstance])
88                                        return
89                                }
90                        }
91                        assayInstance.properties = params
92                        if (!assayInstance.hasErrors() && assayInstance.save(flush: true)) {
93                                flash.message = "${message(code: 'default.updated.message', args: [message(code: 'assay.label', default: 'Assay'), assayInstance.id])}"
94                                redirect(action: "show", id: assayInstance.id)
95                        }
96                        else {
97                                render(view: "edit", model: [assayInstance: assayInstance])
98                        }
99                }
100                else {
101                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
102                        redirect(action: "list")
103                }
104        }
105
106        def delete = {
107                def assayInstance = Assay.get(params.id)
108                if (assayInstance) {
109                        try {
110                                assayInstance.delete(flush: true)
111                                flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
112                                redirect(action: "list")
113                        }
114                        catch (org.springframework.dao.DataIntegrityViolationException e) {
115                                flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
116                                redirect(action: "show", id: params.id)
117                        }
118                }
119                else {
120                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
121                        redirect(action: "list")
122                }
123        }
124
125        def assayExportFlow = {
126                entry {
127                        action{
128                                def user            = authenticationService.getLoggedInUser()
129                                flow.userStudies    = Study.giveReadableStudies(user)
130                        }
131                        on("success").to "selectAssay"
132                }
133
134                selectAssay {
135                        on ("submit"){
136                                flow.assay = Assay.get(params.assayId)
137
138                                // check if assay exists
139                                if (!flow.assay) throw new Exception("No assay found with id: ${params.assayId}")
140
141                                // obtain fields for each category
142                                flow.fieldMap = assayService.collectAssayTemplateFields(flow.assay, null)
143
144                                flash.errorMessage = flow.fieldMap.remove('Module Error')
145                                flow.measurementTokens = flow.fieldMap.remove('Module Measurement Data')
146                        }.to "selectFields"
147
148                        on(Exception).to "handleError"
149                }
150
151                selectFields {
152                        on ("submit"){
153
154                                def fieldMapSelection = [:]
155
156                                flow.fieldMap.eachWithIndex { cat, cat_i ->
157
158                                        if (params."cat_$cat_i" == 'on') {
159                                                fieldMapSelection[cat.key] = []
160
161                                                cat.value.eachWithIndex { field, field_i ->
162
163                                                        if (params."cat_${cat_i}_${field_i}" == 'on')
164                                                                fieldMapSelection[cat.key] += field
165                                                }
166
167                                                if (fieldMapSelection[cat.key] == [])
168                                                        fieldMapSelection.remove(cat.key)
169                                        }
170                                }
171
172                                def measurementTokens = []
173
174                                if (params."cat_4" == 'on') {
175                                        measurementTokens = params.list( "measurementToken" )
176                                }
177
178                                // collect the assay data according to user selecting
179                                def assayData           = assayService.collectAssayData(flow.assay, fieldMapSelection, measurementTokens, [])
180
181                                flash.errorMessage      = assayData.remove('Module Error')
182
183                                flow.rowData            = assayService.convertColumnToRowStructure(assayData)
184
185                                // prepare the assay data preview
186                                def previewRows         = Math.min(flow.rowData.size()    as int, 5) - 1
187                                def previewCols         = Math.min(flow.rowData[0].size() as int, 5) - 1
188
189                                flow.assayDataPreview   = flow.rowData[0..previewRows].collect{ it[0..previewCols] as ArrayList }
190
191                                // store the selected file type in the flow
192                                flow.exportFileType = params.exportFileType
193
194                        }.to "compileExportData"
195
196                        on(Exception).to "handleError"
197                }
198
199                compileExportData {
200                        on ("ok"){
201                                session.rowData = flow.rowData
202                                session.exportFileType = flow.exportFileType
203                        }.to "export"
204                        on ("cancel").to "selectAssay"
205                }
206
207                export {
208                        redirect(action: 'doExport')
209                }
210
211                handleError() {
212                        render(view: 'errorPage')
213                }
214        }
215       
216        /**
217         * This method is called when a user wants to fetch data from GSCF
218         */
219        def galaxyExport = {
220                def galaxy_url = params.get( "GALAXY_URL" );
221                def tool_id = params.get( "tool_id" );
222               
223                // Without galaxy url, we can't do anything
224                if( !galaxy_url || !tool_id ) {
225                        redirect( action: "assayExport" );
226                }
227               
228                def user            = authenticationService.getLoggedInUser()
229                def userStudies    = Study.giveReadableStudies(user)
230               
231                [ galaxy_url: galaxy_url, tool_id: tool_id, userStudies: userStudies ]
232        }
233       
234        def sendToGalaxy = {
235                // Generate the url to redirect the user to
236                def galaxy_url = params.get( "GALAXY_URL" );
237                def tool_id = params.get( "tool_id" );
238               
239                // Without galaxy url, we can't do anything
240                if( !galaxy_url || !tool_id  ) {
241                        redirect( action: "assayExport" );
242                }
243               
244                def assayId = params.long( "assayId" );
245                def assay = assayId ? Assay.get( assayId ) : null;
246               
247                // If no assay is given, return to galaxyExport screen
248                if( !assay ) {
249                        redirect( action: "galaxyExport", params: [ "GALAXY_URL": galaxy_url ] );
250                }
251               
252                // create a random session token that will be used to allow to module to
253                // sync with gscf prior to presenting the measurement data
254                def sessionToken = UUID.randomUUID().toString()
255                def consumer = "galaxy";
256
257                // put the session token to work
258                authenticationService.logInRemotely( consumer, sessionToken, authenticationService.getLoggedInUser() )
259               
260                // Create a url where the data can be fetched
261                def fetchUrl = g.createLink( absolute: true, controller: "assay", action: "fetchGalaxyData", params: [ assayToken: assay.assayUUID, sessionToken: sessionToken ] );
262               
263                // Generate url to redirect the user to
264                def url = galaxy_url + "?tool_id=" + URLEncoder.encode( tool_id.toString(), "UTF-8" ) + "&URL=" + URLEncoder.encode( fetchUrl.toString(), "UTF-8" )
265               
266                log.trace "Redirecting galaxy user back to " + url;
267               
268                redirect( url: url );
269        }
270       
271        // This method is accessible for each user. However, he should return with a valid
272        // session token
273        @Secured(['true'])
274        def fetchGalaxyData = {
275                // Check accessibility
276                def consumer = "galaxy";
277                def remoteUser = authenticationService.getRemotelyLoggedInUser( consumer, params.sessionToken );
278                if( !remoteUser ) {
279                        response.status = 401;
280                        render "You must be logged in";
281                        return
282                }
283
284                // retrieve assay
285                def assay = Assay.findByAssayUUID( params.assayToken );
286               
287                if( !assay ) {
288                        response.status = 404;
289                        render "No assay found";
290                        return
291                }
292               
293                // Return assay data
294                def fieldMap = assayService.collectAssayTemplateFields( assay, null, remoteUser )
295
296                def measurementTokens = fieldMap.remove('Module Measurement Data');
297                def assayData = assayService.collectAssayData(assay, fieldMap, measurementTokens, [], remoteUser)
298
299                def rowData   = assayService.convertColumnToRowStructure(assayData)
300
301                // Invalidate session token
302                authenticationService.logOffRemotely( consumer, params.sessionToken );
303                               
304                def outputDelimiter = '\t'
305                def outputFileExtension = 'txt'
306
307                def filename = "export.$outputFileExtension"
308                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
309                response.setContentType("application/octet-stream")
310                try {
311                        // Skip first row for now to support the current PLS tool in Galaxy, will change in the future
312                        assayService.exportRowWiseDataToCSVFile(rowData[1..-1], response.outputStream, outputDelimiter, java.util.Locale.US)
313                } catch (Exception e) {
314                        flash.errorMessage = e.message
315                        redirect action: 'errorPage'
316                }
317        }
318
319        /**
320         * Export the row data in session.rowData to the outputStream of the http
321         * response.
322         */
323        def doExport = {
324
325                // make sure we're coming from the export flow, otherwise redirect there
326                if (!(session.rowData && session.exportFileType))
327                        redirect(action: 'assayExportFlow')
328
329                // process requested output file type
330                def outputDelimiter, outputFileExtension, locale = java.util.Locale.US
331
332                switch(session.exportFileType) {
333                        case '2': // Comma delimited csv
334                                outputDelimiter = ','
335                                outputFileExtension = 'csv'
336                                break
337                        case '3': // Semicolon delimited csv
338                                outputDelimiter = ';'
339                                outputFileExtension = 'csv'
340                                locale = java.util.Locale.GERMAN // force use of comma as decimal separator
341                                break
342                        default: // Tab delimited with .txt extension
343                                outputDelimiter = '\t'
344                                outputFileExtension = 'txt'
345                }
346
347                def filename = "export.$outputFileExtension"
348                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
349                response.setContentType("application/octet-stream")
350                try {
351
352                        assayService.exportRowWiseDataToCSVFile(session.rowData, response.outputStream, outputDelimiter, locale)
353
354                        // clear the data from the session
355                        session.removeAttribute('rowData')
356                        session.removeAttribute('exportFileType')
357
358                } catch (Exception e) {
359
360                        flash.errorMessage = e.message
361                        redirect action: 'errorPage'
362
363                }
364        }
365
366        /**
367         * Method to export one or more assays to excel in separate sheets.
368         *
369         * @param       params.ids              One or more assay IDs to export
370         * @param       params.format   "list" in order to export all assays in one big excel sheet
371         *                                                      "sheets" in order to export every assay on its own sheet (default)
372         */
373        def exportToExcel = {
374                def format = params.get( 'format', 'sheets' );
375                if( format == 'list' ) {
376                        exportToExcelAsList( params );
377                } else {
378                        exportToExcelAsSheets( params );
379                }
380        }
381
382        /**
383         * Method to export one or more assays to excel in separate sheets.
384         *
385         * @param       params.ids              One or more assay IDs to export
386         */
387        def exportToExcelAsSheets = {
388                def assays = getAssaysFromParams( params );
389
390                if( !assays )
391                        return;
392
393                // Send headers to the browser so the user can download the file
394                def filename = 'export.xlsx'
395                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
396                response.setContentType("application/octet-stream")
397
398                try {
399                        // Loop through all assays to collect the data
400                        def rowWiseAssayData = [];
401
402                        assays.each { assay ->
403                                // Determine which fields should be exported for this assay
404                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
405                                def measurementTokens = fieldMap.remove('Module Measurement Data')
406
407                                // Retrieve row based data for this assay
408                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, [] );
409                                def rowData   = assayService.convertColumnToRowStructure(assayData)
410
411                                // Put each assay on another sheet
412                                rowWiseAssayData << rowData;
413                        }
414
415                        assayService.exportRowWiseDataForMultipleAssaysToExcelFile( rowWiseAssayData, response.getOutputStream() )
416
417                        response.outputStream.flush()
418
419                } catch (Exception e) {
420                        throw e;
421                }
422        }
423
424        /**
425         * Method to export one or more assays to excel.
426         *
427         * @param       params.ids              One or more assay IDs to export
428         */
429        def exportToExcelAsList = {
430                def assays = getAssaysFromParams( params );
431
432                if( !assays )
433                        return;
434
435                // Send headers to the browser so the user can download the file
436                def filename = 'export.csv'
437                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
438                response.setContentType("application/octet-stream")
439
440                try {
441                        // Loop through all assays to collect the data
442                        def columnWiseAssayData = [];
443
444                        assays.each { assay ->
445                                // Determine which fields should be exported for this assay
446                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
447                                def measurementTokens = fieldMap.remove('Module Measurement Data')
448
449                                // Retrieve row based data for this assay
450                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, [] );
451
452                                // Prepend study and assay data to the list
453                                assayData = assayService.prependAssayData( assayData, assay, assay.samples?.size() )
454                                assayData = assayService.prependStudyData( assayData, assay, assay.samples?.size() )
455
456                                // Put each assay on another sheet
457                                columnWiseAssayData << assayData;
458                        }
459
460                        // Merge data from all assays
461                        def mergedColumnWiseData = assayService.mergeColumnWiseDataOfMultipleStudies( columnWiseAssayData );
462
463                        def rowData   = assayService.convertColumnToRowStructure(mergedColumnWiseData)
464                        assayService.exportRowWiseDataToCSVFile( rowData, response.getOutputStream() )
465
466                        response.outputStream.flush()
467
468                } catch (Exception e) {
469                        throw e;
470                }
471        }
472
473        /**
474         * Method to export one or more samples to csv in separate sheets.
475         *
476         * @param       params.ids              One or more sample ids to export
477         */
478        def exportSamplesToCsv = {
479                def samples = getSamplesFromParams( params );
480
481                if( !samples ) {
482                        return;
483                }
484
485                // Determine a list of assays these samples have been involved in. That way, we can
486                // retrieve the data for that assay once, and save precious time doing HTTP calls
487                def assays = [:];
488               
489                samples.each { sample ->
490                        def thisAssays = sample.getAssays();
491                       
492                        // Loop through all assays. If it already exists, add the sample it to the list
493                        thisAssays.each { assay ->
494                                if( !assays[ assay.id ] ) {
495                                        assays[ assay.id ] = [ 'assay': assay, 'samples': [] ]
496                                }
497                               
498                                assays[ assay.id ].samples << sample
499                        }
500                }
501               
502                // Now collect data for all assays
503                try {
504                        // Loop through all assays to collect the data
505                        def columnWiseAssayData = [];
506
507                        assays.each { assayInfo ->
508                                def assay = assayInfo.value.assay;
509                                def assaySamples = assayInfo.value.samples;
510                               
511                                // Determine which fields should be exported for this assay
512                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
513                                def measurementTokens = fieldMap.remove('Module Measurement Data')
514                               
515                                // Retrieve row based data for this assay
516                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, assaySamples );
517                               
518                                // Prepend study and assay data to the list
519                                assayData = assayService.prependAssayData( assayData, assay, assaySamples.size() )
520                                assayData = assayService.prependStudyData( assayData, assay, assaySamples.size() )
521                               
522                                // Make sure the assay data can be distinguished later
523                                assayData.put( "Assay data - " + assay.name, assayData.remove( "Assay Data") )
524                                assayData.put( "Module measurement data - " + assay.name, assayData.remove( "Module Measurement Data") )
525                               
526                                // Add the sample IDs to the list, in order to be able to combine
527                                // data for a sample that has been processed in multiple assays
528                                assayData[ "Sample Data" ][ "id" ] = assaySamples*.id;
529                               
530                                println "Assay data"
531                                assayData.each { println it }
532
533                                columnWiseAssayData << assayData;
534                        }
535                       
536                        def mergedColumnWiseData = assayService.mergeColumnWiseDataOfMultipleStudiesForASetOfSamples( columnWiseAssayData );
537
538                        def rowData   = assayService.convertColumnToRowStructure(mergedColumnWiseData)
539                       
540                        // Send headers to the browser so the user can download the file
541                        def filename = 'export.csv'
542                        response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
543                        response.setContentType("application/octet-stream")
544       
545                        assayService.exportRowWiseDataToCSVFile( rowData, response.getOutputStream() )
546
547                        response.outputStream.flush()
548
549                } catch (Exception e) {
550                        throw e;
551                }
552        }
553
554
555        def getAssaysFromParams( params ) {
556                def ids = params.list( 'ids' ).findAll { it.isLong() }.collect { Long.valueOf( it ) };
557                def tokens = params.list( 'tokens' );
558
559                if( !ids && !tokens ) {
560                        flash.errorMessage = "No assay ids given";
561                        redirect( action: "errorPage" );
562                        return [];
563                }
564
565                // Find all assays for the given ids
566                def assays = [];
567                ids.each { id ->
568                        def assay = Assay.get( id );
569                        if( assay )
570                                assays << assay;
571                }
572
573                // Also accept tokens for defining studies
574                tokens.each { token ->
575                        def assay = Assay.findByAssayUUID( token );
576                        if( assay )
577                                assays << assay;
578                }
579
580                if( !assays ) {
581                        flash.errorMessage = "No assays found";
582                        redirect( action: "errorPage" );
583                        return [];
584                }
585
586                return assays.unique();
587        }
588
589        def getSamplesFromParams( params ) {
590                def ids = params.list( 'ids' ).findAll { it.isLong() }.collect { Long.valueOf( it ) };
591                def tokens = params.list( 'tokens' );
592
593                if( !ids && !tokens ) {
594                        flash.errorMessage = "No sample ids given";
595                        redirect( action: "errorPage" );
596                        return [];
597                }
598
599                // Find all assays for the given ids
600                def samples = [];
601                ids.each { id ->
602                        def sample = Sample.get( id );
603                        if( sample )
604                                samples << sample;
605                }
606
607                // Also accept tokens for defining studies
608                tokens.each { token ->
609                        def sample = Sample.findBySampleUUID( token );
610                        if( sample )
611                                samples << sample;
612                }
613
614                if( !samples ) {
615                        flash.errorMessage = "No assays found";
616                        redirect( action: "errorPage" );
617                        return [];
618                }
619
620                return samples.unique();
621        }
622       
623        def errorPage = {
624                render(view: 'assayExport/errorPage')
625        }
626}
Note: See TracBrowser for help on using the browser.