From 076b04a895fc09090ece31e9a317e5f79c4127d5 Mon Sep 17 00:00:00 2001 From: David Milward Date: Tue, 26 Jun 2018 18:35:56 +0100 Subject: [PATCH 01/24] Adding in task name to collection and adapting gsp pages --- gradle.properties | 24 ++++++++++--------- grails-app/conf/application.yml | 2 +- .../RecordCollectionController.groovy | 3 ++- .../sentinel/RecordFileCommand.groovy | 1 + .../RecordCollectionGormEntity.groovy | 4 ++++ .../sentinel/BootStrap.groovy | 3 ++- .../sentinel/CsvImportProcessorService.groovy | 2 +- .../sentinel/CsvImportService.groovy | 4 +++- .../sentinel/ExcelImportService.groovy | 2 +- .../RecordCollectionGormService.groovy | 2 +- .../views/recordCollection/importCsv.gsp | 4 ++++ grails-app/views/recordCollection/index.gsp | 2 ++ .../sentinel/CsvImport.groovy | 2 +- 13 files changed, 36 insertions(+), 19 deletions(-) diff --git a/gradle.properties b/gradle.properties index aa3c774..5e6cf44 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,17 @@ +#Tue Jun 26 14:44:22 BST 2018 +jsonViewsVersion=1.2.7 +webdriverBinariesGradleVersion=1.4 +grailsWrapperVersion=1.0.0 +poiVersion=3.17 grailsVersion=3.3.2 -gormVersion=6.1.8.RELEASE +hamcrestVersion=1.3 +seleniumVersion=3.6.0 gradleWrapperVersion=3.5 -droolsVersion=7.5.0.Final -okHttpVersion=3.9.1 -moshiVersion=1.5.0 -mysqlVersion=5.1.46 +gormVersion=6.1.8.RELEASE grailsExecutorVersion=0.4 -seleniumVersion=3.6.0 -assetPipelineVersion=2.14.8 -webdriverBinariesGradleVersion=1.4 -poiVersion=3.17 -jsonViewsVersion=1.2.7 +mysqlVersion=5.1.46 ersatzVersion=1.6.2 -hamcrestVersion=1.3 \ No newline at end of file +assetPipelineVersion=2.14.8 +okHttpVersion=3.9.1 +moshiVersion=1.5.0 +droolsVersion=7.5.0.Final diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index bc9f99d..0fc35c6 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -107,7 +107,7 @@ dataSource: environments: development: dataSource: - dbCreate: update + dbCreate: create url: '${JDBC_CONNECTION_STRING}' test: dataSource: diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy index b81ec14..4b0867c 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy @@ -98,8 +98,9 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon log.debug 'Content Type {}', cmd.csvFile.contentType InputStream inputStream = cmd.csvFile.inputStream Integer batchSize = cmd.batchSize + String datasetName = cmd.datasetName CsvImport importService = csvImportByContentType (ImportContentType.of(cmd.csvFile.contentType)) - importService.save(inputStream, batchSize) + importService.save(inputStream, datasetName, batchSize) redirect controller: 'recordCollection', action: 'index' } diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy index b54acc8..c8c7444 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy @@ -8,6 +8,7 @@ import org.springframework.web.multipart.MultipartFile class RecordFileCommand implements Validateable { MultipartFile csvFile Integer batchSize = 100 + String datasetName static constraints = { batchSize nullable: false diff --git a/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy b/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy index 331cc04..90b964d 100644 --- a/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy +++ b/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy @@ -5,6 +5,8 @@ import grails.compiler.GrailsCompileStatic @GrailsCompileStatic class RecordCollectionGormEntity { + String datasetName + Date dateCreated Date lastUpdated @@ -17,6 +19,8 @@ class RecordCollectionGormEntity { static constraints = { records nullable: true mappings nullable: true + datasetName blank: true + datasetName nullable: true } static mapping = { diff --git a/grails-app/init/uk/co/metadataconsulting/sentinel/BootStrap.groovy b/grails-app/init/uk/co/metadataconsulting/sentinel/BootStrap.groovy index b454d23..cbb7bf5 100644 --- a/grails-app/init/uk/co/metadataconsulting/sentinel/BootStrap.groovy +++ b/grails-app/init/uk/co/metadataconsulting/sentinel/BootStrap.groovy @@ -17,7 +17,8 @@ class BootStrap { List mapping = mappingGormUrl() File f = new File('src/test/resources/DIDS_XMLExample_01.csv') InputStream inputStream = f.newInputStream() - csvImportService.save(inputStream, 100) + String datasetName = "SampleDataset" + csvImportService.save(inputStream, datasetName, 100) } List mappingGormUrl() { diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportProcessorService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportProcessorService.groovy index 7852bdd..a23f6dc 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportProcessorService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportProcessorService.groovy @@ -40,7 +40,7 @@ class CsvImportProcessorService implements GrailsConfigurationAware, CsvImportPr } @Override - int processInputStream(InputStream inputStream, Integer batchSize, Closure headerListClosure, Closure cls) { + int processInputStream(InputStream inputStream, Integer batchSize, Closure headerListClosure, Closure cls) { int processed = 0 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) List lines = [] diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy index ca5384e..1883bc6 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy @@ -26,8 +26,10 @@ class CsvImportService implements CsvImport, Benchmark { @CompileDynamic @Override - void save(InputStream inputStream, Integer batchSize) { + void save(InputStream inputStream, String datasetName, Integer batchSize ) { + RecordCollectionGormEntity recordCollection = recordCollectionGormService.save() + recordCollection.datasetName = datasetName executorService.submit { log.info 'fetching validation rules' diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy index d859577..43d8878 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy @@ -20,7 +20,7 @@ class ExcelImportService implements CsvImport, Benchmark { @CompileDynamic @Override - void save(InputStream inputStream, Integer batchSize) { + void save(InputStream inputStream, String datasetName, Integer batchSize) { RecordCollectionGormEntity recordCollection = recordCollectionGormService.save() executorService.submit { diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy index 6cf2df9..fbd35a3 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy @@ -39,7 +39,7 @@ class RecordCollectionGormService implements GormErrorsMessage { @Transactional RecordCollectionGormEntity save() { RecordCollectionGormEntity recordCollection = new RecordCollectionGormEntity() - if ( !recordCollection.save() ) { + if ( !recordCollection.save(validate:false) ) { log.warn '{}', errorsMsg(recordCollection, messageSource) } recordCollection diff --git a/grails-app/views/recordCollection/importCsv.gsp b/grails-app/views/recordCollection/importCsv.gsp index 36c35c4..3491e13 100644 --- a/grails-app/views/recordCollection/importCsv.gsp +++ b/grails-app/views/recordCollection/importCsv.gsp @@ -23,6 +23,10 @@ +
+ +
diff --git a/grails-app/views/recordCollection/index.gsp b/grails-app/views/recordCollection/index.gsp index 8fe5b0b..0420dec 100644 --- a/grails-app/views/recordCollection/index.gsp +++ b/grails-app/views/recordCollection/index.gsp @@ -18,6 +18,7 @@ + @@ -25,6 +26,7 @@ +
${recordCollection.datasetName} ${recordCollection.lastUpdated} diff --git a/src/main/groovy/uk/co/metadataconsulting/sentinel/CsvImport.groovy b/src/main/groovy/uk/co/metadataconsulting/sentinel/CsvImport.groovy index 453c201..bb98c5b 100644 --- a/src/main/groovy/uk/co/metadataconsulting/sentinel/CsvImport.groovy +++ b/src/main/groovy/uk/co/metadataconsulting/sentinel/CsvImport.groovy @@ -4,5 +4,5 @@ import groovy.transform.CompileStatic @CompileStatic interface CsvImport { - void save(InputStream inputStream, Integer batchSize) + void save(InputStream inputStream, String datasetName, Integer batchSize) } \ No newline at end of file From 3f7ef93bceb233f4c438fe0dd71bcd7d865dacb7 Mon Sep 17 00:00:00 2001 From: David Milward Date: Thu, 28 Jun 2018 15:52:56 +0100 Subject: [PATCH 02/24] Added basic code to export validated record list as csv file --- .../RecordCollectionController.groovy | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy index 4b0867c..be2569b 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy @@ -1,11 +1,14 @@ package uk.co.metadataconsulting.sentinel -import grails.config.Config -import grails.core.support.GrailsConfigurationAware + import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import org.springframework.context.MessageSource import uk.co.metadataconsulting.sentinel.modelcatalogue.ValidationRules +import grails.config.Config +import grails.core.support.GrailsConfigurationAware + +import static org.springframework.http.HttpStatus.OK @Slf4j @CompileStatic @@ -28,6 +31,8 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon RecordCollectionGormService recordCollectionGormService + RecordGormService recordGormService + RecordCollectionService recordCollectionService ExcelImportService excelImportService @@ -68,6 +73,36 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon ] } + def exportValidRecords(Long recordCollectionId){ + + String csvMimeType + String encoding + + final String filename = 'dataset_valid.csv' + List recordPortionMappingList = recordCollectionMappingGormService.findAllByRecordCollectionId(recordCollectionId) + + def validCsvExport = response.outputStream + response.status = OK.value() + response.contentType = "${csvMimeType};charset=${encoding}"; + response.setHeader "Content-disposition", "attachment; filename=${filename}" + + recordPortionMappingList.each { RecordPortionMapping record -> + + RecordGormEntity dataRecord = recordGormService.findById(record.id) + + if (dataRecord.validate()) { + validCsvExport << "${dataRecord}," + }else{ + println dataRecord + } + } + validCsvExport.flush() + validCsvExport.close() + + + redirect action: 'index', controller: 'record', params: [recordCollectionId: recordCollectionId] + } + def importCsv() { [:] } From 71e674fa1db5e0222d423bce2dfed3091d0df3e9 Mon Sep 17 00:00:00 2001 From: David Milward Date: Thu, 28 Jun 2018 18:00:01 +0100 Subject: [PATCH 03/24] Update to use the Excel plugin --- .../RecordCollectionController.groovy | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy index be2569b..f2d699f 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy @@ -4,6 +4,8 @@ package uk.co.metadataconsulting.sentinel import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import org.springframework.context.MessageSource +import pl.touk.excel.export.WebXlsxExporter +import pl.touk.excel.export.XlsxExporter import uk.co.metadataconsulting.sentinel.modelcatalogue.ValidationRules import grails.config.Config import grails.core.support.GrailsConfigurationAware @@ -11,7 +13,6 @@ import grails.core.support.GrailsConfigurationAware import static org.springframework.http.HttpStatus.OK @Slf4j -@CompileStatic class RecordCollectionController implements ValidateableErrorsMessage, GrailsConfigurationAware { static allowedMethods = [ @@ -103,6 +104,36 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon redirect action: 'index', controller: 'record', params: [recordCollectionId: recordCollectionId] } + def exportValidExcel(Long recordCollectionId) { + + String csvMimeType + String encoding + + final String filename = 'dataset_valid.csv' + //List recordPortionMappingList = recordCollectionMappingGormService.findAllByRecordCollectionId(recordCollectionId) + List recordGormEntityList = recordGormService.findAllByRecordCollectionId(recordCollectionId) + List dataRecordsList = new ArrayList() + List headers = new ArrayList() + recordGormEntityList.each { RecordGormEntity record -> + record.portions.each { RecordPortionGormEntity portion -> + headers.add(portion.header) + } + } + + // new XlsxExporter('/tmp/myReportFile.xlsx'). + // add(dataRecordsList, properties).save() + new WebXlsxExporter().with { + setResponseHeaders(response) + fillHeader(headers) + add(dataRecordsList ) + save(response.outputStream) + } + + + redirect action: 'index', controller: 'record', params: [recordCollectionId: recordCollectionId] + } + + def importCsv() { [:] } From 98a94c767d4fb06308f0fc4b4abf654811f6d1f3 Mon Sep 17 00:00:00 2001 From: David Milward Date: Fri, 29 Jun 2018 11:36:42 +0100 Subject: [PATCH 04/24] Added in Excel Plugin --- .../sentinel/RecordIndexCommand.groovy | 1 + .../metadataconsulting/sentinel/UrlMappings.groovy | 1 + .../sentinel/RecordGormService.groovy | 7 +++++++ grails-app/views/record/index.gsp | 12 ++++++++---- grails-app/views/record/show.gsp | 1 + 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordIndexCommand.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordIndexCommand.groovy index ea73a52..7d98d71 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordIndexCommand.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordIndexCommand.groovy @@ -9,6 +9,7 @@ class RecordIndexCommand implements Validateable { Integer offset Integer max Long recordCollectionId + //String datasetName RecordCorrectnessDropdown correctness = RecordCorrectnessDropdown.ALL static constraints = { diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy index c5f665c..1a9d692 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy @@ -7,6 +7,7 @@ class UrlMappings { "/recordCollection/cloneMapping"(controller: 'recordCollection', action: 'cloneMapping') "/recordCollection/cloneSave"(controller: 'recordCollection', action: 'cloneSave', httpMethod: 'POST') "/recordCollection/validate"(controller: 'recordCollection', action: 'validate') + "/recordCollection/exportValidExcel"(controller: 'recordCollection', action: 'exportValidExcel') "/recordCollection/delete"(controller: 'recordCollection', action: 'delete') "/recordCollection/$recordCollectionId/mapping"(controller: 'recordCollection', action: 'headersMapping') "/recordCollectionMapping/catalogueElements/$dataModelId"(controller: 'recordCollectionMapping', action: 'catalogueElements') diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy index 1fc6833..a8aa83d 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy @@ -85,6 +85,13 @@ class RecordGormService implements GormErrorsMessage { query.list(paginationQuery.toMap()) } + @ReadOnly + List findAllByRecordCollectionId(Long recordCollectionId ) { + DetachedCriteria query = queryByRecordCollectionId(recordCollectionId) + return query as List + + } + @ReadOnly RecordGormEntity findById(Long recordId, List joinProperties = null) { DetachedCriteria query = queryById(recordId) diff --git a/grails-app/views/record/index.gsp b/grails-app/views/record/index.gsp index 2e0c5cf..97391db 100644 --- a/grails-app/views/record/index.gsp +++ b/grails-app/views/record/index.gsp @@ -10,10 +10,10 @@
    +
From fe540aac7091728ab43f7cdb4fd09694a8c67d6a Mon Sep 17 00:00:00 2001 From: sdelamo Date: Fri, 29 Jun 2018 13:19:07 +0200 Subject: [PATCH 05/24] datasetName in a GormService and test --- .../sentinel/RecordFileCommand.groovy | 1 + .../RecordCollectionGormEntity.groovy | 3 +-- .../sentinel/CsvImportService.groovy | 3 +-- .../sentinel/ExcelImportService.groovy | 2 +- .../RecordCollectionGormService.groovy | 3 ++- ...CollectionControllerIntegrationSpec.groovy | 2 +- .../RecordControllerIntegrationSpec.groovy | 2 +- ...CollectionGormEntityConstraintsSpec.groovy | 22 +++++++++++++++++++ .../RecordFileCommandConstraintsSpec.groovy | 14 ++++++++++++ 9 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntityConstraintsSpec.groovy diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy index c8c7444..14cee17 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordFileCommand.groovy @@ -11,6 +11,7 @@ class RecordFileCommand implements Validateable { String datasetName static constraints = { + datasetName nullable: false, blank: false batchSize nullable: false csvFile validator: { MultipartFile val, RecordFileCommand obj -> if ( val == null ) { diff --git a/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy b/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy index 90b964d..e7bff5d 100644 --- a/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy +++ b/grails-app/domain/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntity.groovy @@ -19,8 +19,7 @@ class RecordCollectionGormEntity { static constraints = { records nullable: true mappings nullable: true - datasetName blank: true - datasetName nullable: true + datasetName nullable: false, blank: false } static mapping = { diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy index 1883bc6..e8f3add 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/CsvImportService.groovy @@ -28,8 +28,7 @@ class CsvImportService implements CsvImport, Benchmark { @Override void save(InputStream inputStream, String datasetName, Integer batchSize ) { - RecordCollectionGormEntity recordCollection = recordCollectionGormService.save() - recordCollection.datasetName = datasetName + RecordCollectionGormEntity recordCollection = recordCollectionGormService.save(datasetName) executorService.submit { log.info 'fetching validation rules' diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy index 43d8878..58b60ea 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelImportService.groovy @@ -21,7 +21,7 @@ class ExcelImportService implements CsvImport, Benchmark { @CompileDynamic @Override void save(InputStream inputStream, String datasetName, Integer batchSize) { - RecordCollectionGormEntity recordCollection = recordCollectionGormService.save() + RecordCollectionGormEntity recordCollection = recordCollectionGormService.save(datasetName) executorService.submit { log.info 'fetching validation rules' diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy index fbd35a3..7cbe09f 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionGormService.groovy @@ -37,8 +37,9 @@ class RecordCollectionGormService implements GormErrorsMessage { } @Transactional - RecordCollectionGormEntity save() { + RecordCollectionGormEntity save(String datasetName) { RecordCollectionGormEntity recordCollection = new RecordCollectionGormEntity() + recordCollection.datasetName = datasetName if ( !recordCollection.save(validate:false) ) { log.warn '{}', errorsMsg(recordCollection, messageSource) } diff --git a/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordCollectionControllerIntegrationSpec.groovy b/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordCollectionControllerIntegrationSpec.groovy index a90a3a1..0deb0b6 100644 --- a/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordCollectionControllerIntegrationSpec.groovy +++ b/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordCollectionControllerIntegrationSpec.groovy @@ -50,7 +50,7 @@ class RecordCollectionControllerIntegrationSpec extends Specification { ruleFetcherService.metadataUrl = ersatz.httpUrl when: - RecordCollectionGormEntity recordCollection= recordCollectionGormService.save() + RecordCollectionGormEntity recordCollection= recordCollectionGormService.save("Test") then: recordCollectionGormService.count() == old(recordCollectionGormService.count()) + 1 diff --git a/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordControllerIntegrationSpec.groovy b/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordControllerIntegrationSpec.groovy index e23dc94..6b4ac73 100644 --- a/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordControllerIntegrationSpec.groovy +++ b/src/integration-test/groovy/uk.co.metadataconsulting.sentinel/RecordControllerIntegrationSpec.groovy @@ -57,7 +57,7 @@ class RecordControllerIntegrationSpec extends Specification { ruleFetcherService.metadataUrl = ersatz.httpUrl when: - RecordCollectionGormEntity recordCollection= recordCollectionGormService.save() + RecordCollectionGormEntity recordCollection= recordCollectionGormService.save("Test") then: recordCollectionGormService.count() == old(recordCollectionGormService.count()) + 1 diff --git a/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntityConstraintsSpec.groovy b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntityConstraintsSpec.groovy new file mode 100644 index 0000000..c129fd4 --- /dev/null +++ b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionGormEntityConstraintsSpec.groovy @@ -0,0 +1,22 @@ +package uk.co.metadataconsulting.sentinel + +import grails.testing.gorm.DomainUnitTest +import spock.lang.Specification + +class RecordCollectionGormEntityConstraintsSpec extends Specification implements DomainUnitTest { + + void 'verify datasetName is required and cannot be null'() { + when: + domain.datasetName = null + + then: + !domain.validate(['datasetName']) + + when: + domain.datasetName = '' + + then: + !domain.validate(['datasetName']) + } + +} diff --git a/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordFileCommandConstraintsSpec.groovy b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordFileCommandConstraintsSpec.groovy index 69acc3b..a8a0195 100644 --- a/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordFileCommandConstraintsSpec.groovy +++ b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordFileCommandConstraintsSpec.groovy @@ -18,4 +18,18 @@ class RecordFileCommandConstraintsSpec extends Specification { !cmd.validate(['batchSize']) cmd.errors['batchSize'].code == 'nullable' } + + void 'verify datasetName is required and cannot be null'() { + when: + cmd.datasetName = null + + then: + !cmd.validate(['datasetName']) + + when: + cmd.datasetName = '' + + then: + !cmd.validate(['datasetName']) + } } From fa81307bde6b21f59403bfa735312bd18057d09f Mon Sep 17 00:00:00 2001 From: sdelamo Date: Fri, 29 Jun 2018 14:50:29 +0200 Subject: [PATCH 06/24] CSV Export --- .../RecordCollectionController.groovy | 70 ++++++++++--------- .../sentinel/UrlMappings.groovy | 1 + .../sentinel/ExcelGeneratorService.groovy | 0 .../RecordCollectionExportService.groovy | 30 ++++++++ .../sentinel/RecordGormService.groovy | 15 ++++ .../sentinel/RecordService.groovy | 57 ++++++++------- .../sentinel/RecordPortionUtils.groovy | 17 +++++ grails-app/views/record/index.gsp | 2 +- .../sentinel/RecordPortion.groovy | 10 +++ .../RecordCollectionExportRowView.groovy | 13 ++++ .../export/RecordCollectionExportView.groovy | 9 +++ ...lectionControllerAllowedMethodsSpec.groovy | 30 ++++++++ 12 files changed, 196 insertions(+), 58 deletions(-) create mode 100644 grails-app/services/uk/co/metadataconsulting/sentinel/ExcelGeneratorService.groovy create mode 100644 grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy create mode 100644 grails-app/utils/uk/co/metadataconsulting/sentinel/RecordPortionUtils.groovy create mode 100644 src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportRowView.groovy create mode 100644 src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportView.groovy diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy index f2d699f..63d1ad6 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy @@ -1,11 +1,11 @@ package uk.co.metadataconsulting.sentinel - -import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import org.springframework.context.MessageSource -import pl.touk.excel.export.WebXlsxExporter -import pl.touk.excel.export.XlsxExporter +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportRowView +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportView + +//import pl.touk.excel.export.WebXlsxExporter import uk.co.metadataconsulting.sentinel.modelcatalogue.ValidationRules import grails.config.Config import grails.core.support.GrailsConfigurationAware @@ -24,6 +24,7 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon headersMapping: 'GET', cloneMapping: 'GET', cloneSave: 'POST', + exportValidRecords: 'GET' ] MessageSource messageSource @@ -42,15 +43,23 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon RuleFetcherService ruleFetcherService + + RecordCollectionExportService recordCollectionExportService + int defaultPaginationMax = 25 int defaultPaginationOffset = 0 + String csvMimeType + String encoding @Override void setConfiguration(Config co) { defaultPaginationMax = co.getProperty('sentinel.pagination.max', Integer, 25) defaultPaginationOffset = co.getProperty('sentinel.pagination.offset', Integer, 0) + csvMimeType = co.getProperty('grails.mime.types.csv', String, 'text/csv') + encoding = co.getProperty('grails.converters.encoding', String, 'UTF-8') } + def index() { Integer max = params.int('max') ?: defaultPaginationMax @@ -76,32 +85,30 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon def exportValidRecords(Long recordCollectionId){ - String csvMimeType - String encoding - final String filename = 'dataset_valid.csv' - List recordPortionMappingList = recordCollectionMappingGormService.findAllByRecordCollectionId(recordCollectionId) - - def validCsvExport = response.outputStream - response.status = OK.value() - response.contentType = "${csvMimeType};charset=${encoding}"; - response.setHeader "Content-disposition", "attachment; filename=${filename}" - recordPortionMappingList.each { RecordPortionMapping record -> - - RecordGormEntity dataRecord = recordGormService.findById(record.id) - - if (dataRecord.validate()) { - validCsvExport << "${dataRecord}," - }else{ - println dataRecord + def outs = response.outputStream + response.status = OK.value() + response.contentType = "${csvMimeType};charset=${encoding}" + response.setHeader "Content-disposition", "attachment; filename=${filename}" + final String separator = ';' + RecordCollectionExportView view = recordCollectionExportService.export(recordCollectionId) + List headers = view.headers + // TODO remove this line + headers = headers.collect { [it, it, it, it, it, it] }.flatten() + outs << "${headers.join(separator)}\n" + + if ( view.rows ) { + List portionsHeaders = view.rows.first().recordPortionList.collect { RecordPortion.toHeaderCsv(separator) } + String line = "${portionsHeaders.join(separator)}\n" + outs << line + view.rows.each { RecordCollectionExportRowView row -> + outs << "${row.toCsv(separator)}\n" } } - validCsvExport.flush() - validCsvExport.close() - - redirect action: 'index', controller: 'record', params: [recordCollectionId: recordCollectionId] + outs.flush() + outs.close() } def exportValidExcel(Long recordCollectionId) { @@ -122,18 +129,17 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon // new XlsxExporter('/tmp/myReportFile.xlsx'). // add(dataRecordsList, properties).save() - new WebXlsxExporter().with { - setResponseHeaders(response) - fillHeader(headers) - add(dataRecordsList ) - save(response.outputStream) - } +// new WebXlsxExporter().with { +// setResponseHeaders(response) +// fillHeader(headers) +// add(dataRecordsList ) +// save(response.outputStream) +// } redirect action: 'index', controller: 'record', params: [recordCollectionId: recordCollectionId] } - def importCsv() { [:] } diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy index 1a9d692..abf2ea5 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy @@ -7,6 +7,7 @@ class UrlMappings { "/recordCollection/cloneMapping"(controller: 'recordCollection', action: 'cloneMapping') "/recordCollection/cloneSave"(controller: 'recordCollection', action: 'cloneSave', httpMethod: 'POST') "/recordCollection/validate"(controller: 'recordCollection', action: 'validate') + "/recordCollection/exportValidRecords"(controller: 'recordCollection', action: 'exportValidRecords') "/recordCollection/exportValidExcel"(controller: 'recordCollection', action: 'exportValidExcel') "/recordCollection/delete"(controller: 'recordCollection', action: 'delete') "/recordCollection/$recordCollectionId/mapping"(controller: 'recordCollection', action: 'headersMapping') diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelGeneratorService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/ExcelGeneratorService.groovy new file mode 100644 index 0000000..e69de29 diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy new file mode 100644 index 0000000..a2c2631 --- /dev/null +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy @@ -0,0 +1,30 @@ +package uk.co.metadataconsulting.sentinel + +import groovy.transform.CompileStatic +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportRowView +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportView + +@CompileStatic +class RecordCollectionExportService { + + RecordService recordService + + RecordGormService recordGormService + + RecordCollectionExportView export(Long recordCollectionId, RecordCorrectnessDropdown correctnes = RecordCorrectnessDropdown.VALID) { + List recordIds = recordService.findAllIdsByRecordCollectionId(recordCollectionId, correctnes, null) + List recordGormEntityList = recordGormService.findAllByIds(recordIds) + List rows = [] + List headers + if ( recordGormEntityList ) { + headers = recordGormEntityList.first().portions*.header + for ( RecordGormEntity recordGormEntity : recordGormEntityList ) { + List recordPortionList = recordGormEntity.portions.collect { RecordPortionGormEntity recordPortionGormEntity -> + RecordPortionUtils.of(recordPortionGormEntity) } + rows << new RecordCollectionExportRowView(recordPortionList: recordPortionList) + } + } + + new RecordCollectionExportView(rows: rows, headers: headers) + } +} \ No newline at end of file diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy index a8aa83d..d0497cd 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordGormService.groovy @@ -111,4 +111,19 @@ class RecordGormService implements GormErrorsMessage { Number count() { RecordGormEntity.count() } + + @ReadOnly + List findAllByIds(List ids, List propertiesToJoin = ['portions']) { + DetachedCriteria query = queryAllByIds(ids) + if ( propertiesToJoin ) { + for ( String propertyName : propertiesToJoin ) { + query.join('portions') + } + } + query.list() + } + + DetachedCriteria queryAllByIds(List ids) { + RecordGormEntity.where { id in ids } + } } \ No newline at end of file diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordService.groovy index 096b549..f927042 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/RecordService.groovy @@ -40,6 +40,8 @@ class RecordService { } as Set } + + @Transactional void validate(Long recordId, List recordPortionMappingList, Map validationRulesMap) { DetachedCriteria query = recordGormService.queryById(recordId) @@ -54,32 +56,37 @@ class RecordService { } List findAllByRecordCollectionId(Long recordCollectionId, RecordCorrectnessDropdown correctness, PaginationQuery paginationQuery) { - // TODO Do this with a query - DetachedCriteria query = recordGormService.queryByRecordCollectionId(recordCollectionId) - //query.join('portions') - - if ( correctness == RecordCorrectnessDropdown.ALL ) { - Map args = paginationQuery.toMap() - - List l = query.list(args) - - List ids = query.id().list(args) as List - Set invalidRecordIds = findAllInvalidRecordIds() - - return ids.collect { Long id -> - new RecordViewModel(id: id, valid: !invalidRecordIds.contains(id)) - } + switch (correctness) { + case RecordCorrectnessDropdown.ALL: + List ids = findAllIdsByRecordCollectionId(recordCollectionId, correctness, paginationQuery) + Set invalidRecordIds = findAllInvalidRecordIds() + return ids.collect { Long id -> + new RecordViewModel(id: id, valid: !invalidRecordIds.contains(id)) + } + case RecordCorrectnessDropdown.VALID: + return findAllIdsByRecordCollectionId(recordCollectionId, correctness, paginationQuery).collect { + new RecordViewModel(id: it, valid: true) + } + case RecordCorrectnessDropdown.INVALID: + return findAllIdsByRecordCollectionId(recordCollectionId, correctness, paginationQuery).collect { + new RecordViewModel(id: it, valid: false) + } } + } - boolean valid = validForCorrectnes(correctness) - - if ( valid ) { - return findAllValidRecords(recordCollectionId, paginationQuery).collect { - new RecordViewModel(id:it, valid: true) - } - } - return findAllInvalidRecords(recordCollectionId, paginationQuery).collect { - new RecordViewModel(id:it, valid: false) + List findAllIdsByRecordCollectionId(Long recordCollectionId, RecordCorrectnessDropdown correctness, PaginationQuery paginationQuery) { + switch (correctness) { + case RecordCorrectnessDropdown.ALL: + // TODO Do this with a query + DetachedCriteria query = recordGormService.queryByRecordCollectionId(recordCollectionId) + //query.join('portions') + Map args = paginationQuery?.toMap() + List ids = (args ? query.id().list(args) : query.id().list()) as List + return ids + case RecordCorrectnessDropdown.VALID: + return findAllValidRecords(recordCollectionId, paginationQuery) + case RecordCorrectnessDropdown.INVALID: + return findAllInvalidRecords(recordCollectionId, paginationQuery) } } @@ -98,7 +105,7 @@ class RecordService { return [] as List } DetachedCriteria query = queryValidRecords(recordCollectionId, validRecordIds) - query.id().list(paginationQuery.toMap()) as List + return (paginationQuery ? query.id().list(paginationQuery.toMap()) : query.id().list()) as List } List findAllInvalidRecords(Long recordCollectionId, PaginationQuery paginationQuery) { diff --git a/grails-app/utils/uk/co/metadataconsulting/sentinel/RecordPortionUtils.groovy b/grails-app/utils/uk/co/metadataconsulting/sentinel/RecordPortionUtils.groovy new file mode 100644 index 0000000..a10e249 --- /dev/null +++ b/grails-app/utils/uk/co/metadataconsulting/sentinel/RecordPortionUtils.groovy @@ -0,0 +1,17 @@ +package uk.co.metadataconsulting.sentinel + +import groovy.transform.CompileStatic + +@CompileStatic +class RecordPortionUtils { + + static RecordPortion of(RecordPortionGormEntity recordPortionGormEntity) { + new RecordPortion( + header: recordPortionGormEntity.header, + name: recordPortionGormEntity.name, + value: recordPortionGormEntity.value, + status: recordPortionGormEntity.status, + reason: recordPortionGormEntity.reason, + numberOfRulesValidatedAgainst: recordPortionGormEntity.numberOfRulesValidatedAgainst) + } +} diff --git a/grails-app/views/record/index.gsp b/grails-app/views/record/index.gsp index 97391db..578a28f 100644 --- a/grails-app/views/record/index.gsp +++ b/grails-app/views/record/index.gsp @@ -23,7 +23,7 @@ - + diff --git a/src/main/groovy/uk/co/metadataconsulting/sentinel/RecordPortion.groovy b/src/main/groovy/uk/co/metadataconsulting/sentinel/RecordPortion.groovy index 89e9415..ea42874 100644 --- a/src/main/groovy/uk/co/metadataconsulting/sentinel/RecordPortion.groovy +++ b/src/main/groovy/uk/co/metadataconsulting/sentinel/RecordPortion.groovy @@ -12,4 +12,14 @@ class RecordPortion { ValidationStatus status = ValidationStatus.NOT_VALIDATED String reason Integer numberOfRulesValidatedAgainst = 0 + + static String toHeaderCsv(String separator = ';') { + List l = ['name', 'value', 'status', 'reason', 'numberOfRulesValidatedAgainst'] + l.join(separator) + } + + String toCsv(String separator = ';') { + List l = [name ?: '', value ?: '', status.toString() ?: '', reason ?: '', numberOfRulesValidatedAgainst] + l.join(separator) + } } diff --git a/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportRowView.groovy b/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportRowView.groovy new file mode 100644 index 0000000..f6bdd8f --- /dev/null +++ b/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportRowView.groovy @@ -0,0 +1,13 @@ +package uk.co.metadataconsulting.sentinel.export + +import groovy.transform.CompileStatic +import uk.co.metadataconsulting.sentinel.RecordPortion + +@CompileStatic +class RecordCollectionExportRowView { + List recordPortionList + + String toCsv(String separator = ';') { + recordPortionList.collect { it.toCsv(separator) }.join(separator) + } +} diff --git a/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportView.groovy b/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportView.groovy new file mode 100644 index 0000000..158a6cc --- /dev/null +++ b/src/main/groovy/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportView.groovy @@ -0,0 +1,9 @@ +package uk.co.metadataconsulting.sentinel.export + +import groovy.transform.CompileStatic + +@CompileStatic +class RecordCollectionExportView { + List headers = [] + List rows = [] +} diff --git a/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionControllerAllowedMethodsSpec.groovy b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionControllerAllowedMethodsSpec.groovy index 6f12553..48863d8 100644 --- a/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionControllerAllowedMethodsSpec.groovy +++ b/src/test/groovy/uk/co/metadataconsulting/sentinel/RecordCollectionControllerAllowedMethodsSpec.groovy @@ -3,12 +3,42 @@ package uk.co.metadataconsulting.sentinel import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification import spock.lang.Unroll +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportView + import static javax.servlet.http.HttpServletResponse.SC_OK import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY class RecordCollectionControllerAllowedMethodsSpec extends Specification implements ControllerUnitTest { + @Unroll + def "test RecordCollectionController.exportValidRecords does not accept #method requests"(String method) { + when: + request.method = method + controller.exportValidRecords() + + then: + response.status == SC_METHOD_NOT_ALLOWED + + where: + method << ['PATCH', 'DELETE', 'POST', 'PUT'] + } + + def "test RecordCollectionController.exportValidRecords accepts GET requests"() { + given: + controller.recordCollectionExportService = Stub(RecordCollectionExportService) { + export(_,_) >> new RecordCollectionExportView() + } + + when: + request.method = 'GET' + controller.exportValidRecords() + + then: + response.status == SC_OK + } + + @Unroll def "test RecordCollectionController.cloneSave does not accept #method requests"(String method) { when: From 3674587feba5f5b3c0d4004372502d092f5652cc Mon Sep 17 00:00:00 2001 From: sdelamo Date: Fri, 29 Jun 2018 17:27:44 +0200 Subject: [PATCH 07/24] excel --- build.gradle | 4 + gradle.properties | 1 + grails-app/conf/application.yml | 11 +- .../RecordCollectionController.groovy | 120 +++++++++++++----- .../sentinel/UrlMappings.groovy | 2 +- .../ExportRecordCollectionCommand.groovy | 16 +++ grails-app/i18n/messages.properties | 7 + .../sentinel/export/ExportService.groovy | 41 ++++++ .../RecordCollectionExportService.groovy | 9 +- grails-app/views/record/index.gsp | 13 +- .../sentinel/RecordPortion.groovy | 12 +- .../sentinel/export/ExportFormat.groovy | 8 ++ .../RecordCollectionExportRowView.groovy | 4 + ...lectionControllerAllowedMethodsSpec.groovy | 9 +- .../sentinel/UrlMappingsSpec.groovy | 1 + ...ordCollectionCommandConstraintsSpec.groovy | 21 +++ 16 files changed, 224 insertions(+), 55 deletions(-) create mode 100644 grails-app/controllers/uk/co/metadataconsulting/sentinel/export/ExportRecordCollectionCommand.groovy create mode 100644 grails-app/services/uk/co/metadataconsulting/sentinel/export/ExportService.groovy rename grails-app/services/uk/co/metadataconsulting/sentinel/{ => export}/RecordCollectionExportService.groovy (74%) create mode 100644 src/main/groovy/uk/co/metadataconsulting/sentinel/export/ExportFormat.groovy create mode 100644 src/test/groovy/uk/co/metadataconsulting/sentinel/export/ExportRecordCollectionCommandConstraintsSpec.groovy diff --git a/build.gradle b/build.gradle index b75a725..f508c46 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ apply plugin: "org.grails.plugins.views-json" repositories { mavenLocal() maven { url "https://repo.grails.org/grails/core" } + jcenter() } dependencies { @@ -86,6 +87,9 @@ dependencies { testCompile "com.stehno.ersatz:ersatz:$ersatzVersion:safe@jar" testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" + + compile "builders.dsl:spreadsheet-builder-poi:$spreadsheetBuilderVersion" + compile "builders.dsl:spreadsheet-builder-groovy:$spreadsheetBuilderVersion" } bootRun { diff --git a/gradle.properties b/gradle.properties index 5e6cf44..54313da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,4 @@ assetPipelineVersion=2.14.8 okHttpVersion=3.9.1 moshiVersion=1.5.0 droolsVersion=7.5.0.Final +spreadsheetBuilderVersion=1.0.5 diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index 0fc35c6..d4fa511 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -65,6 +65,7 @@ grails: xml: - text/xml - application/xml + urlmapping: cache: maxsize: 1000 @@ -99,16 +100,16 @@ hibernate: dataSource: pooled: true jmxExport: true - driverClassName: '${JDBC_DRIVER}' - dialect: '${JDBC_DIALECT}' - username: '${JDBC_USERNAME}' - password: '${JDBC_PASSWORD}' + driverClassName: com.mysql.jdbc.Driver + dialect: org.hibernate.dialect.MySQL5InnoDBDialect + username: root + password: root environments: development: dataSource: dbCreate: create - url: '${JDBC_CONNECTION_STRING}' + url: jdbc:mysql://127.0.0.1:8889/metadatasentinel_dev test: dataSource: dbCreate: create-drop diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy index 63d1ad6..95d6883 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/RecordCollectionController.groovy @@ -1,16 +1,18 @@ package uk.co.metadataconsulting.sentinel +import builders.dsl.spreadsheet.builder.poi.PoiSpreadsheetBuilder +import grails.config.Config +import grails.core.support.GrailsConfigurationAware import groovy.util.logging.Slf4j import org.springframework.context.MessageSource +import uk.co.metadataconsulting.sentinel.export.ExportRecordCollectionCommand +import uk.co.metadataconsulting.sentinel.export.ExportService import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportRowView +import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportService import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportView - -//import pl.touk.excel.export.WebXlsxExporter import uk.co.metadataconsulting.sentinel.modelcatalogue.ValidationRules -import grails.config.Config -import grails.core.support.GrailsConfigurationAware - import static org.springframework.http.HttpStatus.OK +import uk.co.metadataconsulting.sentinel.export.ExportFormat @Slf4j class RecordCollectionController implements ValidateableErrorsMessage, GrailsConfigurationAware { @@ -24,7 +26,7 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon headersMapping: 'GET', cloneMapping: 'GET', cloneSave: 'POST', - exportValidRecords: 'GET' + export: 'GET' ] MessageSource messageSource @@ -43,23 +45,21 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon RuleFetcherService ruleFetcherService - RecordCollectionExportService recordCollectionExportService + ExportService exportService + int defaultPaginationMax = 25 int defaultPaginationOffset = 0 - String csvMimeType - String encoding + String separator @Override void setConfiguration(Config co) { + separator = co.getProperty('export.csv.separator', String, ';') defaultPaginationMax = co.getProperty('sentinel.pagination.max', Integer, 25) defaultPaginationOffset = co.getProperty('sentinel.pagination.offset', Integer, 0) - csvMimeType = co.getProperty('grails.mime.types.csv', String, 'text/csv') - encoding = co.getProperty('grails.converters.encoding', String, 'UTF-8') } - def index() { Integer max = params.int('max') ?: defaultPaginationMax @@ -83,30 +83,80 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon ] } - def exportValidRecords(Long recordCollectionId){ + def export(ExportRecordCollectionCommand cmd) { + + if ( cmd.hasErrors() ) { + // TODO Maybe display an error message with flash.error and redirect to HomePage + return + } - final String filename = 'dataset_valid.csv' + RecordCollectionGormEntity collectionGormEntity = recordCollectionGormService.find(cmd.recordCollectionId) + if ( !collectionGormEntity ) { + // TODO Maybe display an error message with flash.error and redirect to HomePage + return + } + final String filenameprefix = collectionGormEntity.datasetName + response.status = OK.value() + RecordCollectionExportView view = recordCollectionExportService.export(cmd.recordCollectionId) def outs = response.outputStream - response.status = OK.value() - response.contentType = "${csvMimeType};charset=${encoding}" - response.setHeader "Content-disposition", "attachment; filename=${filename}" - final String separator = ';' - RecordCollectionExportView view = recordCollectionExportService.export(recordCollectionId) List headers = view.headers - // TODO remove this line - headers = headers.collect { [it, it, it, it, it, it] }.flatten() - outs << "${headers.join(separator)}\n" - - if ( view.rows ) { - List portionsHeaders = view.rows.first().recordPortionList.collect { RecordPortion.toHeaderCsv(separator) } - String line = "${portionsHeaders.join(separator)}\n" - outs << line - view.rows.each { RecordCollectionExportRowView row -> - outs << "${row.toCsv(separator)}\n" - } + List portionsHeaders = view.rows.first().recordPortionList.collect { RecordPortion.toHeaderList() }.flatten() + String filename = "${filenameprefix}.${exportService.fileExtensionForFormat(cmd.format)}" + response.setHeader "Content-disposition", "attachment; filename=${filename}" + response.contentType = exportService.mimeTypeForFormat(cmd.format) + switch (cmd.format) { + case ExportFormat.CSV: + // TODO remove this line? + headers = headers.collect { [it, it, it, it, it, it] }.flatten() + outs << "${headers.join(separator)}\n" + if ( view.rows ) { + String line = "${portionsHeaders.join(separator)}\n" + outs << line + view.rows.each { RecordCollectionExportRowView row -> + outs << "${row.toCsv(separator)}\n" + } + } + break + case ExportFormat.XLSX: + + PoiSpreadsheetBuilder.create(outs).build { + sheet(collectionGormEntity.datasetName) { + row { + for ( String header : headers ) { + cell { + value header + colspan RecordPortion.toHeaderList().size() + } + } + } + if ( portionsHeaders ) { + row { + for ( String recordPortionHeader : portionsHeaders ) { + cell { + value messageSource.getMessage("export.header.${recordPortionHeader}".toString(), + [] as Object[], + recordPortionHeader, + request.locale) + } + } + } + } + if ( view.rows ) { + for (RecordCollectionExportRowView rowView : view.rows ) { + row { + for (String val : rowView.toList()) { + cell { + value val + } + } + } + } + } + } + } + break } - outs.flush() outs.close() } @@ -127,8 +177,8 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon } } - // new XlsxExporter('/tmp/myReportFile.xlsx'). - // add(dataRecordsList, properties).save() + // new XlsxExporter('/tmp/myReportFile.xlsx'). + // add(dataRecordsList, properties).save() // new WebXlsxExporter().with { // setResponseHeaders(response) // fillHeader(headers) @@ -194,9 +244,9 @@ class RecordCollectionController implements ValidateableErrorsMessage, GrailsCon List dataModelList = ruleFetcherService.fetchDataModels()?.dataModels if ( !dataModelList ) { flash.error = messageSource.getMessage('dataModel.couldNotLoad', [] as Object[], 'Could not load data Models', request.locale) - } + } [ - dataModelList: dataModelList, + dataModelList: dataModelList, recordCollectionId: recordCollectionId, recordPortionMappingList: recordCollectionMappingGormService.findAllByRecordCollectionId(recordCollectionId) ] diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy index abf2ea5..f042399 100644 --- a/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/UrlMappings.groovy @@ -7,7 +7,7 @@ class UrlMappings { "/recordCollection/cloneMapping"(controller: 'recordCollection', action: 'cloneMapping') "/recordCollection/cloneSave"(controller: 'recordCollection', action: 'cloneSave', httpMethod: 'POST') "/recordCollection/validate"(controller: 'recordCollection', action: 'validate') - "/recordCollection/exportValidRecords"(controller: 'recordCollection', action: 'exportValidRecords') + "/recordCollection/export"(controller: 'recordCollection', action: 'export') "/recordCollection/exportValidExcel"(controller: 'recordCollection', action: 'exportValidExcel') "/recordCollection/delete"(controller: 'recordCollection', action: 'delete') "/recordCollection/$recordCollectionId/mapping"(controller: 'recordCollection', action: 'headersMapping') diff --git a/grails-app/controllers/uk/co/metadataconsulting/sentinel/export/ExportRecordCollectionCommand.groovy b/grails-app/controllers/uk/co/metadataconsulting/sentinel/export/ExportRecordCollectionCommand.groovy new file mode 100644 index 0000000..7276ac7 --- /dev/null +++ b/grails-app/controllers/uk/co/metadataconsulting/sentinel/export/ExportRecordCollectionCommand.groovy @@ -0,0 +1,16 @@ +package uk.co.metadataconsulting.sentinel.export + +import grails.compiler.GrailsCompileStatic +import grails.validation.Validateable +import uk.co.metadataconsulting.sentinel.export.ExportFormat + +@GrailsCompileStatic +class ExportRecordCollectionCommand implements Validateable { + + Long recordCollectionId + ExportFormat format = ExportFormat.XLSX + + static constraints = { + recordCollectionId nullable: false + } +} \ No newline at end of file diff --git a/grails-app/i18n/messages.properties b/grails-app/i18n/messages.properties index b045136..335748c 100644 --- a/grails-app/i18n/messages.properties +++ b/grails-app/i18n/messages.properties @@ -54,3 +54,10 @@ typeMismatch.java.lang.Short=Property {0} must be a valid number typeMismatch.java.math.BigDecimal=Property {0} must be a valid number typeMismatch.java.math.BigInteger=Property {0} must be a valid number typeMismatch=Property {0} is type-mismatched + + +export.header.name=Name +export.header.value=Value +export.header.status=Status +export.header.reason=Failure Reason +export.header.numberOfRulesValidatedAgainst=# Rules Validated Against diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/export/ExportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/export/ExportService.groovy new file mode 100644 index 0000000..9b4dcb2 --- /dev/null +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/export/ExportService.groovy @@ -0,0 +1,41 @@ +package uk.co.metadataconsulting.sentinel.export + +import grails.config.Config +import grails.core.support.GrailsConfigurationAware +import groovy.transform.CompileStatic +import uk.co.metadataconsulting.sentinel.export.ExportFormat + +@CompileStatic +class ExportService implements GrailsConfigurationAware { + String xlsxMimeType + String xlsMimeType + String csvMimeType + String encoding + + @Override + void setConfiguration(Config co) { + csvMimeType = co.getProperty('grails.mime.types.csv', String, 'text/csv') + xlsMimeType = co.getProperty('grails.mime.types.xlsMimeType', String,'application/vnd.ms-excel') + xlsxMimeType = co.getProperty('grails.mime.types.xlsxMimeType', String, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + encoding = co.getProperty('grails.converters.encoding', String, 'UTF-8') + } + + String fileExtensionForFormat(ExportFormat format) { + switch (format) { + case ExportFormat.CSV: + return 'csv' + + case ExportFormat.XLSX: + return "xlsx" + } + } + String mimeTypeForFormat(ExportFormat format) { + switch (format) { + case ExportFormat.CSV: + return "${csvMimeType};charset=${encoding}" + + case ExportFormat.XLSX: + return "${xlsxMimeType};charset=${encoding}" + } + } +} \ No newline at end of file diff --git a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy b/grails-app/services/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportService.groovy similarity index 74% rename from grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy rename to grails-app/services/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportService.groovy index a2c2631..451436b 100644 --- a/grails-app/services/uk/co/metadataconsulting/sentinel/RecordCollectionExportService.groovy +++ b/grails-app/services/uk/co/metadataconsulting/sentinel/export/RecordCollectionExportService.groovy @@ -1,6 +1,13 @@ -package uk.co.metadataconsulting.sentinel +package uk.co.metadataconsulting.sentinel.export import groovy.transform.CompileStatic +import uk.co.metadataconsulting.sentinel.RecordCorrectnessDropdown +import uk.co.metadataconsulting.sentinel.RecordGormEntity +import uk.co.metadataconsulting.sentinel.RecordGormService +import uk.co.metadataconsulting.sentinel.RecordPortion +import uk.co.metadataconsulting.sentinel.RecordPortionGormEntity +import uk.co.metadataconsulting.sentinel.RecordPortionUtils +import uk.co.metadataconsulting.sentinel.RecordService import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportRowView import uk.co.metadataconsulting.sentinel.export.RecordCollectionExportView diff --git a/grails-app/views/record/index.gsp b/grails-app/views/record/index.gsp index 578a28f..b0440aa 100644 --- a/grails-app/views/record/index.gsp +++ b/grails-app/views/record/index.gsp @@ -1,4 +1,6 @@ <%@ page import="uk.co.metadataconsulting.sentinel.RecordCorrectnessDropdown" %> +<%@ page import="uk.co.metadataconsulting.sentinel.export.ExportFormat" %> + Records @@ -23,10 +25,13 @@ - - - - +