Changeset 1559


Ignore:
Timestamp:
Feb 24, 2011, 5:14:08 PM (7 years ago)
Author:
s.h.sikkema@…
Message:

Assay export functionality

Location:
trunk
Files:
3 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/studycapturing/AssayController.groovy

    r1455 r1559  
    133133
    134134    /**
     135     * Shows a page where individual fields for the different categories (ie.
     136     * subject data, sampling events... etc.) can be selected for export
     137     *
     138     * @param params.id Assay id
     139     */
     140    def selectFields = {
     141        // receives an assay id
     142        def assay = Assay.get(params.assayId)
     143
     144        // obtain fields for each category
     145        try {
     146
     147            def fieldMap = assayService.collectAssayTemplateFields(assay)
     148           
     149        } catch (Exception e) {
     150
     151            flash.errorMessage = e.message
     152            redirect action: 'selectAssay'
     153
     154        }
     155        def measurementTokens = fieldMap.remove('Module Measurement Data')
     156
     157        flash.fieldMap = fieldMap
     158        flash.measurementTokens = measurementTokens
     159        flash.assayId = params.assayId
     160
     161        [fieldMap: fieldMap, measurementTokens: measurementTokens*.name]
     162    }
     163
     164    /**
    135165     * Exports all assay information as an Excel file.
    136166     *
    137167     * @param params.id Assay id
    138168     */
    139     def exportAssayAsExcel = {
    140 
    141         Assay assay = Assay.get(params.assayId)
     169    def compileExportData = {
     170
     171        def fieldMap = flash.fieldMap
     172        def measurementTokens = flash.measurementTokens
     173
     174        def fieldMapSelection = [:]
     175
     176        fieldMap.eachWithIndex { cat, cat_i ->
     177
     178            if (params."cat_$cat_i" == 'on') {
     179                fieldMapSelection[cat.key] = []
     180
     181                cat.value.eachWithIndex { field, field_i ->
     182
     183                    if (params."cat_${cat_i}_${field_i}" == 'on') {
     184
     185                        fieldMapSelection[cat.key] += field
     186
     187                    }
     188
     189                }
     190
     191                if (fieldMapSelection[cat.key] == []) fieldMapSelection.remove(cat.key)
     192
     193            }
     194
     195        }
     196
     197        def measurementTokensSelection = []
     198
     199        if (params."cat_4" == 'on') {
     200
     201            measurementTokensSelection = params.measurementToken == 'null' ? measurementTokens : [ name: params.measurementToken]
     202
     203        }
     204
     205        Assay assay = Assay.get(flash.assayId)
    142206
    143207        // check if assay exists
    144208        if (!assay) {
    145             flash.errorMessage = "No assay found with id: $params.assayId."
     209
     210            flash.errorMessage = flash.assayId ? "No assay found with id: ${flash.assayId}" : 'Assay has no value (null).'
    146211            redirect action: 'selectAssay'
    147212            return
     213
    148214        }
    149215
    150216        try {
    151217
    152             def assayData = assayService.collectAssayData(assay, grailsApplication.config.modules.metabolomics.url)
    153 
    154             def filename = 'export.xlsx'
    155             response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
    156             response.setContentType("application/octet-stream")
    157 
    158             assayService.exportColumnWiseDataToExcelFile(assayData, response.outputStream)
     218            def assayData = assayService.collectAssayData(assay, fieldMapSelection, measurementTokensSelection)
     219
     220            def rowData = assayService.convertColumnToRowStructure(assayData)
     221
     222            flash.rowData = rowData
     223
     224            def assayDataPreview = rowData[0..4].collect{it[0..4]}
     225
     226
     227            [assayDataPreview: assayDataPreview]
    159228
    160229        } catch (Exception e) {
     
    165234        }
    166235    }
     236
     237    def doExport = {
     238
     239        def filename = 'export.xlsx'
     240        response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
     241        response.setContentType("application/octet-stream")
     242        try {
     243           
     244            assayService.exportRowWiseDataToExcelFile(flash.rowData, response.outputStream)
     245
     246        } catch (Exception e) {
     247
     248            flash.errorMessage = e.message
     249            redirect action: 'selectAssay'
     250
     251        }
     252
     253
     254    }
    167255}
  • trunk/grails-app/services/dbnp/studycapturing/AssayService.groovy

    r1455 r1559  
    1616import org.apache.poi.xssf.usermodel.XSSFWorkbook
    1717import org.apache.poi.hssf.usermodel.HSSFWorkbook
    18 import grails.converters.JSON
    19 import javax.servlet.http.HttpServletResponse
    2018
    2119class AssayService {
     
    2422    def authenticationService
    2523    def moduleCommunicationService
     24
     25    /**
     26     * Collects the assay field names per category in a map as well as the
     27     * module's measurements.
     28     *
     29     * @param assay the assay for which to collect the fields
     30     * @return a map of categories as keys and field names or measurements as
     31     *  values
     32     */
     33    def collectAssayTemplateFields(assay) throws Exception {
     34
     35        def getUsedTemplateFieldNames = { templateEntities ->
     36
     37            // gather all unique and non null template fields that haves values
     38            templateEntities*.giveFields().flatten().unique().findAll{ field ->
     39
     40                field && templateEntities.any { it.fieldExists(field.name) && it.getFieldValue(field.name) }
     41
     42            }*.name
     43
     44        }
     45
     46        // check whether module is reachable
     47        if (!moduleCommunicationService.isModuleReachable(assay.module.url)) {
     48
     49            throw new Exception('Module is not reachable')
     50
     51        }
     52
     53        def samples = assay.samples
     54
     55        [   'Subject Data' :            getUsedTemplateFieldNames( samples*."parentSubject".unique() ),
     56            'Sampling Event Data' :     getUsedTemplateFieldNames( samples*."parentEvent".unique() ),
     57            'Sample Data' :             getUsedTemplateFieldNames( samples ),
     58            'Event Group' :             ['name'],
     59            'Module Measurement Data':  requestModuleMeasurementNames(assay)
     60        ]
     61
     62    }
    2663
    2764    /**
     
    3471     *
    3572     * @param assay the assay to collect data for
    36      * @consumer the module url
     73     * @fieldMap map with categories as keys and fields as values
     74     * @measurementTokens selection of measurementTokens
    3775     * @return The assay data structure as described above.
    3876     */
    39     def collectAssayData(assay, consumer) throws Exception {
    40 
    41         def path = "/rest/getMeasurementData?assayToken=$assay.assayUUID"
    42 
    43         // check whether module is reachable
    44         if (!moduleCommunicationService.isModuleReachable(consumer)) {
    45 
    46             throw new Exception('Module is not reachable')
    47 
    48         }
    49 
    50         // Gather sample meta data from GSCF
    51         def samples = assay.samples
    52 
    53         def getUsedTemplateFieldNames = { templateEntities ->
    54 
    55             // gather all unique and non null template fields that haves values
    56             templateEntities*.giveFields().flatten().unique().findAll{ field ->
    57 
    58                 field && templateEntities.any { it.fieldExists(field.name) && it.getFieldValue(field.name) }
    59 
    60             }*.name
    61 
    62         }
     77    def collectAssayData(assay, fieldMap, measurementTokens) throws Exception {
    6378
    6479        def collectFieldValuesForTemplateEntities = { templateFieldNames, templateEntities ->
     
    7893        }
    7994
    80         def getFieldValues = { templateEntities, propertyName = '' ->
     95        def getFieldValues = { templateEntities, fieldNames, propertyName = '' ->
    8196
    8297            def returnValue
     
    86101            if (propertyName == '') {
    87102
    88                 returnValue = collectFieldValuesForTemplateEntities(getUsedTemplateFieldNames(templateEntities), templateEntities)
     103                returnValue = collectFieldValuesForTemplateEntities(fieldNames, templateEntities)
    89104
    90105            } else {
     
    96111                // samples, there may be less than 10 parent subjects. Maybe
    97112                // there's only 1 parent subject. We don't want to collect field
    98                 // values for this subject 10 times ...
    99                 def fieldNames, fieldValues
     113                // values for this single subject 10 times ...
     114                def fieldValues
    100115
    101116                // we'll get the unique list of properties to make sure we're
     
    104119                def uniqueProperties = templateEntities*."$propertyName".unique()
    105120
    106                 fieldNames =    getUsedTemplateFieldNames(uniqueProperties)
    107                 fieldValues =   collectFieldValuesForTemplateEntities(fieldNames, uniqueProperties)
     121                fieldValues = collectFieldValuesForTemplateEntities(fieldNames, uniqueProperties)
    108122
    109123                // prepare a lookup hashMap to be able to map an entities'
     
    137151        }
    138152
    139         [   'Subject Data' :            getFieldValues(samples, 'parentSubject'),
    140             'Sampling Event Data' :     getFieldValues(samples, 'parentEvent'),
    141             'Sample Data' :             getFieldValues(samples),
    142             'Event Group' :             [name: samples*.parentEventGroup*.name.flatten()],
    143             'Module Measurement Data':  requestModuleMeasurements(consumer, path)]
     153        // check whether module is reachable
     154        if (!moduleCommunicationService.isModuleReachable(assay.module.url)) {
     155
     156            throw new Exception('Module is not reachable')
     157
     158        }
     159
     160        def samples = assay.samples
     161
     162        def eventFieldMap = [:]
     163
     164        // check whether event group data was requested
     165        if (fieldMap['Event Group']) {
     166
     167            def names = samples*.parentEventGroup*.name.flatten()
     168
     169            // only set name field when there's actual data
     170            if (!names.every {!it}) eventFieldMap['name'] = names
     171
     172        }
     173
     174        [   'Subject Data' :            getFieldValues(samples, fieldMap['Subject Data'], 'parentSubject'),
     175            'Sampling Event Data' :     getFieldValues(samples, fieldMap['Sampling Event Data'], 'parentEvent'),
     176            'Sample Data' :             getFieldValues(samples, fieldMap['Sample Data']),
     177            'Event Group' :             eventFieldMap,
     178            'Module Measurement Data':  measurementTokens ? requestModuleMeasurements(assay, measurementTokens) : [:]
     179        ]
     180    }
     181
     182    /**
     183     * Retrieves measurement names from the module through a rest call
     184     *
     185     * @param consumer the url of the module
     186     * @param path path of the rest call to the module
     187     * @return
     188     */
     189    def requestModuleMeasurementNames(assay) {
     190
     191        def moduleUrl = assay.module.url
     192
     193        def path = moduleUrl + "/rest/getMeasurementMetaData?assayToken=$assay.assayUUID"
     194
     195        moduleCommunicationService.callModuleRestMethodJSON(moduleUrl, path)
     196
    144197    }
    145198
     
    148201     *
    149202     * @param consumer the url of the module
    150      * @param path of the rest call to the module
     203     * @param path path of the rest call to the module
    151204     * @return
    152205     */
    153     def requestModuleMeasurements(consumer, path) {
    154 
    155         def (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleRestMethodJSON(consumer, consumer+path)
     206    def requestModuleMeasurements(assay, fields) {
     207
     208        def moduleUrl = assay.module.url
     209
     210        def tokenString = ''
     211
     212        fields.each{tokenString+="&measurementToken=${it.name.encodeAsURL()}"}
     213
     214        def path = moduleUrl + "/rest/getMeasurementData?assayToken=$assay.assayUUID" + tokenString
     215       
     216        def (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleRestMethodJSON(moduleUrl, path)
    156217
    157218        if (!sampleTokens?.size()) return []
     
    172233
    173234    /**
    174      * Export column wise data in Excel format to a stream.
    175      *
     235     * Converts column
    176236     * @param columnData multidimensional map containing column data.
    177237     * On the top level, the data must be grouped by category. Each key is the
     
    194254     * | 3          | 6         | 9         | 12        | 15        |
    195255     *
    196      * @param outputStream the stream to write to
    197      * @param useOfficeOpenXML flag to specify xlsx (standard) or xls output
    198      * @return
    199      */
    200     def exportColumnWiseDataToExcelFile(columnData, outputStream, useOfficeOpenXML = true) {
    201 
    202         def convertColumnToRowStructure = { data ->
     256     * @return row wise data
     257     */
     258    def convertColumnToRowStructure(columnData) {
    203259
    204260            // check if all columns have the dimensionality 2
    205             if (data.every { it.value.every { it.value instanceof ArrayList } }) {
     261            if (columnData.every { it.value.every { it.value instanceof ArrayList } }) {
    206262
    207263                def headers = [[],[]]
    208264
    209                 data.each { category ->
     265                columnData.each { category ->
    210266
    211267                    if (category.value.size()) {
     
    225281
    226282                // add all column wise data into 'd'
    227                 data.each { it.value.each { d << it.value } }
     283                columnData.each { it.value.each { d << it.value } }
    228284
    229285                // transpose d into row wise data and combine with header rows
     
    232288
    233289        }
     290
     291    /**
     292     * Export column wise data in Excel format to a stream.
     293     *
     294     * @param columnData Multidimensional map containing column data
     295     * @param outputStream Stream to write to
     296     * @param useOfficeOpenXML Flag to specify xlsx (standard) or xls output
     297     * @return
     298     */
     299    def exportColumnWiseDataToExcelFile(columnData, outputStream, useOfficeOpenXML = true) {
     300
    234301        // transform data into row based structure for easy writing
    235302        def rows = convertColumnToRowStructure(columnData)
     
    250317     * Export row wise data in Excel format to a stream
    251318     *
    252      * @param rowData a list of lists containing for each row all cell values
    253      * @param outputStream the stream to write to
    254      * @param useOfficeOpenXML flag to specify xlsx (standard) or xls output
     319     * @param rowData List of lists containing for each row all cell values
     320     * @param outputStream Stream to write to
     321     * @param useOfficeOpenXML Flag to specify xlsx (standard) or xls output
    255322     * @return
    256323     */
  • trunk/grails-app/views/assay/selectAssay.gsp

    r1445 r1559  
    1515        var a = eval(jsonData);
    1616        var sel = $('#'+selectID).empty();
    17        
     17
    1818        $(a).each(function(i, el){
    1919          sel.append($("<option></option>").attr("value",el.id).text(el.name))
     
    3333    ${flash.errorMessage}
    3434  </div>
    35   <g:form name="assaySelect" action="exportAssayAsExcel">
     35  %{--<g:form name="assaySelect" action="exportAssayAsExcel">--}%
     36  <g:form name="assaySelect" action="selectFields">
    3637    <g:select optionKey="id" optionValue="title" name="studyId" from="${userStudies}" id="study"
    3738      onChange="${remoteFunction(controller:'study',action:'ajaxGetAssays',params:'\'id=\'+escape(this.value)',onComplete: 'updateAssay(XMLHttpRequest.responseText, \'assay\')')}"/>
  • trunk/test/unit/dbnp/studycapturing/AssayControllerTests.groovy

    r1455 r1559  
    2626
    2727    void testWrongAssayID() {
    28         mockParams.assayId = 3
     28        mockFlash.assayId = 3
    2929
    30         controller.exportAssayAsExcel()
     30        controller.compileExportData()
    3131
    3232        assertEquals 'Redirected action should match', [action: 'selectAssay'], redirectArgs
    33         assertEquals 'Error message', 'No assay found with id: 3.', mockFlash.errorMessage
     33        assertEquals 'Error message', 'No assay found with id: 3', mockFlash.errorMessage
    3434    }
    3535
    3636    void testExceptionHandling() {
    37         mockParams.assayId = 1
     37        mockFlash.assayId = 1
    3838
    3939        controller.metaClass.'grailsApplication' = [
     
    4343        controller.assayService = [
    4444
    45                 collectAssayData:                   {a, b -> throw new Exception('msg1') },
    46                 exportColumnWiseDataToExcelFile:    {a, b -> throw new Exception('msg2') }
     45                collectAssayData:               {a, b, c -> throw new Exception('msg1') },
     46                convertColumnToRowStructure:    {a -> throw new Exception('msg2')},
     47                exportRowWiseDataToExcelFile:   {a, b -> throw new Exception('msg3') }
    4748
    4849        ]
    4950
    50         controller.exportAssayAsExcel()
     51        controller.compileExportData()
    5152
    5253        assertEquals 'Redirected action should match', [action: 'selectAssay'], redirectArgs
    5354        assertEquals 'Error message', 'msg1', mockFlash.errorMessage
    5455
    55         controller.assayService.collectAssayData = {a, b -> true}
    56         controller.exportAssayAsExcel()
     56        controller.assayService.collectAssayData = {a, b, c -> true}
     57        controller.compileExportData()
    5758
    5859        assertEquals 'Redirected action should match', [action: 'selectAssay'], redirectArgs
    5960        assertEquals 'Error message', 'msg2', mockFlash.errorMessage
    6061
     62        controller.doExport()
     63
     64        assertEquals 'Redirected action should match', [action: 'selectAssay'], redirectArgs
     65        assertEquals 'Error message', 'msg3', mockFlash.errorMessage
     66
    6167    }
    6268
  • trunk/test/unit/dbnp/studycapturing/AssayServiceTests.groovy

    r1545 r1559  
    77import grails.converters.JSON
    88import org.dbnp.gdt.*
     9import org.dbnp.gdt.AssayModule
     10import org.dbnp.gdt.TemplateEntity
     11import groovy.mock.interceptor.MockFor
     12import org.dbnp.gdt.GdtService
     13import org.dbnp.gdt.TemplateStringField
    914
    1015/**
     
    3742                                    new Template(id: 2, fields: [TemplateField.get(3)])])
    3843
    39         mockDomain(Subject,       [ new Subject(id: 1, name:'subject1', template: Template.get(1), species: Term.get(1)),
    40                                     new Subject(id: 2, name:'subject2', template: Template.get(2), species: Term.get(1))])
     44        def mockGdtService = [getTemplateFieldTypeByCasedName: { a -> TemplateStringField }]
     45        def mockLog = [ info:{a->println a},error:{a->println "Error: $a"}]
     46
     47        mockDomain(Subject,       [ new Subject(gdtService: mockGdtService, id: 1, name:'subject1', template: Template.get(1), species: Term.get(1)),
     48                                    new Subject(gdtService: mockGdtService, id: 2, name:'subject2', template: Template.get(2), species: Term.get(1))])
    4149
    4250        mockDomain(SamplingEvent, [ new SamplingEvent(id:1, startTime: 2, duration: 5, sampleTemplate: new Template()),
     
    5159                                    new Sample(id: 2, name:'sample2', parentSubject: Subject.get(2), parentEvent: SamplingEvent.get(2), parentEventGroup: EventGroup.get(2))])
    5260
    53         mockDomain(Assay,         [ new Assay(id: 1, externalAssayID: 'assay1', samples:[Sample.get(1),Sample.get(2)]),
    54                                     new Assay(id: 2, externalAssayID: 'assay1', samples:[])])
     61        mockDomain(AssayModule,   [ new AssayModule(id: 1, url: 'http://www.example.com') ])
     62
     63        mockDomain(Assay,         [ new Assay(id: 1, externalAssayID: 'assay1', module: AssayModule.get(1), samples: [Sample.get(1),Sample.get(2)]),
     64                                    new Assay(id: 2, externalAssayID: 'assay1', module: AssayModule.get(1), samples: [])])
     65
     66        Subject.get(1).metaClass.static.log = mockLog
    5567
    5668        Subject.get(1).setFieldValue('tf1', 'tfv1')
     
    186198        def assay = Assay.get(1)
    187199
    188         def consumer = 'http://metabolomics.nmcdsp.nl'
    189 
    190 //        // mock URL's getText to be able to mock a http request
    191 //        URL.metaClass.getText = {
    192 //            new JSON ([['sample1', 'sample2', 'sample3'],
    193 //              ['measurement1','measurement2','measurement3','measurement4'],
    194 //              [1,2,3,4,5,6,7,8,9,10,11,12] ]).toString()
    195 //        }
    196 
    197         def assayData = service.collectAssayData(assay, consumer)
    198 
    199         println assayData
     200
     201//        collectAssayData(assay, fieldMap, measurementTokens)
     202
     203        def fieldMap = [
     204                'Subject Data':['tf1','tf2','tf3','species','name'],
     205                'Sampling Event Data':['startTime','duration'],
     206                'Sample Data':['name'],
     207                'Event Group':['name']
     208        ]
     209
     210        def measurementTokens = [[name:'measurement1'], [name:'measurement2'], [name:'measurement3'], [name:'measurement4']]
     211
     212        String.metaClass.'encodeAsURL' = {delegate}
     213
     214        def assayData = service.collectAssayData(assay, fieldMap, measurementTokens)
    200215
    201216        def sample1index = assayData.'Sample Data'.'name'.findIndexOf{it == 'sample1'}
     
    210225        assertEquals 'Sampling event template fields', [2,12], assayData.'Sampling Event Data'.startTime[sample1index, sample2index]
    211226        assertEquals 'Sampling event template fields', [5,15], assayData.'Sampling Event Data'.duration[sample1index, sample2index]
    212         assertEquals 'Sampling event template fields', '[null, null]', assayData.'Sampling Event Data'.sampleTemplate.toString()
     227//        assertEquals 'Sampling event template fields', '[null, null]', assayData.'Sampling Event Data'.sampleTemplate.toString()
    213228        assertEquals 'Sample template fields', ['sample1', 'sample2'], assayData.'Sample Data'.name[sample1index, sample2index]
    214229
Note: See TracChangeset for help on using the changeset viewer.