├── omod ├── src │ ├── main │ │ ├── webapp │ │ │ ├── pages │ │ │ │ ├── paramWidget │ │ │ │ │ ├── java.lang.Integer.gsp │ │ │ │ │ ├── org.openmrs.Location.gsp │ │ │ │ │ ├── java.util.Date.gsp │ │ │ │ │ └── org.openmrs.EncounterType.gsp │ │ │ │ ├── widgetTemplates │ │ │ │ │ ├── locationWidget.gsp │ │ │ │ │ ├── encounterTypeWidget.gsp │ │ │ │ │ ├── locationWidgetMultiple.gsp │ │ │ │ │ └── encounterTypeWidgetMultiple.gsp │ │ │ │ ├── adHocAnalysisParameterPopup.gsp │ │ │ │ ├── allDefinitionLibraries.gsp │ │ │ │ ├── reportsapp │ │ │ │ │ └── home.gsp │ │ │ │ ├── adHocRun.gsp │ │ │ │ ├── adHocManage.gsp │ │ │ │ ├── reportStatus.gsp │ │ │ │ ├── reportHistory.gsp │ │ │ │ ├── runReport.gsp │ │ │ │ └── adHocAnalysis.gsp │ │ │ └── resources │ │ │ │ └── scripts │ │ │ │ ├── app.js │ │ │ │ ├── directives │ │ │ │ ├── locationWidget.js │ │ │ │ └── encounterTypeWidget.js │ │ │ │ ├── reportStatus.js │ │ │ │ ├── reportHistory.js │ │ │ │ ├── adHocRun.js │ │ │ │ ├── runReport.js │ │ │ │ └── adHocAnalysis.js │ │ ├── compass │ │ │ ├── sass │ │ │ │ ├── runReport.scss │ │ │ │ ├── reportsapp │ │ │ │ │ └── home.scss │ │ │ │ └── adHocReport.scss │ │ │ └── config.rb │ │ ├── resources │ │ │ ├── webModuleApplicationContext.xml │ │ │ ├── config.xml │ │ │ └── apps │ │ │ │ └── reports_app.json │ │ └── java │ │ │ └── org │ │ │ └── openmrs │ │ │ └── module │ │ │ └── reportingui │ │ │ ├── page │ │ │ └── controller │ │ │ │ ├── AllDefinitionLibrariesPageController.java │ │ │ │ ├── AdHocManagePageController.java │ │ │ │ ├── AdHocRunPageController.java │ │ │ │ ├── AdHocAnalysisPageController.java │ │ │ │ ├── ViewReportRequestPageController.java │ │ │ │ └── RunReportPageController.java │ │ │ └── fragment │ │ │ └── controller │ │ │ ├── DefinitionLibraryFragmentController.java │ │ │ ├── AdHocAnalysisFragmentController.java │ │ │ └── ReportStatusFragmentController.java │ └── test │ │ └── java │ │ └── org │ │ └── openmrs │ │ └── module │ │ └── reportingui │ │ ├── fragment │ │ └── controller │ │ │ └── DefinitionLibraryFragmentControllerComponentTest.java │ │ └── page │ │ └── controller │ │ └── RunReportPageControllerTest.java └── pom.xml ├── .tx └── config ├── api ├── src │ ├── test │ │ ├── resources │ │ │ ├── test-hibernate.cfg.xml │ │ │ └── TestingApplicationContext.xml │ │ └── java │ │ │ └── org │ │ │ └── openmrs │ │ │ └── module │ │ │ └── reportingui │ │ │ └── converter │ │ │ └── StringArrayToCohortConverterTest.java │ └── main │ │ ├── java │ │ └── org │ │ │ └── openmrs │ │ │ └── module │ │ │ └── reportingui │ │ │ ├── converter │ │ │ ├── StringToReportDefinitionConverter.java │ │ │ ├── StringToDataSetDefinitionConverter.java │ │ │ └── StringArrayToCohortConverter.java │ │ │ └── ReportingUiModuleActivator.java │ │ └── resources │ │ ├── messages.properties │ │ ├── messages_ht.properties │ │ └── messages_fr.properties └── pom.xml ├── .gitignore ├── README.md └── pom.xml /omod/src/main/webapp/pages/paramWidget/java.lang.Integer.gsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/widgetTemplates/locationWidget.gsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/widgetTemplates/encounterTypeWidget.gsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/app.js: -------------------------------------------------------------------------------- 1 | angular.module('reportingui', [ 2 | 'ngResource', 3 | 'ngSanitize', 4 | 'ui.bootstrap', 5 | 'uicommons.filters', 6 | 'encounterTypeService', 7 | 'locationService' 8 | ]); -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/paramWidget/org.openmrs.Location.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | def multiple = "java.util.List" == param.collectionType[0] || "java.util.Set" == param.collectionType[0] 3 | %> 4 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/paramWidget/java.util.Date.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/paramWidget/org.openmrs.EncounterType.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | def multiple = "java.util.List" == param.collectionType[0] || "java.util.Set" == param.collectionType[0] 3 | %> 4 | 5 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com/ 3 | 4 | [mirebalais.reporting-ui-module] 5 | source_file = api/src/main/resources/messages.properties 6 | source_lang = en 7 | trans.fr = api/src/main/resources/messages_fr.properties 8 | trans.ht = api/src/main/resources/messages_ht.properties 9 | 10 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/widgetTemplates/locationWidgetMultiple.gsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/widgetTemplates/encounterTypeWidgetMultiple.gsp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/test/resources/test-hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /omod/src/main/compass/sass/runReport.scss: -------------------------------------------------------------------------------- 1 | .past-reports { 2 | display: inline-block; 3 | vertical-align: top; 4 | width: 60%; 5 | 6 | table { 7 | font-size: 0.8em; 8 | } 9 | 10 | a { 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .running-reports { 16 | display: inline-block; 17 | vertical-align: top; 18 | width: 38%; 19 | 20 | table { 21 | font-size: 0.9em; 22 | } 23 | } 24 | 25 | .report-list { 26 | margin-bottom: 1em; 27 | } 28 | 29 | img.small { 30 | width: 24px; 31 | height: 24px; 32 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.idea 3 | 4 | target/ 5 | api/target/ 6 | omod/target/ 7 | omod/src/main/compass/.sass-cache 8 | 9 | /.settings 10 | /.project 11 | *.DS_Store 12 | 13 | omod/.DS_Store 14 | 15 | /.classpath 16 | 17 | 18 | activemq-data 19 | 20 | api/.project 21 | api/.classpath 22 | api/.settings/ 23 | api/activemq-data 24 | 25 | omod/.project 26 | omod/.classpath 27 | omod/.settings/ 28 | omod/src/main/compass/sass-external/ 29 | omod/src/main/webapp/resources/styles/ 30 | *.sass-cache 31 | 32 | # Rubygems build directories # 33 | omod/.rubygems 34 | omod/.rubygems-provided 35 | 36 | -------------------------------------------------------------------------------- /omod/src/main/resources/webModuleApplicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/AllDefinitionLibrariesPageController.java: -------------------------------------------------------------------------------- 1 | package org.openmrs.module.reportingui.page.controller; 2 | 3 | import org.openmrs.module.reporting.definition.library.AllDefinitionLibraries; 4 | import org.openmrs.module.reporting.evaluation.Definition; 5 | import org.openmrs.ui.framework.annotation.SpringBean; 6 | import org.openmrs.ui.framework.page.PageModel; 7 | 8 | import java.util.Set; 9 | 10 | /** 11 | * 12 | */ 13 | public class AllDefinitionLibrariesPageController { 14 | 15 | public void get(@SpringBean AllDefinitionLibraries allDefinitionLibraries, 16 | PageModel model) { 17 | Set> allTypes = allDefinitionLibraries.getAllDefinitionTypes(); 18 | model.put("allTypes", allTypes); 19 | model.put("allDefinitionLibraries", allDefinitionLibraries); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /omod/src/main/compass/config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "../webapp/resources/styles" 6 | sass_dir = "sass" 7 | images_dir = "images" 8 | javascripts_dir = "javascripts" 9 | add_import_path "sass-external/uicommons-scss" 10 | 11 | # You can select your preferred output style here (can be overridden via the command line): 12 | # output_style = :expanded or :nested or :compact or :compressed 13 | 14 | # To enable relative paths to assets via compass helper functions. Uncomment: 15 | # relative_assets = true 16 | 17 | # To disable debugging comments that display the original location of your selectors. Uncomment: 18 | # line_comments = false 19 | 20 | 21 | # If you prefer the indented syntax, you might want to regenerate this 22 | # project again passing --syntax sass, or you can uncomment this: 23 | # preferred_syntax = :sass 24 | # and then run: 25 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 26 | -------------------------------------------------------------------------------- /api/src/test/resources/TestingApplicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | classpath:hibernate.cfg.xml 15 | classpath:test-hibernate.cfg.xml 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/adHocAnalysisParameterPopup.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 20 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/allDefinitionLibraries.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | %> 4 | 5 | <% allTypes.each { definitionType -> 6 | def definitions = allDefinitionLibraries.getDefinitionSummaries(definitionType) 7 | %> 8 | 9 |

${ definitionType.simpleName }

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% definitions.each { %> 21 | 22 | 23 | 24 | 30 | 31 | <% } %> 32 | 33 |
NameDescriptionParameters
${ ui.message(it.name) }${ ui.message(it.description) } 25 | <%= it.parameters.collect { 26 | "${it.name}:${it.type.simpleName}" + 27 | (it.collectionType ? "(${it.collectionType.simpleName})" : "") 28 | }.join("
") %> 29 |
34 | 35 | <% } %> -------------------------------------------------------------------------------- /omod/src/main/compass/sass/reportsapp/home.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | $pihSecondaryDarkBlue: #003F5E; 4 | 5 | .reportBox { 6 | display: inline-block; 7 | color: $text; 8 | vertical-align: top; 9 | margin: 0 10px; 10 | width: 47%; 11 | 12 | p { 13 | font-size: 1.2em; 14 | font-family: "OpenSansBold"; 15 | text-transform: uppercase; 16 | margin-bottom: 10px; 17 | margin-top: 10px; 18 | } 19 | 20 | ul { 21 | border-bottom: 3px solid white; 22 | background-color: $pihSecondaryDarkBlue; 23 | color: $white; 24 | padding: 10px; 25 | 26 | li { 27 | padding-bottom: 0.5em; 28 | } 29 | 30 | a, a:visited, a.visited { 31 | color: $white; 32 | } 33 | 34 | span.label { 35 | display: inline-block; 36 | } 37 | 38 | span.data { 39 | margin: 0 2%; 40 | display: inline-block; 41 | font-size: 1.8em; 42 | font-family: "OpenSansBold"; 43 | 44 | .number { 45 | font-size: 0.8em; 46 | font-family: "OpenSans"; 47 | } 48 | 49 | &.percentage { 50 | font-size: 1.3em; 51 | } 52 | } 53 | 54 | .subtle { 55 | color: darken($white, 40%); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/directives/locationWidget.js: -------------------------------------------------------------------------------- 1 | angular.module('reportingui') 2 | .directive('locationWidget', ['LocationService', function(LocationService) { 3 | 4 | var uniqueId = 0; 5 | 6 | function link(scope, element, attrs) { 7 | scope.uniqueId = 'locationWidget' + ++uniqueId; 8 | scope.template = 'widgetTemplates/locationWidget' + (attrs.multiple === 'true' ? 'Multiple' : '') + '.page'; 9 | scope.locations = []; 10 | LocationService.getLocations().then(function(result) { 11 | scope.locations = result; 12 | }); 13 | }; 14 | 15 | return { 16 | restrict: 'E', 17 | scope: { 18 | target: '=' 19 | }, 20 | controller: function($scope) { 21 | $scope.collectValues = function() { 22 | var temp = _.where($scope.locations, { selected: true }); 23 | $scope.target = _.map(temp, function(item) { 24 | var obj = angular.copy(item); 25 | delete obj.selected; 26 | return obj; 27 | }); 28 | } 29 | }, 30 | template: '
', 31 | link: link 32 | }; 33 | }]); -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/AdHocManagePageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.page.controller; 16 | 17 | import org.openmrs.api.context.Context; 18 | import org.openmrs.module.reportingrest.adhoc.AdHocExportManager; 19 | import org.openmrs.ui.framework.annotation.SpringBean; 20 | import org.openmrs.ui.framework.page.PageModel; 21 | 22 | import java.util.List; 23 | 24 | public class AdHocManagePageController { 25 | 26 | public void get(@SpringBean AdHocExportManager adHocExportManager, 27 | PageModel model) { 28 | List exports = adHocExportManager.getAdHocDataSets(Context.getAuthenticatedUser()); 29 | model.addAttribute("exports", exports); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/directives/encounterTypeWidget.js: -------------------------------------------------------------------------------- 1 | angular.module('reportingui') 2 | .directive('encounterTypeWidget', ['EncounterTypeService', function(EncounterTypeService) { 3 | 4 | var uniqueId = 0; 5 | 6 | function link(scope, element, attrs) { 7 | scope.uniqueId = 'encTypeWidget' + ++uniqueId; 8 | scope.template = 'widgetTemplates/encounterTypeWidget' + (attrs.multiple === 'true' ? 'Multiple' : '') + '.page'; 9 | scope.encounterTypes = []; 10 | EncounterTypeService.getEncounterTypes().then(function(result) { 11 | scope.encounterTypes = result; 12 | }); 13 | }; 14 | 15 | return { 16 | restrict: 'E', 17 | scope: { 18 | target: '=' 19 | }, 20 | controller: function($scope) { 21 | $scope.collectValues = function() { 22 | var temp = _.where($scope.encounterTypes, { selected: true }); 23 | $scope.target = _.map(temp, function(item) { 24 | var obj = angular.copy(item); 25 | delete obj.selected; 26 | return obj; 27 | }); 28 | } 29 | }, 30 | template: '
', 31 | link: link 32 | }; 33 | }]); -------------------------------------------------------------------------------- /api/src/main/java/org/openmrs/module/reportingui/converter/StringToReportDefinitionConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.converter; 16 | 17 | import org.openmrs.module.reporting.report.definition.ReportDefinition; 18 | import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.core.convert.converter.Converter; 21 | import org.springframework.stereotype.Component; 22 | 23 | @Component 24 | public class StringToReportDefinitionConverter implements Converter { 25 | 26 | @Autowired 27 | ReportDefinitionService service; 28 | 29 | @Override 30 | public ReportDefinition convert(String source) { 31 | return service.getDefinitionByUuid(source); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/org/openmrs/module/reportingui/converter/StringToDataSetDefinitionConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.converter; 16 | 17 | import org.openmrs.module.reporting.dataset.definition.DataSetDefinition; 18 | import org.openmrs.module.reporting.dataset.definition.service.DataSetDefinitionService; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.core.convert.converter.Converter; 21 | import org.springframework.stereotype.Component; 22 | 23 | @Component 24 | public class StringToDataSetDefinitionConverter implements Converter { 25 | 26 | @Autowired 27 | DataSetDefinitionService service; 28 | 29 | @Override 30 | public DataSetDefinition convert(String source) { 31 | return service.getDefinitionByUuid(source); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /api/src/test/java/org/openmrs/module/reportingui/converter/StringArrayToCohortConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.converter; 16 | 17 | import org.junit.Test; 18 | import org.openmrs.Cohort; 19 | import org.openmrs.module.reporting.common.ReportingMatchers; 20 | 21 | import static org.junit.Assert.assertThat; 22 | 23 | public class StringArrayToCohortConverterTest { 24 | 25 | @Test 26 | public void testConvert() throws Exception { 27 | Cohort cohort = new StringArrayToCohortConverter().convert(new String[]{"1", "2", "3,4,5"}); 28 | assertThat(cohort, ReportingMatchers.isCohortWithExactlyIds(1, 2, 3, 4, 5)); 29 | } 30 | 31 | @Test(expected = NumberFormatException.class) 32 | public void testConvertIllegal() throws Exception { 33 | Cohort cohort = new StringArrayToCohortConverter().convert(new String[]{"1", "2", "null"}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | org.openmrs.module 7 | reportingui 8 | 1.0-SNAPSHOT 9 | 10 | 11 | reportingui-api 12 | jar 13 | Reporting UI Module API 14 | API project for ReportingUI 15 | 16 | 17 | 18 | 19 | src/main/resources 20 | 21 | *.xml 22 | 23 | true 24 | 25 | 26 | src/main/resources 27 | 28 | *.xml 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | src/test/resources 37 | true 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /api/src/main/java/org/openmrs/module/reportingui/ReportingUiModuleActivator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | package org.openmrs.module.reportingui; 15 | 16 | 17 | import org.apache.commons.logging.Log; 18 | import org.apache.commons.logging.LogFactory; 19 | import org.openmrs.module.BaseModuleActivator; 20 | import org.openmrs.module.ModuleActivator; 21 | 22 | /** 23 | * This class contains the logic that is run every time this module is either started or stopped. 24 | */ 25 | public class ReportingUiModuleActivator extends BaseModuleActivator { 26 | 27 | private Log log = LogFactory.getLog(getClass()); 28 | 29 | /** 30 | * @see ModuleActivator#started() 31 | */ 32 | @Override 33 | public void started() { 34 | log.info("Reporting UI Module started"); 35 | } 36 | 37 | /** 38 | * @see ModuleActivator#stopped() 39 | */ 40 | @Override 41 | public void stopped() { 42 | log.info("Reporting UI Module stopped"); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/AdHocRunPageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.page.controller; 16 | 17 | import org.openmrs.api.context.Context; 18 | import org.openmrs.module.reportingrest.adhoc.AdHocExportManager; 19 | import org.openmrs.ui.framework.annotation.SpringBean; 20 | import org.openmrs.ui.framework.page.PageModel; 21 | import org.springframework.web.bind.annotation.RequestParam; 22 | 23 | import java.util.List; 24 | 25 | public class AdHocRunPageController { 26 | 27 | public void get(@SpringBean AdHocExportManager adHocExportManager, 28 | @RequestParam("dataset") List dsdUuids, 29 | PageModel model) { 30 | List exports = adHocExportManager.getAdHocDataSets(Context.getAuthenticatedUser()); 31 | model.addAttribute("exports", exports); 32 | model.addAttribute("selected", dsdUuids); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/fragment/controller/DefinitionLibraryFragmentController.java: -------------------------------------------------------------------------------- 1 | package org.openmrs.module.reportingui.fragment.controller; 2 | 3 | import org.openmrs.api.context.Context; 4 | import org.openmrs.module.reporting.definition.library.AllDefinitionLibraries; 5 | import org.openmrs.module.reporting.definition.library.LibraryDefinitionSummary; 6 | import org.openmrs.ui.framework.SimpleObject; 7 | import org.openmrs.ui.framework.UiUtils; 8 | import org.openmrs.ui.framework.annotation.SpringBean; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 15 | */ 16 | public class DefinitionLibraryFragmentController { 17 | 18 | public List getDefinitions(@RequestParam("type") String type, 19 | UiUtils ui, 20 | @SpringBean AllDefinitionLibraries allDefinitionLibraries) throws Exception { 21 | Class clazz = Context.loadClass(type); 22 | List definitions = allDefinitionLibraries.getDefinitionSummaries(clazz); 23 | return simplify(ui, definitions); 24 | } 25 | 26 | private List simplify(UiUtils ui, List definitions) { 27 | List simplified = SimpleObject.fromCollection(definitions, ui, "type", "key", "name:message", "description:message", "parameters.name", "parameters.label:message", "parameters.type", "parameters.collectionType"); 28 | return simplified; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /omod/src/test/java/org/openmrs/module/reportingui/fragment/controller/DefinitionLibraryFragmentControllerComponentTest.java: -------------------------------------------------------------------------------- 1 | package org.openmrs.module.reportingui.fragment.controller; 2 | 3 | import org.junit.Test; 4 | import org.openmrs.module.appui.TestUiUtils; 5 | import org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition; 6 | import org.openmrs.module.reporting.definition.library.AllDefinitionLibraries; 7 | import org.openmrs.module.reporting.definition.library.LibraryDefinitionSummary; 8 | import org.openmrs.ui.framework.SimpleObject; 9 | import org.openmrs.web.test.BaseModuleWebContextSensitiveTest; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | 12 | import java.util.List; 13 | 14 | import static org.hamcrest.Matchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | /** 18 | * 19 | */ 20 | public class DefinitionLibraryFragmentControllerComponentTest extends BaseModuleWebContextSensitiveTest { 21 | 22 | @Autowired 23 | private AllDefinitionLibraries allDefinitionLibraries; 24 | 25 | @Test 26 | public void testGetResources() throws Exception { 27 | DefinitionLibraryFragmentController controller = new DefinitionLibraryFragmentController(); 28 | List definitions = controller.getDefinitions(PatientDataDefinition.class.getName(), new TestUiUtils(), allDefinitionLibraries); 29 | 30 | List expected = allDefinitionLibraries.getDefinitionSummaries(PatientDataDefinition.class); 31 | assertThat(definitions.size(), is(expected.size())); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/reportStatus.js: -------------------------------------------------------------------------------- 1 | var reportinguiApp = angular.module('reportingui'); 2 | 3 | reportinguiApp.controller('ReportStatusCtrl', [ '$scope', '$timeout', '$resource', '$location', function($scope, $timeout, $resource, $location) { 4 | 5 | var resource = $resource("/" + OPENMRS_CONTEXT_PATH + "/ws/rest/v1/reportingrest/reportRequest/:uuid", { }); 6 | 7 | $scope.loading = true; 8 | 9 | $scope.checkStatus = function(uuid) { 10 | if (uuid) { 11 | $scope.uuid = uuid; 12 | } else { 13 | uuid = $scope.uuid; 14 | } 15 | $scope.reportRequest = resource.get({ uuid: uuid }, function(req) { 16 | if (req.status != 'COMPLETED' && req.status != 'SAVED' && req.status != 'FAILED' && req.status != 'SCHEDULE_COMPLETED') { 17 | $timeout($scope.checkStatus, 3000); 18 | } 19 | }); 20 | $scope.loading = false; 21 | } 22 | 23 | $scope.canDownload = function() { 24 | return $scope.reportRequest.status == 'COMPLETED' || $scope.reportRequest.status == 'SAVED'; 25 | } 26 | 27 | $scope.download = function() { 28 | location.href = '/' + OPENMRS_CONTEXT_PATH + '/module/reporting/reports/viewReport.form?uuid=' + $scope.reportRequest.uuid; 29 | } 30 | 31 | $scope.canSave = function() { 32 | return false; 33 | // TODO when we support marking reports as saved via REST 34 | // return $scope.reportRequest.status == 'COMPLETED'; 35 | } 36 | 37 | $scope.save = function() { 38 | // TODO when we support marking reports as saved via REST 39 | } 40 | 41 | }]); -------------------------------------------------------------------------------- /api/src/main/java/org/openmrs/module/reportingui/converter/StringArrayToCohortConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.converter; 16 | 17 | import org.openmrs.Cohort; 18 | import org.springframework.core.convert.converter.Converter; 19 | import org.springframework.stereotype.Component; 20 | 21 | /** 22 | * 23 | */ 24 | @Component 25 | public class StringArrayToCohortConverter implements Converter { 26 | 27 | /** 28 | * Accepts elemants like "135" and "135,203,415" 29 | * @param source each element must be parseable to an Integer, or else a comma-separated list of Integers 30 | * @return 31 | */ 32 | @Override 33 | public Cohort convert(String[] source) { 34 | Cohort cohort = new Cohort(); 35 | for (String maybeCommaSeparated : source) { 36 | String[] patientIds = maybeCommaSeparated.split(","); 37 | for (String patientId : patientIds) { 38 | cohort.addMember(Integer.valueOf(patientId)); 39 | } 40 | } 41 | return cohort; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /omod/src/main/resources/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${project.parent.artifactId} 6 | 7 | ${project.parent.name} 8 | 9 | ${project.parent.version} 10 | 11 | ${project.parent.groupId}.${project.parent.artifactId} 12 | 13 | OpenMRS 14 | 15 | 16 | ${project.parent.description} 17 | 18 | 19 | https://modules.openmrs.org/modules/download/${project.parent.artifactId}/update.rdf 20 | 21 | ${openmrsCoreVersion} 22 | 23 | 24 | 25 | org.openmrs.module.reporting 26 | 27 | 28 | org.openmrs.module.reportingrest 29 | 30 | 31 | org.openmrs.module.uiframework 32 | 33 | 34 | org.openmrs.module.appui 35 | 36 | 37 | 38 | ${project.parent.groupId}.${project.parent.artifactId}.ReportingUiModuleActivator 39 | 40 | 41 | 42 | 43 | 44 | en 45 | messages.properties 46 | 47 | 48 | fr 49 | messages_fr.properties 50 | 51 | 52 | ht 53 | messages_ht.properties 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /omod/src/main/resources/apps/reports_app.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "reportingui.reports", 4 | "description": "Homepage showing a list of different kinds of reports", 5 | "order": 10, 6 | "extensionPoints": [ 7 | { 8 | "id": "org.openmrs.module.reportingui.reports.overview", 9 | "description": "Links to available Overview Reports", 10 | "supportedExtensionTypes": [ "link" ] 11 | }, 12 | { 13 | "id": "org.openmrs.module.reportingui.reports.dataquality", 14 | "description": "Links to available Data Quality Reports", 15 | "supportedExtensionTypes": [ "link" ] 16 | }, 17 | { 18 | "id": "org.openmrs.module.reportingui.reports.dataexport", 19 | "description": "Links to available Data Exports", 20 | "supportedExtensionTypes": [ "link" ] 21 | } 22 | ], 23 | "extensions": [ 24 | { 25 | "id": "reportingui.reports.homepagelink", 26 | "extensionPointId": "org.openmrs.referenceapplication.homepageLink", 27 | "type": "link", 28 | "label": "reportingui.reportsapp.home.title", 29 | "url": "reportingui/reportsapp/home.page", 30 | "icon": "icon-list-alt", 31 | "order": 5, 32 | "requiredPrivilege": "App: reportingui.reports" 33 | }, 34 | { 35 | "id": "reportingui.dataExports.adHoc", 36 | "extensionPointId": "org.openmrs.module.reportingui.reports.dataexport", 37 | "type": "link", 38 | "label": "reportingui.adHocAnalysis.label", 39 | "url": "reportingui/adHocManage.page", 40 | "order": 9999, 41 | "requiredPrivilege": "App: reportingui.adHocAnalysis", 42 | "featureToggle": "reportingui_adHocAnalysis" 43 | } 44 | ], 45 | "requiredPrivilege": "App: reportingui.reports" 46 | } 47 | ] -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/reportsapp/home.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | ui.includeCss("reportingui", "reportsapp/home.css") 4 | 5 | def appFrameworkService = context.getService(context.loadClass("org.openmrs.module.appframework.service.AppFrameworkService")) 6 | def overviews = appFrameworkService.getExtensionsForCurrentUser("org.openmrs.module.reportingui.reports.overview") 7 | def dataQualityReports = appFrameworkService.getExtensionsForCurrentUser("org.openmrs.module.reportingui.reports.dataquality") 8 | def dataExports = appFrameworkService.getExtensionsForCurrentUser("org.openmrs.module.reportingui.reports.dataexport") 9 | 10 | def contextModel = [:] 11 | %> 12 | 13 | 19 | 20 | 21 |
22 | <% if (overviews) { %> 23 |

${ ui.message("reportingui.reportsapp.overviewReports") }

24 |
    25 | <% overviews.each { %> 26 |
  • 27 | ${ ui.includeFragment("uicommons", "extension", [ extension: it, contextModel: contextModel ]) } 28 |
  • 29 | <% } %> 30 |
31 | <% } %> 32 | 33 | <% if (dataQualityReports) { %> 34 |

${ ui.message("reportingui.reportsapp.dataQualityReports") }

35 |
    36 | <% dataQualityReports.each { %> 37 |
  • 38 | ${ ui.includeFragment("uicommons", "extension", [ extension: it, contextModel: contextModel ]) } 39 |
  • 40 | <% } %> 41 |
42 | <% } %> 43 |
44 | 45 |
46 | <% if (dataExports) { %> 47 |

${ ui.message("reportingui.reportsapp.dataExports") }

48 |
    49 | <% dataExports.each { %> 50 |
  • 51 | ${ ui.includeFragment("uicommons", "extension", [ extension: it, contextModel: contextModel ]) } 52 |
  • 53 | <% } %> 54 |
55 | <% } %> 56 |
-------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/AdHocAnalysisPageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.page.controller; 16 | 17 | import org.apache.commons.lang.StringUtils; 18 | import org.codehaus.jackson.map.ObjectMapper; 19 | import org.openmrs.api.context.Context; 20 | import org.openmrs.module.reporting.dataset.definition.RowPerObjectDataSetDefinition; 21 | import org.openmrs.module.reportingrest.adhoc.AdHocDataSet; 22 | import org.openmrs.module.reportingrest.adhoc.AdHocExportManager; 23 | import org.openmrs.ui.framework.annotation.SpringBean; 24 | import org.openmrs.ui.framework.page.PageModel; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | 27 | public class AdHocAnalysisPageController { 28 | 29 | public void get(@RequestParam(value = "definition", required = false) String uuid, 30 | @RequestParam(value = "definitionClass", required = false) String definitionClass, 31 | @SpringBean AdHocExportManager adHocExportManager, 32 | PageModel model) throws Exception { 33 | 34 | RowPerObjectDataSetDefinition definition = null; 35 | String initialStateJson = null; 36 | if (StringUtils.isNotBlank(uuid)) { 37 | definition = adHocExportManager.getAdHocDataSetByUuid(uuid); 38 | 39 | AdHocDataSet ds = new AdHocDataSet(definition); 40 | 41 | ObjectMapper jackson = new ObjectMapper(); 42 | initialStateJson = jackson.writeValueAsString(ds); 43 | 44 | } 45 | else if (StringUtils.isNotBlank(definitionClass)) { 46 | Class clazz = Context.loadClass(definitionClass); 47 | definition = (RowPerObjectDataSetDefinition) clazz.newInstance(); 48 | definition.setUuid(null); 49 | } 50 | else { 51 | throw new IllegalArgumentException("definition or definitionClass is required"); 52 | } 53 | model.addAttribute("definition", definition); 54 | model.addAttribute("initialStateJson", initialStateJson); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /omod/src/test/java/org/openmrs/module/reportingui/page/controller/RunReportPageControllerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.page.controller; 16 | 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | import org.openmrs.module.reporting.report.definition.ReportDefinition; 20 | import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; 21 | import org.openmrs.module.reporting.report.renderer.RenderingMode; 22 | import org.openmrs.module.reporting.report.service.ReportService; 23 | import org.openmrs.ui.framework.page.PageModel; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import static org.hamcrest.CoreMatchers.is; 29 | import static org.junit.Assert.assertThat; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.when; 32 | 33 | /** 34 | * 35 | */ 36 | public class RunReportPageControllerTest { 37 | 38 | private String reportDefinitionUuid; 39 | private ReportDefinition reportDefinition; 40 | private List renderingModes; 41 | private ReportDefinitionService reportDefinitionService; 42 | private ReportService reportService; 43 | 44 | @Before 45 | public void setUp() throws Exception { 46 | reportDefinitionUuid = "uuid-of-report-definition"; 47 | reportDefinition = new ReportDefinition(); 48 | renderingModes = new ArrayList(); 49 | 50 | reportDefinitionService = mock(ReportDefinitionService.class); 51 | when(reportDefinitionService.getDefinitionByUuid(reportDefinitionUuid)).thenReturn(reportDefinition); 52 | 53 | reportService = mock(ReportService.class); 54 | when(reportService.getRenderingModes(reportDefinition)).thenReturn(renderingModes); 55 | } 56 | 57 | @Test 58 | public void testGet() throws Exception { 59 | PageModel model = new PageModel(); 60 | RunReportPageController controller = new RunReportPageController(); 61 | String breadcrumb = "breadcrumb"; 62 | controller.get(reportDefinitionService, reportService, reportDefinitionUuid, breadcrumb, model); 63 | 64 | assertThat((ReportDefinition) model.getAttribute("reportDefinition"), is(reportDefinition)); 65 | assertThat((List) model.getAttribute("renderingModes"), is(renderingModes)); 66 | assertThat((String) model.getAttribute("breadcrumb"), is(breadcrumb)); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/reportHistory.js: -------------------------------------------------------------------------------- 1 | var runReportApp = angular.module("reportHistoryApp", ['ui.bootstrap']). 2 | filter('translate', function() { 3 | return function(input, prefix) { 4 | var code = prefix ? prefix + input : input; 5 | return emr.message(code, input); 6 | } 7 | }); 8 | 9 | runReportApp.controller('ReportHistoryController', ['$scope', '$http', '$window', '$timeout', function($scope, $http, $window, $timeout) { 10 | 11 | $scope.loading = true; 12 | 13 | $scope.queue = []; 14 | 15 | $scope.completed = []; 16 | 17 | $scope.highlight = window.highlight; 18 | 19 | $scope.refreshHistory = function() { 20 | $http.get("reportStatus/getQueuedRequests.action"). 21 | success(function(data, status, headers, config) { 22 | $scope.queue = data; 23 | $scope.loading = false; 24 | if ($scope.queue.length > 0) { 25 | $timeout($scope.refreshHistory, 10000); 26 | } 27 | }). 28 | error(function(data, status, headers, config) { 29 | console.log("Error getting queue: " + status); 30 | $scope.queue = []; 31 | }); 32 | 33 | $http.get("reportStatus/getCompletedRequests.action"). 34 | success(function(data, status, headers, config) { 35 | $scope.completed = data; 36 | }). 37 | error(function(data, status, headers, config) { 38 | console.log("Error getting completed: " + status); 39 | $scope.completed = []; 40 | }); 41 | } 42 | 43 | $scope.hasResults = function() { 44 | return !$scope.loading && $scope.completed.length > 0; 45 | } 46 | 47 | $scope.hasNoResults = function() { 48 | return !$scope.loading && $scope.completed.length == 0; 49 | } 50 | 51 | var defaultSuccessAction = function(data, status, headers, config) { 52 | emr.successMessage(data.message); 53 | $scope.refreshHistory(); 54 | } 55 | 56 | var defaultErrorAction = function(data, status, headers, config) { 57 | emr.errorMessage(data.message); 58 | $scope.refreshHistory(); 59 | } 60 | 61 | $scope.cancelRequest = function(request) { 62 | $http.post("reportStatus/cancelRequest.action?reportRequest=" + request.uuid). 63 | success(defaultSuccessAction). 64 | error(defaultErrorAction); 65 | } 66 | 67 | $scope.canSave = function(request) { 68 | return request.status == 'COMPLETED'; 69 | } 70 | 71 | $scope.saveRequest = function(request) { 72 | $http.post("reportStatus/saveRequest.action?reportRequest=" + request.uuid). 73 | success(defaultSuccessAction). 74 | error(defaultErrorAction); 75 | } 76 | 77 | $scope.viewStatus = function(request) { 78 | location.href = emr.pageLink('reportingui', 'reportStatus', { request: request.uuid }); 79 | } 80 | 81 | }]); -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/ViewReportRequestPageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.page.controller; 16 | 17 | import org.openmrs.module.reporting.report.ReportRequest; 18 | import org.openmrs.module.reporting.report.renderer.RenderingMode; 19 | import org.openmrs.module.reporting.report.service.ReportService; 20 | import org.openmrs.module.reporting.web.renderers.WebReportRenderer; 21 | import org.openmrs.ui.framework.annotation.SpringBean; 22 | import org.openmrs.ui.framework.page.FileDownload; 23 | import org.springframework.web.bind.annotation.RequestParam; 24 | 25 | /** 26 | * 27 | */ 28 | public class ViewReportRequestPageController { 29 | 30 | public Object get(@SpringBean ReportService reportService, 31 | @RequestParam("request") String requestUuid) { 32 | ReportRequest req = reportService.getReportRequestByUuid(requestUuid); 33 | if (req == null) { 34 | throw new IllegalArgumentException("ReportRequest not found"); 35 | } 36 | 37 | RenderingMode renderingMode = req.getRenderingMode(); 38 | String linkUrl = "/module/reporting/reports/reportHistoryOpen"; 39 | 40 | if (renderingMode.getRenderer() instanceof WebReportRenderer) { 41 | /* 42 | WebReportRenderer webRenderer = (WebReportRenderer) renderingMode.getRenderer(); 43 | linkUrl = webRenderer.getLinkUrl(req.getReportDefinition().getParameterizable()); 44 | linkUrl = request.getContextPath() + (linkUrl.startsWith("/") ? "" : "/") + linkUrl; 45 | if (req != null) { 46 | ReportData reportData = getReportService().loadReportData(req); 47 | if (reportData != null) { 48 | request.getSession().setAttribute(ReportingConstants.OPENMRS_REPORT_DATA, reportData); 49 | request.getSession().setAttribute(ReportingConstants.OPENMRS_REPORT_ARGUMENT, renderingMode.getArgument()); 50 | request.getSession().setAttribute(ReportingConstants.OPENMRS_LAST_REPORT_URL, linkUrl); 51 | } 52 | } 53 | return new ModelAndView(new RedirectView(linkUrl)); 54 | */ 55 | throw new IllegalStateException("Web Renderers not yet implemented"); 56 | } 57 | else { 58 | String filename = renderingMode.getRenderer().getFilename(req).replace(" ", "_"); 59 | String contentType = renderingMode.getRenderer().getRenderedContentType(req); 60 | byte[] data = reportService.loadRenderedOutput(req); 61 | 62 | if (data == null) { 63 | throw new IllegalStateException("Error retrieving the report"); 64 | } else { 65 | return new FileDownload(filename, contentType, data); 66 | } 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/adHocRun.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | ui.includeJavascript("uicommons", "moment.min.js") 4 | ui.includeJavascript("uicommons", "angular.min.js") 5 | ui.includeJavascript("uicommons", "angular-ui/ui-bootstrap-tpls-0.6.0.min.js") 6 | ui.includeJavascript("reportingui", "adHocRun.js") 7 | %> 8 | 9 | <%= ui.includeFragment("appui", "messages", [ codes: [ 10 | "reportingui.adHocReport.timeframe.startDateLabel", 11 | "reportingui.adHocReport.timeframe.endDateLabel", 12 | "reportingui.parameter.type.java.util.Date", 13 | "reportingui.parameter.type.org.openmrs.VisitType", 14 | "reportingui.parameter.type.org.openmrs.Location", 15 | "reportingui.parameter.type.org.openmrs.EncounterType" 16 | 17 | ].flatten() 18 | ]) %> 19 | 20 | 32 | 33 |
34 | 35 |

${ ui.message("reportingui.adHocRun.label") }

36 | 37 | 38 | ${ ui.message("reportingui.adHocRun.noneAvailable") } 39 | 40 | 41 |
42 |
43 |

44 | 45 |

    46 |
  • 47 | 48 | 51 |
  • 52 |
53 |

54 | 55 |

56 | 57 | 58 |

59 | 60 |

61 | 62 | 66 |

67 | 68 | 72 |
73 |
74 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Background 2 | 3 | When you want to get data out of an OpenMRS system, it is sometimes worthwhile to have programmers or advanced administrators design a very specific report or data export, such as might be run "monthly, for each clinic." But sometimes mid-level want to be able to do "ad hoc" analyses and exports, where they explore data and export it on the fly. 4 | 5 | The Reporting Module provides powerful and flexible tools for Row-Per-(Patient, Visit, Encounter, etc) exports, however this functionality only has an administrative UI that is unsuitable for an end user. 6 | 7 | The Reporting Compatibility Module has two key tools (Cohort Builder and Data Export) with friendlier UIs (though they are quite dated). Though these are used by many implementations, they are built on top of a deprecated query and export framework, and they are really past what should be the end of their useful lifetime. 8 | Objectives 9 | 10 | The goal of this project is to build a tool that can be used by doctors and data analysts to do Ad Hoc queries and exports of data. These queries may happen in response to an on-the-fly question from a hospital manager, or in response to a specific suspicion or need that a clinician has to analyze certain data. We want to allow people to access the necessary data without needing a developer or advanced sysadmin to be a bottleneck in the process. 11 | 12 | Specifically, we want to build a UI around row-per-domain-object exports for key domain objects (primarily Patient, Visit, Encounter, and Obs) that is suitable for users of medium sophistication. 13 | 14 | We want to design the tool in a way that it can be used to query for domain objects matching certain criteria (like the Cohort Builder), and also to export details of the matching objects (like the Data Export tool). 15 | ##Design 16 | 17 | The Reporting Module has a RowPerObjectDataSetDefinition base class (with implemantations for Patient, Encounter, etc), and under the hood we should be building instances of this. 18 | 19 | The Reporting Module also supports the idea of "definition libraries" (see the AllDefinitionLibraries class) which include built-in and implementation-configured queries and data definitions. Rather than trying to give users full access to all of the primitive queries in the reporting module, we will focus on letting them access definitions encapsulated in these libraries. 20 | ##Status 21 | 22 | Considerable initial work has been done on this tool already, in the reportingui module, but there is much left to do. 23 | 24 | Further work is captured under the following JIRA epics: 25 | 26 | RA-261 (version 1): https://issues.openmrs.org/browse/RA-261 27 | RA-279 (version 2): https://issues.openmrs.org/browse/RA-279 28 | 29 | ##Dev Notes 30 | 31 | The code is at https://github.com/openmrs/openmrs-module-reportingui. See all files and packages that have "adhoc" in the name. 32 | 33 | The easiest way to test out this code is to: 34 | 35 | check out code from https://github.com/openmrs/openmrs-distro-referenceapplication 36 | add the latest snapshot version of the reportingui module to pom.xml (in several places, alongside everywhere you find the "reporting" module) 37 | follow the instructions at Developer How-To Launch a Local Instance of the Reference Application 38 | The entry point to the tool should be at http://localhost:8080/openmrs/reportingui/adHocManage.page 39 | alternately you can enable the "reportingui_adHocAnalysis" feature toggle, to see a link in the UI. 40 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/adHocRun.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | 16 | var app = angular.module('runAdHocExport', ['ui.bootstrap']). 17 | 18 | filter('translate', function() { 19 | return function(input, prefix) { 20 | if (input && input.uuid) { 21 | input = input.uuid; 22 | } 23 | var code = prefix ? prefix + input : input; 24 | return emr.message(code, input); 25 | } 26 | }). 27 | 28 | controller('RunAdHocExportController', ['$scope', '$http', function($scope, $http) { 29 | 30 | $scope.outputFormat = 'org.openmrs.module.reporting.report.renderer.CsvReportRenderer'; 31 | 32 | $scope.exports = adHocExports; 33 | 34 | _.each($scope.exports, function(item) { 35 | item.selected = initialSelection.indexOf(item.uuid) >= 0; 36 | }); 37 | 38 | $scope.paramValues = { }; 39 | 40 | $scope.requiredParameters = function() { 41 | var params = []; 42 | var addParameterSafely = function(p) { 43 | if (!_.findWhere(params, { name: p.name })) { 44 | params.push(p); 45 | } 46 | } 47 | _.each($scope.exports, function(item) { 48 | if (item.selected) { 49 | _.each(item.parameters, function(p) { 50 | addParameterSafely(p); 51 | }); 52 | } 53 | }); 54 | return params; 55 | } 56 | 57 | var missingParameters = function() { 58 | return _.some($scope.requiredParameters(), function(item) { 59 | return !($scope.paramValues[item.name]); 60 | }); 61 | } 62 | 63 | $scope.canRun = function() { 64 | return _.findWhere($scope.exports, { selected: true }) && !missingParameters(); 65 | } 66 | 67 | function serverFriendly(original) { 68 | if (original instanceof Date) { 69 | return moment(original).format("YYYY-MM-DD HH:mm:ss"); 70 | } 71 | else { 72 | return original; 73 | } 74 | } 75 | 76 | $scope.run = function() { 77 | var uuids = _.pluck(_.where($scope.exports, { selected: true }), 'uuid'); 78 | var params = { 79 | dataset: uuids, 80 | outputFormat: $scope.outputFormat 81 | }; 82 | for (key in $scope.paramValues) { 83 | params['param[' + key + ']'] = serverFriendly($scope.paramValues[key]); 84 | } 85 | 86 | $http.post(emr.fragmentActionLink('reportingui', 'adHocAnalysis', 'runAdHocExport', params)). 87 | success(function(data) { 88 | if (data.uuid) { 89 | location.href = emr.pageLink('reportingui', 'reportStatus', { request: data.uuid }); 90 | } 91 | else { 92 | emr.errorAlert(data.error); 93 | } 94 | }). 95 | error(function() { 96 | emr.errorAlert('ERROR!'); 97 | }); 98 | } 99 | 100 | }]); -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/runReport.js: -------------------------------------------------------------------------------- 1 | var runReportApp = angular.module("runReportApp", [ ]). 2 | filter('translate', function() { 3 | return function(input, prefix) { 4 | var code = prefix ? prefix + input : input; 5 | return emr.message(code, input); 6 | } 7 | }); 8 | 9 | runReportApp.controller('RunReportController', ['$scope', '$http', '$window', '$timeout', function($scope, $http, $window, $timeout) { 10 | 11 | $scope.loading = true; 12 | 13 | $scope.queue = []; 14 | 15 | $scope.completed = []; 16 | 17 | $scope.submitting = false; 18 | 19 | $scope.refreshHistory = function() { 20 | $http.get("reportStatus/getQueuedRequests.action?reportDefinition=" + $window.reportDefinition.uuid). 21 | success(function(data, status, headers, config) { 22 | $scope.queue = data; 23 | $scope.loading = false; 24 | if ($scope.queue.length > 0) { 25 | $timeout($scope.refreshHistory, 10000); 26 | } 27 | }). 28 | error(function(data, status, headers, config) { 29 | console.log("Error getting queue: " + status); 30 | $scope.queue = []; 31 | }); 32 | 33 | $http.get("reportStatus/getCompletedRequests.action?reportDefinition=" + $window.reportDefinition.uuid). 34 | success(function(data, status, headers, config) { 35 | $scope.completed = data; 36 | }). 37 | error(function(data, status, headers, config) { 38 | console.log("Error getting completed: " + status); 39 | $scope.completed = []; 40 | }); 41 | } 42 | 43 | $scope.hasResults = function() { 44 | return !$scope.loading && $scope.completed.length > 0; 45 | } 46 | 47 | $scope.hasNoResults = function() { 48 | return !$scope.loading && $scope.completed.length == 0; 49 | } 50 | 51 | var defaultSuccessAction = function(data, status, headers, config) { 52 | emr.successMessage(data.message); 53 | $scope.refreshHistory(); 54 | } 55 | 56 | var defaultErrorAction = function(data, status, headers, config) { 57 | emr.errorMessage(data.message); 58 | $scope.refreshHistory(); 59 | } 60 | 61 | $scope.cancelRequest = function(request) { 62 | $http.post("reportStatus/cancelRequest.action?reportRequest=" + request.uuid). 63 | success(defaultSuccessAction). 64 | error(defaultErrorAction); 65 | } 66 | 67 | $scope.canSave = function(request) { 68 | return request.status == 'COMPLETED'; 69 | } 70 | 71 | $scope.saveRequest = function(request) { 72 | $http.post("reportStatus/saveRequest.action?reportRequest=" + request.uuid). 73 | success(defaultSuccessAction). 74 | error(defaultErrorAction); 75 | } 76 | 77 | $scope.runReport = function() { 78 | // this is a plain old form, not really using angular. need to rewrite the datepickers to be angular-friendly 79 | var form = angular.element('#run-report'); 80 | var submission = form.serializeArray(); 81 | var missingParams = [ ]; 82 | console.log(submission); 83 | for (var i = 0; i < submission.length; ++i) { 84 | var p = submission[i]; 85 | if (p.name.indexOf("parameterValues[") == 0) { 86 | if (!p.value) { 87 | missingParams.push(p.name); 88 | } 89 | } 90 | } 91 | if (missingParams.length > 0) { 92 | emr.errorMessage(emr.message("reportingui.runReport.missingParameter", "Missing parameter values")); 93 | } 94 | else { 95 | $scope.submitting = true; 96 | $.post("runReport.page?reportDefinition=" + $window.reportDefinition.uuid, submission, function() { 97 | location.href = location.href; 98 | }); 99 | } 100 | } 101 | 102 | }]); -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/adHocManage.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | ui.includeJavascript("uicommons", "moment.min.js") 4 | ui.includeJavascript("uicommons", "angular.min.js") 5 | ui.includeCss("reportingui", "adHocReport.css") 6 | ui.includeJavascript("uicommons", "angular-ui/ui-bootstrap-tpls-0.6.0.min.js") 7 | 8 | def patientDataExports = exports.findAll { it.type == "PatientDataSetDefinition" } 9 | %> 10 | 11 | 49 | 50 |

${ ui.message("reportingui.adHocManage.title") }

51 | 52 |
53 |

${ ui.message("reportingui.adHocManage.group.title.org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition") }

54 | 55 | 56 | 57 | ${ ui.message("reportingui.adHocManage.new") } 58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | <% if (patientDataExports.size() == 0) { %> 71 | 72 | 73 | 74 | <% } %> 75 | <% patientDataExports.each { %> 76 | 77 | 78 | 79 | 84 | 85 | <% } %> 86 | 87 |
${ ui.message("reportingui.adHocReport.name") }${ ui.message("reportingui.adHocReport.description") }${ ui.message("reportingui.adHocManage.actions") }
${ ui.message("emr.none") }
${ it.name }${ it.description ?: "" } 80 | 81 | 82 | 83 |
88 | 89 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/reportStatus.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | 4 | ui.includeJavascript("uicommons", "moment.min.js") 5 | ui.includeJavascript("uicommons", "angular.js") 6 | ui.includeJavascript("uicommons", "angular-resource.min.js") 7 | ui.includeJavascript("uicommons", "angular-sanitize.min.js") 8 | ui.includeJavascript("uicommons", "angular-app.js") 9 | ui.includeJavascript("uicommons", "filters/display.js") 10 | ui.includeJavascript("uicommons", "angular-ui/ui-bootstrap-tpls-0.6.0.min.js") 11 | ui.includeJavascript("uicommons", "services/encounterTypeService.js") 12 | ui.includeJavascript("uicommons", "services/locationService.js") 13 | 14 | ui.includeJavascript("reportingui", "app.js") 15 | ui.includeJavascript("reportingui", "reportStatus.js") 16 | %> 17 | 18 | <%= ui.includeFragment("appui", "messages", [ codes: [ 19 | "reportingui.reportRequest.Status.REQUESTED", 20 | "reportingui.reportRequest.Status.SCHEDULED", 21 | "reportingui.reportRequest.Status.PROCESSING", 22 | "reportingui.reportRequest.Status.FAILED", 23 | "reportingui.reportRequest.Status.COMPLETED", 24 | "reportingui.reportRequest.Status.SCHEDULE_COMPLETED", 25 | "reportingui.reportRequest.Status.SAVED", 26 | "reportingui.outputFormat.CsvReportRenderer", 27 | ].flatten() 28 | ]) %> 29 | 30 | 38 | 39 |
40 | 41 |
42 | ${ ui.message("uicommons.loading.placeholder") } 43 | 44 |
45 | 46 |
47 |
48 | ${ ui.message("reportingui.reportStatus.requestHeading") } 49 | 50 |

51 | ${ ui.message("reportingui.reportStatus.requestedBy") }: {{ reportRequest.requestedBy | omrs.display }} 52 |

53 |

54 | ${ ui.message("reportingui.reportStatus.requestDate") }: {{ reportRequest.requestDate | date:'medium'}} 55 |

56 |

57 | ${ ui.message("reportingui.reportRequest.outputFormat") }: {{ reportRequest.renderingMode.label | omrs.display:'reportingui.outputFormat.' }} 58 |

59 |
60 | 61 |
62 | Status 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {{ reportRequest.status | omrs.display:'reportingui.reportRequest.Status.' }} 90 | 91 |

92 | 96 |

97 | 98 |

99 | 103 |

104 |
105 |
106 | 107 |
108 | 109 | -------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/page/controller/RunReportPageController.java: -------------------------------------------------------------------------------- 1 | package org.openmrs.module.reportingui.page.controller; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.openmrs.module.reporting.evaluation.parameter.Mapped; 5 | import org.openmrs.module.reporting.evaluation.parameter.Parameter; 6 | import org.openmrs.module.reporting.report.ReportRequest; 7 | import org.openmrs.module.reporting.report.definition.ReportDefinition; 8 | import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; 9 | import org.openmrs.module.reporting.report.renderer.RenderingMode; 10 | import org.openmrs.module.reporting.report.service.ReportService; 11 | import org.openmrs.ui.framework.SimpleObject; 12 | import org.openmrs.ui.framework.UiUtils; 13 | import org.openmrs.ui.framework.WebConstants; 14 | import org.openmrs.ui.framework.annotation.SpringBean; 15 | import org.openmrs.ui.framework.page.PageModel; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * 26 | */ 27 | public class RunReportPageController { 28 | 29 | public void get(@SpringBean ReportDefinitionService reportDefinitionService, 30 | @SpringBean ReportService reportService, 31 | @RequestParam("reportDefinition") String reportDefinitionUuid, 32 | @RequestParam(value = "breadcrumb", required = false) String breadcrumb, 33 | PageModel model) throws Exception { 34 | 35 | ReportDefinition reportDefinition = reportDefinitionService.getDefinitionByUuid(reportDefinitionUuid); 36 | if (reportDefinition == null) { 37 | throw new IllegalArgumentException("No reportDefinition with the given uuid"); 38 | } 39 | model.addAttribute("reportDefinition", reportDefinition); 40 | model.addAttribute("renderingModes", reportService.getRenderingModes(reportDefinition)); 41 | model.addAttribute("breadcrumb", breadcrumb); 42 | } 43 | 44 | public String post(@SpringBean ReportDefinitionService reportDefinitionService, 45 | @SpringBean ReportService reportService, 46 | UiUtils ui, 47 | HttpServletRequest request, 48 | @RequestParam("reportDefinition") ReportDefinition reportDefinition, 49 | @RequestParam("renderingMode") String renderingModeDescriptor) { 50 | 51 | RenderingMode renderingMode = null; 52 | for (RenderingMode candidate : reportService.getRenderingModes(reportDefinition)) { 53 | if (candidate.getDescriptor().equals(renderingModeDescriptor)) { 54 | renderingMode = candidate; 55 | break; 56 | } 57 | } 58 | 59 | Collection missingParameters = new ArrayList(); 60 | Map parameterValues = new HashMap(); 61 | for (Parameter parameter : reportDefinition.getParameters()) { 62 | String submitted = request.getParameter("parameterValues[" + parameter.getName() + "]"); 63 | if (parameter.getCollectionType() != null) { 64 | throw new IllegalStateException("Collection parameters not yet implemented"); 65 | } 66 | Object converted; 67 | if (StringUtils.isEmpty(submitted)) { 68 | converted = parameter.getDefaultValue(); 69 | } else { 70 | converted = ui.convert(submitted, parameter.getType()); 71 | } 72 | if (converted == null) { 73 | missingParameters.add(parameter); 74 | } 75 | parameterValues.put(parameter.getName(), converted); 76 | } 77 | if (missingParameters.size() > 0) { 78 | request.getSession().setAttribute(WebConstants.OPENMRS_ERROR_ATTR, ui.message("reportingui.runReport.missingParameter")); 79 | return "redirect:" + ui.pageLink("reportingui", "runReport", SimpleObject.create("reportDefinition", reportDefinition.getUuid())); 80 | } 81 | 82 | ReportRequest reportRequest = new ReportRequest(); 83 | reportRequest.setReportDefinition(new Mapped(reportDefinition, parameterValues)); 84 | reportRequest.setRenderingMode(renderingMode); 85 | //rr.setBaseCohort(command.getBaseCohort()); 86 | //rr.setSchedule(command.getSchedule()); 87 | 88 | // TODO: We might want to check here if this exact same report request is already queued and just re-direct if so 89 | 90 | reportRequest = reportService.queueReport(reportRequest); 91 | reportService.processNextQueuedReports(); 92 | 93 | return "redirect:" + ui.pageLink("reportingui", "runReport", SimpleObject.create("reportDefinition", reportDefinition.getUuid())); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /api/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | reportingui.title=Reporting UI Module 2 | 3 | reportingui.runButtonLabel=Run 4 | 5 | reportingui.runReport.run.legend=Run the report 6 | reportingui.runReport.queue.legend=Queued or Running 7 | reportingui.runReport.completed.legend=Available for Download 8 | reportingui.runReport.missingParameter=Missing parameter values 9 | 10 | reportingui.reportRequest.status=Status 11 | reportingui.reportRequest.parameters=Parameters 12 | reportingui.reportRequest.requested=Requested 13 | reportingui.reportRequest.actions=Actions 14 | reportingui.reportRequest.priority=Priority 15 | reportingui.reportRequest.position=Position in queue 16 | reportingui.reportRequest.outputFormat=Output format 17 | 18 | reportingui.reportRequest.Priority.HIGHEST=Highest 19 | reportingui.reportRequest.Priority.HIGH=High 20 | reportingui.reportRequest.Priority.NORMAL=Normal 21 | reportingui.reportRequest.Priority.LOW=Low 22 | reportingui.reportRequest.Priority.LOWEST=Lowest 23 | 24 | reportingui.reportRequest.Status.REQUESTED=Waiting to be Processed 25 | reportingui.reportRequest.Status.SCHEDULED=Scheduled 26 | reportingui.reportRequest.Status.PROCESSING=Currently Processing 27 | reportingui.reportRequest.Status.FAILED=Failed 28 | reportingui.reportRequest.Status.COMPLETED=Completed 29 | reportingui.reportRequest.Status.SCHEDULE_COMPLETED=Schedule Completed 30 | reportingui.reportRequest.Status.SAVED=Completed and Preserved 31 | 32 | reportingui.outputFormat.CsvReportRenderer=CSV 33 | reportingui.outputFormat.XlsReportRenderer=Excel 34 | 35 | reportingui.reportRequest.cancel.successMessage=Cancelled report request 36 | reportingui.reportRequest.cancel.errorMessage=Failed to cancel report request 37 | 38 | reportingui.reportRequest.save.action=Preserve 39 | reportingui.reportRequest.save.successMessage=Preserved report 40 | reportingui.reportRequest.save.errorMessage=Failed to preserve report 41 | 42 | reportingui.reportHistory.title=History of Reports 43 | reportingui.reportHistory.open=Open 44 | 45 | reportingui.reportStatus.title=Report Status 46 | reportingui.reportStatus.requestHeading=Request 47 | reportingui.reportStatus.requestedBy=Requested by 48 | reportingui.reportStatus.requestDate=Requested on 49 | 50 | reportingui.adHocRun.label=Run Ad Hoc Export 51 | reportingui.adHocRun.noneAvailable=There are no ad hoc data sets available to run. You should create some, or have someone else share their exports with you. 52 | reportingui.adHocRun.dataSets=Data sets to include 53 | reportingui.adHocRun.runButton=Run 54 | reportingui.adHocRun.error.noDatasets=You must choose some datasets 55 | 56 | reportingui.adHocAnalysis.label=Ad Hoc Analysis 57 | 58 | reportingui.parameter.type.java.util.Date = Date 59 | reportingui.parameter.type.org.openmrs.EncounterType = Encounter type 60 | reportingui.parameter.type.org.openmrs.VisitType = Visit type 61 | reportingui.parameter.type.org.openmrs.Location = Location 62 | reportingui.adHocReport.newLabel=New Ad Hoc Analysis 63 | reportingui.adHocReport.parameters.label=Parameters 64 | reportingui.adHocReport.description.label=Save 65 | reportingui.adHocReport.timeframe.startDateLabel=Start date 66 | reportingui.adHocReport.timeframe.endDateLabel=End date 67 | reportingui.adHocReport.searches=Searches 68 | reportingui.adHocReport.columns=Columns 69 | reportingui.adHocReport.next=Next 70 | reportingui.adHocReport.back=Back 71 | reportingui.adHocReport.preview=Preview 72 | reportingui.adHocReport.preview.description=This is a quick preview of the first few rows. The full export will have {{ results.allRows.memberIds.length }} row(s) 73 | reportingui.adHocReport.noResults=No results were found. Please review your search criteria. 74 | reportingui.adHocReport.searchCriteria=Search Criteria 75 | reportingui.adHocReport.searchCriteria.description=Choose how you want to limit the rows of this export 76 | reportingui.adHocReport.searchCriteria.filter.placeholder=Search among available criteria 77 | reportingui.adHocReport.searchCriteria.combination=Intersection of {{ dataExport.rowFilters.length }} search criteria 78 | reportingui.adHocReport.searchCriteria.combination.edit=Custom combination 79 | reportingui.adHocReport.searchCriteria.customRowFilterCombination=Combination: {{ dataExport.customRowFilterCombination }} 80 | reportingui.adHocReport.searchCriteria.combination.example=Example: 1 and not (2 or 3) 81 | reportingui.adHocReport.columns.description=Choose the columns to include in this export 82 | reportingui.adHocReport.columns.filter.placeholder=Search among available columns 83 | reportingui.adHocReport.name=Data Set Name 84 | reportingui.adHocReport.description=Description 85 | reportingui.adHocReport.save.requirements=A dataset must have a name and at least one column before you can save it 86 | reportingui.adHocReport.save.success=Data set saved sucessfully! 87 | reportingui.adHocReport.runLink=Run this export 88 | reportingui.adHocReport.modal.header=Fill in parameter values 89 | reportingui.adHocReport.modal.cancel=Cancel 90 | reportingui.adHocReport.modal.add=Add 91 | 92 | reportingui.adHocManage.title=Ad Hoc Exports 93 | reportingui.adHocManage.group.title.org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition=Patient Data Sets 94 | reportingui.adHocManage.new=New data set 95 | reportingui.adHocManage.actions=Actions 96 | reportingui.adHocManage.confirmDeleteTitle=Delete Data Set 97 | reportingui.adHocManage.confirmDeleteMessage=Are you sure? 98 | 99 | reportingui.reportsapp.home.title=Reports 100 | reportingui.reportsapp.overviewReports=Overview Reports 101 | reportingui.reportsapp.dataQualityReports=Data Quality Reports 102 | reportingui.reportsapp.dataExports=Data Exports -------------------------------------------------------------------------------- /api/src/main/resources/messages_ht.properties: -------------------------------------------------------------------------------- 1 | reportingui.title=Modil Rapò UI 2 | 3 | reportingui.runButtonLabel=Egzekite 4 | 5 | reportingui.runReport.run.legend=Fè rapò a 6 | reportingui.runReport.queue.legend=An Atant ou An Egzekisyon 7 | reportingui.runReport.completed.legend=Disponib pou telechaje 8 | reportingui.runReport.missingParameter=Valè paramèt ki manke yo 9 | 10 | reportingui.reportRequest.status=Pozisyon 11 | reportingui.reportRequest.parameters=Paramèt 12 | reportingui.reportRequest.requested=Kòmande 13 | reportingui.reportRequest.actions=Aksyon Yo 14 | reportingui.reportRequest.priority=Priyorite 15 | reportingui.reportRequest.position=Pozisyon an atant 16 | reportingui.reportRequest.outputFormat=Fòma de sòti 17 | 18 | reportingui.reportRequest.Priority.HIGHEST=Pi Wo 19 | reportingui.reportRequest.Priority.HIGH=Wo 20 | reportingui.reportRequest.Priority.NORMAL=Nòmal 21 | reportingui.reportRequest.Priority.LOW=Ba 22 | reportingui.reportRequest.Priority.LOWEST=Pi Ba 23 | 24 | reportingui.reportRequest.Status.REQUESTED=Ap Tann Trètman 25 | reportingui.reportRequest.Status.SCHEDULED=Previ 26 | reportingui.reportRequest.Status.PROCESSING=Nan Trètman Kounye-a 27 | reportingui.reportRequest.Status.FAILED=Echwe 28 | reportingui.reportRequest.Status.COMPLETED=Tèmine 29 | reportingui.reportRequest.Status.SCHEDULE_COMPLETED=Orè a Fini 30 | reportingui.reportRequest.Status.SAVED=Tèmine e Prezève 31 | 32 | reportingui.outputFormat.CsvReportRenderer=CSV 33 | reportingui.outputFormat.XlsReportRenderer=Excel 34 | 35 | reportingui.reportRequest.cancel.successMessage=Demand rapò anile 36 | reportingui.reportRequest.cancel.errorMessage=Nou pat ka anile demand rapò a 37 | 38 | reportingui.reportRequest.save.action=Prezève 39 | reportingui.reportRequest.save.successMessage=Rapò Ki Prezève 40 | reportingui.reportRequest.save.errorMessage=Nou pat ka prezève rapò a 41 | 42 | reportingui.reportHistory.title=Istwa Rapò yo 43 | reportingui.reportHistory.open=Ouvri 44 | 45 | reportingui.reportStatus.title=Stati Rapo 46 | reportingui.reportStatus.requestHeading=Rekèt 47 | reportingui.reportStatus.requestedBy=Komande pa 48 | reportingui.reportStatus.requestDate=Komande sou 49 | 50 | reportingui.adHocRun.label=Egzekite Ekspòtasyon Ad Hoc 51 | reportingui.adHocRun.noneAvailable=Pa gen okenn seri done ad hoc ki disponib pou anrejistre. Ou ta dwe kreye kèk, oswa fè yon lòt moun pataje ekspòtasyon sa yo ak ou. 52 | reportingui.adHocRun.dataSets=Seri Done 53 | reportingui.adHocRun.runButton=Egzekite 54 | reportingui.adHocRun.error.noDatasets=Ou dwe chwazi kèk Seri Done 55 | 56 | reportingui.adHocAnalysis.label=Analiz Ad Hoc 57 | 58 | reportingui.parameter.type.java.util.Date = Dat 59 | reportingui.parameter.type.org.openmrs.EncounterType = Tip de rankont 60 | reportingui.parameter.type.org.openmrs.VisitType = Tip Vizit 61 | reportingui.parameter.type.org.openmrs.Location = Sit 62 | reportingui.adHocReport.newLabel=Nouvo Analiz Ad Hoc 63 | reportingui.adHocReport.parameters.label=Paramèt Yo 64 | reportingui.adHocReport.description.label=Sovgade 65 | reportingui.adHocReport.timeframe.startDateLabel=Dat Kòmanse\: 66 | reportingui.adHocReport.timeframe.endDateLabel=Dat Fini\: 67 | reportingui.adHocReport.searches=Rechèch Yo 68 | reportingui.adHocReport.columns=Kolòn Yo 69 | reportingui.adHocReport.next=Pwochen 70 | reportingui.adHocReport.back=Retounen 71 | reportingui.adHocReport.preview=Previzyon 72 | reportingui.adHocReport.preview.description=Sa se yon apèsi rapid nan kèk premye ranje yo. Tout Ekspòtasyon an pral gen {{results.allRows.length}} ranje (yo) 73 | reportingui.adHocReport.noResults=Nou pat ka jwenn pyès rezilta. Silvouplè refòmile kritè de rechèch ou te mete a. 74 | reportingui.adHocReport.searchCriteria=Kritè de Rechèch 75 | reportingui.adHocReport.searchCriteria.description=Chwazi kòman ou vle limite ranje yo nan ekspòtasyon sa a 76 | reportingui.adHocReport.searchCriteria.filter.placeholder=Chache Pami Kritè ki Disponib yo 77 | reportingui.adHocReport.searchCriteria.combination=Entèseksyon nan {{dataExport.rowFilter.length}} kritè rechèch 78 | reportingui.adHocReport.searchCriteria.combination.edit=Konbinezon Abityèl 79 | reportingui.adHocReport.searchCriteria.customRowFilterCombination=Konbinezon\: {{dataExport.customRowFilterCombination}} 80 | reportingui.adHocReport.searchCriteria.combination.example=Egzanp\: 1 epi yo pa (2 oswa 3) 81 | reportingui.adHocReport.columns.description=Chwazi kolòn yo ke nou dwe ajoute nan ekspòtasyon sa a 82 | reportingui.adHocReport.columns.filter.placeholder=Chache Pami Kolòn ki Disponib yo 83 | reportingui.adHocReport.name=Non Seri Done yo 84 | reportingui.adHocReport.description=Deskripsyon 85 | reportingui.adHocReport.save.requirements=Yon seri done dwe gen yon non ak omwens yon kolòn anvan ou kapab anrejistre li. 86 | reportingui.adHocReport.save.success=Seri Done yo Enrejistre avèk siksè 87 | reportingui.adHocReport.runLink=Egzekite Ekspòtasyon sa a 88 | reportingui.adHocReport.modal.header=Rampli Valè paramèt yo 89 | reportingui.adHocReport.modal.cancel=Anile 90 | reportingui.adHocReport.modal.add=Ajoute 91 | 92 | reportingui.adHocManage.title=Ekspòtasyon Ad Hoc 93 | reportingui.adHocManage.group.title.org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition=Seri Done sou Pasyan 94 | reportingui.adHocManage.new=Nouvo Seri Done 95 | reportingui.adHocManage.actions=Aksyon 96 | reportingui.adHocManage.confirmDeleteTitle=Efase seri done 97 | reportingui.adHocManage.confirmDeleteMessage=Eske ou si ? 98 | 99 | reportingui.reportsapp.home.title=Rapò Yo 100 | reportingui.reportsapp.overviewReports=Rapò An Rezime Yo 101 | reportingui.reportsapp.dataQualityReports=Rapò Sou Kalite Done 102 | reportingui.reportsapp.dataExports=Ekspòtasyon Done Yo 103 | -------------------------------------------------------------------------------- /api/src/main/resources/messages_fr.properties: -------------------------------------------------------------------------------- 1 | reportingui.title=Module IU de rapports 2 | 3 | reportingui.runButtonLabel=Exécuter 4 | 5 | reportingui.runReport.run.legend=Exécuter ce rapport 6 | reportingui.runReport.queue.legend=En file d'attente ou en cours d'exécution 7 | reportingui.runReport.completed.legend=Disponible pour téléchargement 8 | reportingui.runReport.missingParameter=Valeurs de paramètres manquantes 9 | 10 | reportingui.reportRequest.status=État 11 | reportingui.reportRequest.parameters=Paramètres 12 | reportingui.reportRequest.requested=Demandé 13 | reportingui.reportRequest.actions=Actions 14 | reportingui.reportRequest.priority=Priorité 15 | reportingui.reportRequest.position=Position dans la file d'attente 16 | reportingui.reportRequest.outputFormat=Format de sortie 17 | 18 | reportingui.reportRequest.Priority.HIGHEST=Très haut 19 | reportingui.reportRequest.Priority.HIGH=Haut 20 | reportingui.reportRequest.Priority.NORMAL=Normal 21 | reportingui.reportRequest.Priority.LOW=Bas 22 | reportingui.reportRequest.Priority.LOWEST=Très bas 23 | 24 | reportingui.reportRequest.Status.REQUESTED=En attente de traitement 25 | reportingui.reportRequest.Status.SCHEDULED=Programmé 26 | reportingui.reportRequest.Status.PROCESSING=Traitement en cours 27 | reportingui.reportRequest.Status.FAILED=\ Échec 28 | reportingui.reportRequest.Status.COMPLETED=Terminé 29 | reportingui.reportRequest.Status.SCHEDULE_COMPLETED=Calendrier effectué 30 | reportingui.reportRequest.Status.SAVED=Effectué et conserv 31 | 32 | reportingui.outputFormat.CsvReportRenderer=CSV 33 | reportingui.outputFormat.XlsReportRenderer=Excel 34 | 35 | reportingui.reportRequest.cancel.successMessage=Demande de rapport annulée 36 | reportingui.reportRequest.cancel.errorMessage=L'annulation de demande de rapport a échou 37 | 38 | reportingui.reportRequest.save.action=Conserver 39 | reportingui.reportRequest.save.successMessage=Rapport conserv 40 | reportingui.reportRequest.save.errorMessage=La conservation de rapport a échou 41 | 42 | reportingui.reportHistory.title=Historique des rapports 43 | reportingui.reportHistory.open=Ouvrir 44 | 45 | reportingui.reportStatus.title=État du rapport 46 | reportingui.reportStatus.requestHeading=Demande 47 | reportingui.reportStatus.requestedBy=Demandé par 48 | reportingui.reportStatus.requestDate=Demandé le 49 | 50 | reportingui.adHocRun.label=Exécuter l'exportation ad hoc 51 | reportingui.adHocRun.noneAvailable=Il n'y a pas de groupes de données ad hoc disponibles. Vous devez en créer ou trouver d'autres exportations à partager. 52 | reportingui.adHocRun.dataSets=Groupes de données à inclure 53 | reportingui.adHocRun.runButton=Exécuter 54 | reportingui.adHocRun.error.noDatasets=Vous devez choisir des groupes de données 55 | 56 | reportingui.adHocAnalysis.label=Analyse ad hoc 57 | 58 | reportingui.parameter.type.java.util.Date = Date 59 | reportingui.parameter.type.org.openmrs.EncounterType = Type de rencontre 60 | reportingui.parameter.type.org.openmrs.VisitType = Type de consultation 61 | reportingui.parameter.type.org.openmrs.Location = Lieu 62 | reportingui.adHocReport.newLabel=Nouvelle analyse ad hoc 63 | reportingui.adHocReport.parameters.label=Paramètres 64 | reportingui.adHocReport.description.label=Enregistrer 65 | reportingui.adHocReport.timeframe.startDateLabel=Date de début 66 | reportingui.adHocReport.timeframe.endDateLabel=Date de fin 67 | reportingui.adHocReport.searches=Recherches 68 | reportingui.adHocReport.columns=Colonnes 69 | reportingui.adHocReport.next=Suivant 70 | reportingui.adHocReport.back=Retour 71 | reportingui.adHocReport.preview=Aperçu 72 | reportingui.adHocReport.preview.description=Ceci est un bref aperçu des quelques premières lignes. L'exportation complète comportera {{ results.allRows.memberIds.length }} ligne(s) 73 | reportingui.adHocReport.noResults=Aucun résultat. Veuillez modifier vos critères de recherche. 74 | reportingui.adHocReport.searchCriteria=Critères de recherche 75 | reportingui.adHocReport.searchCriteria.description=Choisissez comment vous voulez limiter les lignes de cette exportation 76 | reportingui.adHocReport.searchCriteria.filter.placeholder=Rechercher parmi les critères disponibles 77 | reportingui.adHocReport.searchCriteria.combination=Intersection des critères de recherche {{ dataExport.rowFilters.length }} 78 | reportingui.adHocReport.searchCriteria.combination.edit=Combinaison personnalisée 79 | reportingui.adHocReport.searchCriteria.customRowFilterCombination=Combinaison \: {{ dataExport.customRowFilterCombination }} 80 | reportingui.adHocReport.searchCriteria.combination.example=Exemple \: 1 et non (2 ou 3) 81 | reportingui.adHocReport.columns.description=Choisissez les colonnes à inclure dans cette exportation 82 | reportingui.adHocReport.columns.filter.placeholder=Rechercher parmi les colonnes disponibles 83 | reportingui.adHocReport.name=Nom du groupe de données 84 | reportingui.adHocReport.description=Description 85 | reportingui.adHocReport.save.requirements=Un groupe de données doit avoir un nom et au moins une colonne avant de pouvoir être enregistré 86 | reportingui.adHocReport.save.success=Le groupe de données a été enregistré. 87 | reportingui.adHocReport.runLink=Exécuter cette exportation 88 | reportingui.adHocReport.modal.header=Remplir les valeurs de paramètres 89 | reportingui.adHocReport.modal.cancel=Annuler 90 | reportingui.adHocReport.modal.add=Ajouter 91 | 92 | reportingui.adHocManage.title=Exportations ad hoc 93 | reportingui.adHocManage.group.title.org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition=Groupes de données patients 94 | reportingui.adHocManage.new=Nouveau groupe de données 95 | reportingui.adHocManage.actions=Actions 96 | reportingui.adHocManage.confirmDeleteTitle=Supprimer des données patients 97 | reportingui.adHocManage.confirmDeleteMessage=Veuillez confirmer. 98 | 99 | reportingui.reportsapp.home.title=Rapports 100 | reportingui.reportsapp.overviewReports=Rapports de vue d'ensemble 101 | reportingui.reportsapp.dataQualityReports=Rapports de qualité des données 102 | reportingui.reportsapp.dataExports=Exportations de données 103 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/reportHistory.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | 4 | ui.includeJavascript("uicommons", "angular.min.js") 5 | ui.includeJavascript("uicommons", "angular-ui/ui-bootstrap-tpls-0.6.0.min.js") 6 | ui.includeJavascript("reportingui", "reportHistory.js") 7 | 8 | def interactiveClass = context.loadClass("org.openmrs.module.reporting.report.renderer.InteractiveReportRenderer") 9 | def isInteractive = { 10 | interactiveClass.isInstance(it.renderer) 11 | } 12 | %> 13 | 14 | 23 | 24 |
25 | 26 |
27 | ${ ui.message("reportingui.runReport.queue.legend") } 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 56 | 61 | 65 | 71 | 72 | 73 |
${ ui.message("reportingui.reportRequest.status") }${ ui.message("reportingui.reportRequest.parameters") }${ ui.message("reportingui.reportRequest.requested") }${ ui.message("reportingui.reportRequest.actions") }
41 | {{ request.reportDefinition.name }} 42 | 44 | 45 | {{request.status | translate:'reportingui.reportRequest.Status.'}} 46 | 47 |
48 | ${ ui.message("reportingui.reportRequest.priority") }: {{request.priority | translate:'reportingui.ReportRequest.Priority.'}}
49 | ${ ui.message("reportingui.reportRequest.position") }: {{request.positionInQueue}} 50 |
51 | 52 |
53 | {{request.evaluateCompleteDatetime}} 54 |
55 |
57 | 58 | {{ param.value }}
59 |
60 |
62 | {{request.requestedBy}}
63 | {{request.requestDate}} 64 |
66 | 67 | 68 | ${ ui.message("emr.cancel") } 69 | 70 |
74 |
75 | 76 |
77 | ${ ui.message("reportingui.runReport.completed.legend") } 78 | 79 | ") }"> 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 98 | 104 | 109 | 113 | 140 | 141 | 142 |
${ ui.message("reportingui.reportRequest.status") }${ ui.message("reportingui.reportRequest.parameters") }${ ui.message("reportingui.reportRequest.requested") }${ ui.message("reportingui.reportRequest.actions") }
96 | {{ request.reportDefinition.name }} 97 | 99 | 100 | {{request.status | translate:'reportingui.reportRequest.Status.'}}
101 | {{request.evaluateCompleteDatetime}} 102 |
103 |
105 | 106 | {{ param.value }}
107 |
108 |
110 | {{request.requestedBy}}
111 | {{request.requestDate}} 112 |
114 | 115 | 116 | 117 | 118 | ${ ui.message("reportingui.reportHistory.open") } 119 | 120 | 121 | 122 | ${ ui.message("uicommons.downloadButtonLabel") } 123 | 124 | 125 |
126 | 127 | 128 | ${ ui.message("reportingui.reportRequest.save.action") } 129 | 130 |
131 | 132 | 133 | Error details 134 | 135 | 136 | 137 | 138 | 139 |
143 |
144 | 145 |
-------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/fragment/controller/AdHocAnalysisFragmentController.java: -------------------------------------------------------------------------------- 1 | package org.openmrs.module.reportingui.fragment.controller; 2 | 3 | import org.codehaus.jackson.JsonNode; 4 | import org.codehaus.jackson.map.ObjectMapper; 5 | import org.codehaus.jackson.type.TypeReference; 6 | import org.joda.time.format.DateTimeFormatter; 7 | import org.joda.time.format.ISODateTimeFormat; 8 | import org.openmrs.api.context.Context; 9 | import org.openmrs.module.reporting.dataset.DataSet; 10 | import org.openmrs.module.reporting.dataset.DataSetColumn; 11 | import org.openmrs.module.reporting.dataset.DataSetRow; 12 | import org.openmrs.module.reporting.dataset.definition.RowPerObjectDataSetDefinition; 13 | import org.openmrs.module.reporting.definition.library.AllDefinitionLibraries; 14 | import org.openmrs.module.reporting.report.ReportRequest; 15 | import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; 16 | import org.openmrs.module.reporting.report.renderer.RenderingMode; 17 | import org.openmrs.module.reporting.report.renderer.ReportRenderer; 18 | import org.openmrs.module.reporting.report.service.ReportService; 19 | import org.openmrs.module.reportingrest.adhoc.AdHocDataSet; 20 | import org.openmrs.module.reportingrest.adhoc.AdHocExportManager; 21 | import org.openmrs.ui.framework.SimpleObject; 22 | import org.openmrs.ui.framework.UiUtils; 23 | import org.openmrs.ui.framework.annotation.SpringBean; 24 | import org.springframework.web.bind.annotation.RequestParam; 25 | 26 | import javax.servlet.http.HttpServletRequest; 27 | import java.io.IOException; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.Enumeration; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Set; 36 | 37 | /** 38 | * 39 | */ 40 | public class AdHocAnalysisFragmentController { 41 | 42 | private DateTimeFormatter iso8601= ISODateTimeFormat.dateTime(); 43 | 44 | public SimpleObject saveDataExport(@RequestParam("dataSet") String dataSetJson, 45 | @SpringBean ReportDefinitionService reportDefinitionService, 46 | @SpringBean AdHocExportManager adHocExportManager, 47 | @SpringBean AllDefinitionLibraries definitionLibraries, 48 | UiUtils ui) throws Exception { 49 | ObjectMapper jackson = new ObjectMapper(); 50 | AdHocDataSet dataSet = jackson.readValue(dataSetJson, AdHocDataSet.class); 51 | 52 | RowPerObjectDataSetDefinition dsd = dataSet.toDataSetDefinition(adHocExportManager, definitionLibraries); 53 | 54 | dsd = adHocExportManager.saveAdHocDataSet(dsd); 55 | 56 | SimpleObject ret = SimpleObject.fromObject(dsd, ui, "uuid", "name", "description"); 57 | ret.put("name", ((String) ret.get("name")).substring(AdHocExportManager.NAME_PREFIX.length())); 58 | return ret; 59 | } 60 | 61 | private boolean notNull(JsonNode node) { 62 | return node != null && !node.isMissingNode() && !node.isNull(); 63 | } 64 | 65 | private Object parseParameterValue(JsonNode param) throws Exception { 66 | // param looks like 67 | // { 68 | // "name":"effectiveDate", 69 | // "type":"java.util.Date", 70 | // "collectionType":null, 71 | // "value":"2013-04-03T04:00:00.000Z" 72 | // } 73 | if (notNull(param.get("collectionType"))) { 74 | throw new IllegalStateException("collection parameters are not yet implemented"); 75 | } 76 | Class clazz = Context.loadClass(param.get("type").getTextValue()); 77 | if (Date.class.equals(clazz)) { 78 | return new SimpleDateFormat("yyyy-MM-dd").parse(param.get("value").getValueAsText()); 79 | } 80 | else { 81 | throw new IllegalStateException("type " + clazz.getName() + " is not yet implemented"); 82 | } 83 | } 84 | 85 | public SimpleObject runAdHocExport(@RequestParam("dataset") List dsdUuids, 86 | @RequestParam("outputFormat") String outputFormat, 87 | //@MethodParam("getParamValues") Map paramValues, // UIFR-137 88 | HttpServletRequest req, 89 | @SpringBean AdHocExportManager adHocExportManager, 90 | @SpringBean ReportService reportService, 91 | UiUtils ui) throws Exception { 92 | if (dsdUuids.size() == 0) { 93 | return SimpleObject.create("error", ui.message("reportingui.adHocRun.error.noDatasets")); 94 | } 95 | 96 | RenderingMode mode = new RenderingMode((ReportRenderer) Context.loadClass(outputFormat).newInstance(), outputFormat, null, 0); 97 | 98 | Map paramValues = getParamValues(req); 99 | ReportRequest reportRequest = adHocExportManager.buildExportRequest(dsdUuids, paramValues, mode); 100 | reportRequest.setDescription("[Ad Hoc Export]"); 101 | reportRequest = reportService.queueReport(reportRequest); 102 | reportService.processNextQueuedReports(); 103 | 104 | return SimpleObject.create("uuid", reportRequest.getUuid()); 105 | } 106 | 107 | /** 108 | * Used by runAdHocDataExport. TODO: refactor so that page can also use #parseParameterValues 109 | * @param request 110 | * @return 111 | */ 112 | private Map getParamValues(HttpServletRequest request) { 113 | Map paramValues = new HashMap(); 114 | for (Enumeration e = request.getParameterNames(); e.hasMoreElements(); ) { 115 | String name = e.nextElement(); 116 | if (name.startsWith("param[")) { 117 | Object value = request.getParameter(name); 118 | name = name.substring(name.indexOf("[") + 1, name.lastIndexOf("]")); 119 | try { 120 | value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse((String) value); 121 | } catch (Exception e1) { 122 | // pass 123 | } 124 | paramValues.put(name, value); 125 | } 126 | } 127 | return paramValues; 128 | } 129 | 130 | private Map parseParameterValues(ObjectMapper jackson, String json) throws IOException { 131 | // Expected json: { "startDate": "2013-12-01", "endDate": "2013-12-07" } 132 | Map map = jackson.readValue(json, new TypeReference>() { }); 133 | for (Map.Entry entry : map.entrySet()) { 134 | try { 135 | entry.setValue(iso8601.parseDateTime((String) entry.getValue()).toDate()); 136 | } catch (Exception e1) { 137 | // pass 138 | } 139 | } 140 | return map; 141 | } 142 | 143 | private List getColumnNames(DataSet data) { 144 | List list = new ArrayList(); 145 | for (DataSetColumn dataSetColumn : data.getMetaData().getColumns()) { 146 | list.add(dataSetColumn.getLabel()); 147 | } 148 | return list; 149 | } 150 | 151 | private List transform(DataSet data, UiUtils ui) { 152 | List columns = data.getMetaData().getColumns(); 153 | 154 | List> list = new ArrayList>(); 155 | for (DataSetRow row : data) { 156 | List simpleRow = new ArrayList(); 157 | Map columnValues = row.getColumnValues(); 158 | for (DataSetColumn column : columns) { 159 | simpleRow.add(ui.format(columnValues.get(column))); 160 | } 161 | list.add(simpleRow); 162 | } 163 | 164 | return list; 165 | } 166 | 167 | public class Result { 168 | 169 | private Set allRows; 170 | 171 | private List columnNames; 172 | 173 | private List> data; 174 | 175 | public Result() { } 176 | 177 | public Set getAllRows() { 178 | return allRows; 179 | } 180 | 181 | public void setAllRows(Set allRows) { 182 | this.allRows = allRows; 183 | } 184 | 185 | public List getColumnNames() { 186 | return columnNames; 187 | } 188 | 189 | public void setColumnNames(List columnNames) { 190 | this.columnNames = columnNames; 191 | } 192 | 193 | public List> getData() { 194 | return data; 195 | } 196 | 197 | public void setData(List> data) { 198 | this.data = data; 199 | } 200 | 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /omod/src/main/compass/sass/adHocReport.scss: -------------------------------------------------------------------------------- 1 | .ad-hoc-report { 2 | h1 { 3 | margin-bottom: 5px; 4 | } 5 | 6 | h6 { 7 | margin-top: 20px; 8 | font-size: 1em; 9 | } 10 | 11 | fieldset { 12 | padding: 5px; 13 | display: block; 14 | margin-top: 10px; 15 | margin-bottom: 10px; 16 | } 17 | 18 | h2 { 19 | margin-bottom: 0; 20 | } 21 | 22 | h3 { 23 | margin-left: 5px; 24 | margin-top: 0; 25 | } 26 | 27 | a.button { 28 | min-width: 80px; 29 | } 30 | 31 | .step { 32 | min-height: 400px; 33 | } 34 | 35 | .navigation { 36 | margin: 20px 0; 37 | } 38 | 39 | .big-table { 40 | overflow-y: scroll; 41 | } 42 | 43 | .step-content { 44 | min-height: 300px; 45 | margin-top: 15px; 46 | } 47 | 48 | p { 49 | margin-bottom: 10px; 50 | 51 | label { 52 | display: block; 53 | } 54 | } 55 | 56 | .icon-chevron-right { 57 | font-size: 1.4em; 58 | position: relative; 59 | top: 200px; 60 | } 61 | 62 | .view-all { 63 | margin-left: 10px; 64 | font-size: 0.9em; 65 | } 66 | 67 | .parameter { 68 | background: darken(#5B57A6, 20%); 69 | color: white; 70 | margin: 5px 10px 0 5px; 71 | padding: 3px; 72 | border-radius: 3px; 73 | width: 40%; 74 | } 75 | 76 | .item { 77 | background: darken(#5B57A6, 20%); 78 | color: white; 79 | margin: 5px 10px 0 5px; 80 | padding: 3px; 81 | border-radius: 3px; 82 | width: 96%; 83 | float: right; 84 | 85 | i { 86 | cursor: pointer; 87 | padding: 0; 88 | float: right; 89 | position: relative; 90 | top: 3px; 91 | } 92 | } 93 | 94 | .ui-autocomplete-input { 95 | width: 350px; 96 | height: 24px; 97 | } 98 | 99 | .actions { 100 | float: right; 101 | margin-left: 5px; 102 | 103 | a, a:active, a:visited { 104 | cursor: pointer; 105 | text-decoration: none; 106 | } 107 | } 108 | 109 | .link { 110 | margin-top: 10px; 111 | } 112 | 113 | em { 114 | margin: 0 5px; 115 | } 116 | 117 | .summary { 118 | display: inline-block; 119 | font-size: 0.8em; 120 | box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 2px 4px #aaa; 121 | 122 | .summary-parameter { 123 | width: 82px; 124 | cursor: pointer; 125 | background: #eee; 126 | padding: 7px; 127 | padding-left: 22px; 128 | display: inline-block; 129 | text-align: center; 130 | vertical-align: top; 131 | 132 | &:not(:last-child):before { 133 | content: " "; 134 | display: block; 135 | width: 0; 136 | height: 0; 137 | border-top: 17px solid transparent; 138 | border-bottom: 17px solid transparent; 139 | border-left: 17px solid #ccc; 140 | position: absolute; 141 | margin-top: -8px; 142 | margin-left: 91px; 143 | z-index: 2; 144 | } 145 | 146 | &:not(:last-child):after { 147 | content: " "; 148 | display: block; 149 | width: 0; 150 | height: 0; 151 | border-top: 16px solid transparent; 152 | border-bottom: 16px solid transparent; 153 | border-left: 16px solid #eee; 154 | position: absolute; 155 | margin-top: -25px; 156 | margin-left: 89px; 157 | z-index: 2; 158 | } 159 | 160 | &.current { 161 | color: white; 162 | background: #007FFF; 163 | 164 | &:after { 165 | border-left: 17px solid #007FFF; 166 | } 167 | } 168 | 169 | &.done { 170 | background: #3F9E66; 171 | color: white; 172 | 173 | &:after { 174 | border-left: 17px solid #3F9E66; 175 | } 176 | } 177 | } 178 | 179 | h5 { 180 | color: #333; 181 | } 182 | 183 | .disabled { 184 | color: lighten(#333, 50%); 185 | } 186 | } 187 | 188 | #edit-button { 189 | float: right; 190 | } 191 | } 192 | 193 | #searches, #columns { 194 | ul { 195 | width: 48%; 196 | display: inline-block; 197 | vertical-align: top; 198 | border: 2px solid #ccc; 199 | height: 400px; 200 | overflow: auto; 201 | margin-top: 15px; 202 | user-select: none; 203 | -webkit-user-select: none; 204 | 205 | input { 206 | margin: 5px; 207 | margin-bottom: 10px; 208 | } 209 | } 210 | 211 | .ul-header { 212 | input { 213 | margin: 0; 214 | width: 98%; 215 | } 216 | 217 | background: #eee; 218 | padding: 5px; 219 | border-bottom: 2px solid #ccc; 220 | 221 | &.selected { 222 | background: #3F9E66; 223 | color: white; 224 | } 225 | } 226 | } 227 | 228 | .option{ 229 | padding: 3px 5px; 230 | cursor: pointer; 231 | 232 | &:nth-child(odd) { 233 | background: #eee; 234 | } 235 | 236 | &:hover { 237 | background: darken(#5B57A6, 20%); 238 | color: white; 239 | } 240 | } 241 | 242 | .no-results-message { 243 | margin: 10px 0 10px 0; 244 | } 245 | 246 | .no-results button { 247 | display: block; 248 | } 249 | 250 | .definition-description { 251 | font-size: 0.75em; 252 | display: block; 253 | } 254 | 255 | .report-header { 256 | h3 { 257 | display: inline-block; 258 | } 259 | } 260 | 261 | .button.confirm { 262 | padding: 5px; 263 | margin-left: 271px; 264 | } 265 | 266 | .manage-adhoc-reports { 267 | width: 60%; 268 | margin: 0; 269 | margin-top: 10px; 270 | 271 | i { 272 | padding: 0; 273 | margin: 0; 274 | } 275 | 276 | a { 277 | margin-right: 4px; 278 | } 279 | 280 | } 281 | 282 | .definition-param { 283 | font-size: 0.75em; 284 | display: list-item; 285 | } 286 | 287 | .modal-definition { 288 | margin-bottom: 25px; 289 | } 290 | 291 | .modal-definition-name { 292 | font-weight: bold; 293 | } 294 | 295 | .modal-params { 296 | margin-bottom: 25px 297 | } 298 | 299 | .modal-params > div { 300 | margin-bottom: 5px; 301 | } 302 | 303 | .modal-header { 304 | background: #3F9E66; 305 | color: white; 306 | padding-left: 10px; 307 | } 308 | 309 | .modal-inputs { 310 | margin-bottom: 5px; 311 | } 312 | 313 | .button-left { 314 | float: left; 315 | } 316 | 317 | .button-right { 318 | float: right; 319 | } 320 | 321 | .cancel-button { 322 | background: #FF5A56 !important; 323 | border: 1px; 324 | border-radius: 3px; 325 | color: white; 326 | text-shadow: rgba(254,252,252,0.5) 0px 1px 0px; 327 | } 328 | 329 | .add-button { 330 | background: #96BA24 !important; 331 | border: 1px; 332 | border-radius: 3px; 333 | color: white; 334 | text-shadow: rgba(254,252,252,0.5) 0px 1px 0px; 335 | } 336 | 337 | .modal-backdrop { 338 | position: fixed; 339 | top: 0; 340 | right: 0; 341 | bottom: 0; 342 | left: 0; 343 | z-index: 1040; 344 | background-color: #000000; 345 | } 346 | .modal-backdrop.fade { 347 | opacity: 0; 348 | } 349 | .modal-backdrop, 350 | .modal-backdrop.fade.in { 351 | opacity: 0.8; 352 | filter: alpha(opacity=80); 353 | } 354 | .modal { 355 | position: fixed; 356 | top: 5%; 357 | left: 50%; 358 | z-index: 1050; 359 | width: 960px; 360 | margin-left: -480px; 361 | background-color: #ffffff; 362 | border: 1px solid #999; 363 | border: 1px solid rgba(0, 0, 0, 0.3); 364 | *border: 1px solid #999; 365 | /* IE6-7 */ 366 | 367 | -webkit-border-radius: 6px; 368 | -moz-border-radius: 6px; 369 | border-radius: 6px; 370 | -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); 371 | -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); 372 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); 373 | -webkit-background-clip: padding-box; 374 | -moz-background-clip: padding-box; 375 | background-clip: padding-box; 376 | outline: none; 377 | } 378 | .modal.fade { 379 | -webkit-transition: opacity .3s linear, top .3s ease-out; 380 | -moz-transition: opacity .3s linear, top .3s ease-out; 381 | -o-transition: opacity .3s linear, top .3s ease-out; 382 | transition: opacity .3s linear, top .3s ease-out; 383 | top: -25%; 384 | } 385 | .modal.fade.in { 386 | top: 5%; 387 | } 388 | .modal-header { 389 | padding: 9px 15px; 390 | border-bottom: 1px solid #eee; 391 | } 392 | .modal-header .close { 393 | margin-top: 2px; 394 | } 395 | .modal-header h3 { 396 | margin: 0; 397 | line-height: 30px; 398 | } 399 | .modal-body { 400 | position: relative; 401 | overflow-y: auto; 402 | max-height: 500px; 403 | padding: 15px; 404 | } 405 | .modal-form { 406 | margin-bottom: 0; 407 | } 408 | .modal-footer { 409 | padding: 14px 15px 15px; 410 | margin-bottom: 0; 411 | text-align: right; 412 | background-color: #f5f5f5; 413 | border-top: 1px solid #ddd; 414 | -webkit-border-radius: 0 0 6px 6px; 415 | -moz-border-radius: 0 0 6px 6px; 416 | border-radius: 0 0 6px 6px; 417 | -webkit-box-shadow: inset 0 1px 0 #ffffff; 418 | -moz-box-shadow: inset 0 1px 0 #ffffff; 419 | box-shadow: inset 0 1px 0 #ffffff; 420 | *zoom: 1; 421 | } 422 | .modal-footer:before, 423 | .modal-footer:after { 424 | display: table; 425 | content: ""; 426 | line-height: 0; 427 | } 428 | .modal-footer:after { 429 | clear: both; 430 | } 431 | .modal-footer .btn + .btn { 432 | margin-left: 5px; 433 | margin-bottom: 0; 434 | } 435 | .modal-footer .btn-group .btn + .btn { 436 | margin-left: -1px; 437 | } 438 | .modal-footer .btn-block + .btn-block { 439 | margin-left: 0; 440 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.openmrs.module 6 | reportingui 7 | 1.0-SNAPSHOT 8 | pom 9 | Reporting UI Module 10 | Integration between reporting, UI Framework, and App UI 11 | https://wiki.openmrs.org/display/docs/Reporting+UI+Module+Module 12 | 13 | 14 | 15 | OpenMRS 16 | 17 | 18 | 19 | 20 | OpenMRS 21 | http://openmrs.org 22 | 23 | 24 | 25 | scm:git:git@github.com:openmrs/openmrs-module-reportingui.git 26 | scm:git:git@github.com:openmrs/openmrs-module-reportingui.git 27 | scm:git:git@github.com:openmrs/openmrs-module-reportingui.git 28 | 29 | 30 | 31 | api 32 | omod 33 | 34 | 35 | 36 | 1.9.4 37 | 0.9.3 38 | 1.4-SNAPSHOT 39 | 2.5 40 | 3.2 41 | 1.0 42 | 1.1 43 | 0.2.7 44 | 2.0 45 | 1.0 46 | 1.5.0 47 | UTF-8 48 | 49 | 50 | 51 | 52 | org.openmrs.api 53 | openmrs-api 54 | ${openmrsCoreVersion} 55 | jar 56 | provided 57 | 58 | 59 | org.openmrs.api 60 | openmrs-api 61 | ${openmrsCoreVersion} 62 | test-jar 63 | test 64 | 65 | 66 | org.openmrs.test 67 | openmrs-test 68 | ${openmrsCoreVersion} 69 | pom 70 | test 71 | 72 | 73 | 74 | org.openmrs.module 75 | reporting-api 76 | ${reportingModuleVersion} 77 | provided 78 | 79 | 80 | org.openmrs.module 81 | reporting-api 82 | ${reportingModuleVersion} 83 | test-jar 84 | test 85 | 86 | 87 | org.openmrs.module 88 | serialization.xstream-api 89 | ${serializationxstreamModuleVersion} 90 | provided 91 | 92 | 93 | org.openmrs.module 94 | calculation-api 95 | ${calculationModuleVersion} 96 | provided 97 | 98 | 99 | 100 | org.openmrs.module 101 | reportingrest-api 102 | ${reportingrestModuleVersion} 103 | provided 104 | 105 | 106 | 107 | org.openmrs.module 108 | uiframework-api 109 | ${uiframeworkModuleVersion} 110 | provided 111 | 112 | 113 | 114 | org.openmrs.module 115 | appframework-api 116 | ${appframeworkModuleVersion} 117 | provided 118 | 119 | 120 | org.openmrs.module 121 | appui-api 122 | ${appuiModuleVersion} 123 | provided 124 | 125 | 126 | org.openmrs.module 127 | appui-api 128 | ${appuiModuleVersion} 129 | test 130 | test-jar 131 | 132 | 133 | 134 | org.codehaus.jackson 135 | jackson-core-asl 136 | ${jacksonVersion} 137 | provided 138 | 139 | 140 | 141 | org.codehaus.jackson 142 | jackson-mapper-asl 143 | ${jacksonVersion} 144 | provided 145 | 146 | 147 | 148 | javax.servlet 149 | servlet-api 150 | 2.5 151 | provided 152 | 153 | 154 | 156 | 157 | rubygems 158 | sass 159 | 3.2.10 160 | gem 161 | provided 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-compiler-plugin 172 | 173 | 1.6 174 | 1.6 175 | 176 | 177 | 178 | org.openmrs.maven.plugins 179 | maven-openmrs-plugin 180 | 1.0.1 181 | 182 | 183 | org.apache.maven.plugins 184 | maven-dependency-plugin 185 | 2.4 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-release-plugin 190 | 2.3.2 191 | 192 | true 193 | @{project.version} 194 | 195 | 196 | 197 | org.jasig.maven 198 | sass-maven-plugin 199 | 1.1.1 200 | 201 | ${basedir}/src/main/webapp 202 | ${basedir}/src/main/webapp/resources/styles 203 | ${basedir}/src/main/webapp/resources/styles 204 | scss 205 | 206 | 207 | 208 | de.saumya.mojo 209 | gem-maven-plugin 210 | 1.0.0-rc4 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | openmrs-repo 219 | OpenMRS Nexus Repository 220 | http://mavenrepo.openmrs.org/nexus/content/repositories/public 221 | 222 | 223 | openmrs-repo-modules 224 | OpenMRS Modules Nexus Repository 225 | http://mavenrepo.openmrs.org/nexus/content/repositories/modules 226 | 227 | 228 | rubygems-releases 229 | http://rubygems-proxy.torquebox.org/releases 230 | 231 | 232 | 233 | 234 | 235 | openmrs-repo 236 | OpenMRS Nexus Repository 237 | http://mavenrepo.openmrs.org/nexus/content/repositories/public 238 | 239 | false 240 | 241 | 242 | 243 | 244 | 245 | 246 | openmrs-repo-modules 247 | Modules 248 | http://mavenrepo.openmrs.org/nexus/content/repositories/modules/ 249 | 250 | 251 | openmrs-repo-snapshots 252 | OpenMRS Snapshots 253 | http://mavenrepo.openmrs.org/nexus/content/repositories/snapshots 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/runReport.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | ui.includeCss("reportingui", "runReport.css") 4 | 5 | ui.includeJavascript("uicommons", "angular.min.js") 6 | ui.includeJavascript("reportingui", "runReport.js") 7 | 8 | def interactiveClass = context.loadClass("org.openmrs.module.reporting.report.renderer.InteractiveReportRenderer") 9 | def isInteractive = { 10 | interactiveClass.isInstance(it.renderer) 11 | } 12 | %> 13 | 14 | 29 | 30 | ${ ui.includeFragment("appui", "messages", [ codes: [ 31 | "reportingui.reportRequest.Status.REQUESTED", "reportingui.reportRequest.Status.SCHEDULED", "reportingui.reportRequest.Status.PROCESSING", 32 | "reportingui.reportRequest.Status.FAILED", "reportingui.reportRequest.Status.COMPLETED", "reportingui.reportRequest.Status.SCHEDULE_COMPLETED", 33 | "reportingui.reportRequest.Status.SAVED", 34 | "reportingui.reportRequest.Priority.HIGHEST", "reportingui.reportRequest.Priority.HIGH", "reportingui.reportRequest.Priority.NORMAL", 35 | "reportingui.reportRequest.Priority.LOW", "reportingui.reportRequest.Priority.LOWEST"] 36 | ])} 37 | 38 |
39 | 40 |

${ ui.message(reportDefinition.name) }

41 |

${ ui.message(reportDefinition.description) }

42 | 43 |
44 |
45 | ${ ui.message("reportingui.runReport.queue.legend") } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 70 | 75 | 79 | 85 | 86 | 87 |
${ ui.message("reportingui.reportRequest.status") }${ ui.message("reportingui.reportRequest.parameters") }${ ui.message("reportingui.reportRequest.requested") }${ ui.message("reportingui.reportRequest.actions") }
58 | 59 | {{request.status | translate:'reportingui.reportRequest.Status.'}} 60 | 61 |
62 | ${ ui.message("reportingui.reportRequest.priority") }: {{request.priority | translate:'reportingui.ReportRequest.Priority.'}}
63 | ${ ui.message("reportingui.reportRequest.position") }: {{request.positionInQueue}} 64 |
65 | 66 |
67 | {{request.evaluateCompleteDatetime}} 68 |
69 |
71 | 72 | {{ param.value }}
73 |
74 |
76 | {{request.requestedBy}}
77 | {{request.requestDate}} 78 |
80 | 81 | 82 | ${ ui.message("emr.cancel") } 83 | 84 |
88 |
89 | 90 |
91 | ${ ui.message("reportingui.runReport.completed.legend") } 92 | 93 | 94 | ${ ui.message("uicommons.loading.placeholder") } 95 | 96 | 97 | 98 | 99 | ${ ui.message("emr.none") } 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 117 | 122 | 126 | 145 | 146 | 147 |
${ ui.message("reportingui.reportRequest.status") }${ ui.message("reportingui.reportRequest.parameters") }${ ui.message("reportingui.reportRequest.requested") }${ ui.message("reportingui.reportRequest.actions") }
114 | {{request.status | translate:'reportingui.reportRequest.Status.'}}
115 | {{request.evaluateCompleteDatetime}} 116 |
118 | 119 | {{ param.value }}
120 |
121 |
123 | {{request.requestedBy}}
124 | {{request.requestDate}} 125 |
127 | 128 | 129 | 130 | 131 | ${ ui.message("reportingui.reportHistory.open") } 132 | 133 | 134 | 135 | ${ ui.message("uicommons.downloadButtonLabel") } 136 | 137 | 138 |
139 | 140 | 141 | ${ ui.message("reportingui.reportRequest.save.action") } 142 | 143 |
144 |
148 |
149 |
150 | 151 |
152 |
153 | ${ ui.message("reportingui.runReport.run.legend") } 154 | 155 |
156 | <% reportDefinition.parameters.each { %> 157 |

158 | <% if (it.collectionType) { %> 159 | Parameters of type = collection are not yet implemented 160 | <% } else if (it?.widgetConfiguration?.uiframeworkFragmentProvider) { %> 161 | ${ ui.includeFragment(it.widgetConfiguration.uiframeworkFragmentProvider, it.widgetConfiguration.uiframeworkFragment, [ 162 | formFieldName: "parameterValues[" + it.name + "]", 163 | label: it.labelOrName 164 | ])} 165 | <% } else if (it.type == java.util.Date) { %> 166 | ${ ui.includeFragment("uicommons", "field/datetimepicker", [ 167 | formFieldName: "parameterValues[" + it.name + "]", 168 | label: it.labelOrName, 169 | useTime: false, 170 | defaultDate: it.defaultValue 171 | ])} 172 | <% } else { %> 173 | Unknown parameter type: ${ it.type } 174 | <% } %> 175 |

176 | <% } %> 177 | <% 178 | def renderingOptions = renderingModes.findAll { 179 | !isInteractive(it) 180 | } 181 | .collect { 182 | [ value: it.descriptor, label: ui.message(it.label) ] 183 | } 184 | %> 185 | <% if (renderingOptions.size() == 1) { %> 186 |

187 | 190 | 191 | ${ renderingOptions[0].label } 192 |

193 | <% } else { %> 194 | ${ ui.includeFragment("uicommons", "field/dropDown", [ 195 | formFieldName: "renderingMode", 196 | label: ui.message("reportingui.reportRequest.outputFormat"), 197 | hideEmptyLabel: true, 198 | options: renderingOptions 199 | ]) } 200 | <% } %> 201 | 202 | 206 |
207 |
208 |
209 | 210 |
-------------------------------------------------------------------------------- /omod/src/main/java/org/openmrs/module/reportingui/fragment/controller/ReportStatusFragmentController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The contents of this file are subject to the OpenMRS Public License 3 | * Version 1.0 (the "License"); you may not use this file except in 4 | * compliance with the License. You may obtain a copy of the License at 5 | * http://license.openmrs.org 6 | * 7 | * Software distributed under the License is distributed on an "AS IS" 8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing rights and limitations 10 | * under the License. 11 | * 12 | * Copyright (C) OpenMRS, LLC. All Rights Reserved. 13 | */ 14 | 15 | package org.openmrs.module.reportingui.fragment.controller; 16 | 17 | import org.openmrs.module.reporting.evaluation.parameter.Mapped; 18 | import org.openmrs.module.reporting.evaluation.parameter.Parameterizable; 19 | import org.openmrs.module.reporting.report.Report; 20 | import org.openmrs.module.reporting.report.ReportRequest; 21 | import org.openmrs.module.reporting.report.definition.ReportDefinition; 22 | import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; 23 | import org.openmrs.module.reporting.report.renderer.InteractiveReportRenderer; 24 | import org.openmrs.module.reporting.report.service.ReportService; 25 | import org.openmrs.ui.framework.SimpleObject; 26 | import org.openmrs.ui.framework.UiUtils; 27 | import org.openmrs.ui.framework.annotation.SpringBean; 28 | import org.openmrs.ui.framework.fragment.action.FailureResult; 29 | import org.openmrs.ui.framework.fragment.action.FragmentActionResult; 30 | import org.openmrs.ui.framework.fragment.action.SuccessResult; 31 | import org.openmrs.util.OpenmrsUtil; 32 | import org.springframework.web.bind.annotation.RequestParam; 33 | 34 | import java.util.ArrayList; 35 | import java.util.Collections; 36 | import java.util.Comparator; 37 | import java.util.HashMap; 38 | import java.util.List; 39 | import java.util.Map; 40 | 41 | /** 42 | * 43 | */ 44 | public class ReportStatusFragmentController { 45 | 46 | public List getQueuedRequests(@RequestParam(value = "reportDefinition", required = false) String reportDefinitionUuid, 47 | @SpringBean ReportDefinitionService reportDefinitionService, 48 | @SpringBean ReportService reportService, 49 | UiUtils ui) { 50 | 51 | ReportDefinition reportDefinition = null; 52 | if (reportDefinitionUuid != null) { 53 | reportDefinition = reportDefinitionService.getDefinitionByUuid(reportDefinitionUuid); 54 | } 55 | List requests = reportService.getReportRequests(reportDefinition, null, null, ReportRequest.Status.REQUESTED, ReportRequest.Status.PROCESSING); 56 | 57 | final Map queueStatus = new HashMap(); 58 | for (ReportRequest request : requests) { 59 | if (request.getStatus().equals(ReportRequest.Status.REQUESTED)) { 60 | Integer positionInQueue = reportService.getPositionInQueue(request); 61 | 62 | // due to an underlying bug in the reporting module, the status on a REQUESTED request isn't updated when it starts PROCESSING 63 | ReportRequest.Status statusOverride = null; 64 | List reportLog = reportService.loadReportLog(request); 65 | if (reportLog != null) { 66 | for (String s : reportLog) { 67 | if (s.indexOf("Starting to process report") != -1) { 68 | statusOverride = ReportRequest.Status.PROCESSING; 69 | } 70 | } 71 | } 72 | 73 | queueStatus.put(request, new QueueStatus(positionInQueue, statusOverride)); 74 | } 75 | } 76 | 77 | Collections.sort(requests, new Comparator() { 78 | @Override 79 | public int compare(ReportRequest left, ReportRequest right) { 80 | return OpenmrsUtil.compareWithNullAsGreatest(queueStatus.get(left), queueStatus.get(right)); 81 | } 82 | }); 83 | 84 | return simplify(ui, requests, queueStatus, null); 85 | 86 | } 87 | 88 | public List getCompletedRequests(@RequestParam(value = "reportDefinition", required = false) String reportDefinitionUuid, 89 | @SpringBean ReportDefinitionService reportDefinitionService, 90 | @SpringBean ReportService reportService, 91 | UiUtils ui) { 92 | ReportDefinition reportDefinition = null; 93 | if (reportDefinitionUuid != null) { 94 | reportDefinition = reportDefinitionService.getDefinitionByUuid(reportDefinitionUuid); 95 | } 96 | 97 | List requests = reportService.getReportRequests(reportDefinition, null, null, 10, ReportRequest.Status.FAILED, ReportRequest.Status.COMPLETED, ReportRequest.Status.SAVED); 98 | 99 | Map errorMessages = new HashMap(); 100 | for (ReportRequest request : requests) { 101 | if (ReportRequest.Status.FAILED.equals(request.getStatus())) { 102 | errorMessages.put(request, reportService.loadReportError(request)); 103 | } 104 | } 105 | 106 | Collections.sort(requests, new Comparator() { 107 | @Override 108 | public int compare(ReportRequest left, ReportRequest right) { 109 | return OpenmrsUtil.compareWithNullAsGreatest(right.getEvaluateStartDatetime(), left.getEvaluateStartDatetime()); 110 | } 111 | }); 112 | 113 | return simplify(ui, requests, null, errorMessages); 114 | } 115 | 116 | public FragmentActionResult cancelRequest(@RequestParam("reportRequest") String reportRequestUuid, 117 | @SpringBean ReportService reportService, 118 | UiUtils ui) { 119 | ReportRequest request = reportService.getReportRequestByUuid(reportRequestUuid); 120 | if (ReportRequest.Status.REQUESTED.equals(request.getStatus())) { 121 | reportService.purgeReportRequest(request); 122 | return new SuccessResult(ui.message("reportingui.reportRequest.cancel.successMessage")); 123 | } 124 | return new FailureResult(ui.message("reportingui.reportRequest.cancel.errorMessage")); 125 | } 126 | 127 | public FragmentActionResult saveRequest(@RequestParam("reportRequest") String reportRequestUuid, 128 | @RequestParam(value="description", required=false) String description, 129 | @SpringBean ReportService reportService, 130 | UiUtils ui) { 131 | ReportRequest request = reportService.getReportRequestByUuid(reportRequestUuid); 132 | if (ReportRequest.Status.COMPLETED.equals(request.getStatus())) { 133 | Report report = reportService.loadReport(request); 134 | reportService.saveReport(report, description); 135 | return new SuccessResult(ui.message("reportingui.reportRequest.save.successMessage")); 136 | } 137 | return new FailureResult(ui.message("reportingui.reportRequest.save.errorMessage")); 138 | } 139 | 140 | 141 | public List simplify(UiUtils ui, List requests, Map queueStatus, Map errorMessages) { 142 | List ret = new ArrayList(); 143 | for (ReportRequest request : requests) { 144 | ret.add(simplify(ui, request, queueStatus == null ? null : queueStatus.get(request), errorMessages == null ? null : errorMessages.get(request))); 145 | } 146 | 147 | return ret; 148 | } 149 | 150 | private SimpleObject simplify(UiUtils ui, ReportRequest request, QueueStatus queueStatus, String error) { 151 | SimpleObject simple = SimpleObject.fromObject(request, ui, 152 | "uuid", "renderingMode.label", "priority", "schedule", 153 | "requestedBy", "requestDate", "status", 154 | "evaluateStartDatetime", "evaluateCompleteDatetime", "renderCompleteDatetime"); 155 | 156 | if (queueStatus != null) { 157 | simple.put("positionInQueue", queueStatus.getPositionInQueue()); 158 | if (queueStatus.getStatusOverride() != null) { 159 | simple.put("status", queueStatus.getStatusOverride()); 160 | } 161 | } 162 | ((SimpleObject) simple.get("renderingMode")).put("interactive", request.getRenderingMode().getRenderer() instanceof InteractiveReportRenderer); 163 | simple.put("reportDefinition", simplify(ui, request.getReportDefinition())); 164 | simple.put("baseCohort", simplify(ui, request.getBaseCohort())); 165 | simple.put("errorMessage", error); 166 | 167 | return simple; 168 | } 169 | 170 | public class QueueStatus implements Comparable { 171 | private Integer positionInQueue; 172 | private ReportRequest.Status statusOverride; 173 | 174 | public QueueStatus(Integer positionInQueue, ReportRequest.Status statusOverride) { 175 | this.positionInQueue = positionInQueue; 176 | this.statusOverride = statusOverride; 177 | } 178 | 179 | public Integer getPositionInQueue() { 180 | return positionInQueue; 181 | } 182 | 183 | public ReportRequest.Status getStatusOverride() { 184 | return statusOverride; 185 | } 186 | 187 | @Override 188 | public int compareTo(QueueStatus other) { 189 | if (ReportRequest.Status.PROCESSING.equals(statusOverride)) { 190 | return -1; 191 | } else if (ReportRequest.Status.PROCESSING.equals(other.statusOverride)) { 192 | return 1; 193 | } 194 | return OpenmrsUtil.compareWithNullAsGreatest(positionInQueue, other.positionInQueue); 195 | } 196 | } 197 | 198 | public class ParamValue { 199 | private String name; 200 | private String value; 201 | 202 | public ParamValue(String name, String value) { 203 | this.name = name; 204 | this.value = value; 205 | } 206 | 207 | public String getName() { 208 | return name; 209 | } 210 | 211 | public String getValue() { 212 | return value; 213 | } 214 | } 215 | 216 | private SimpleObject simplify(UiUtils ui, Mapped mapped) { 217 | if (mapped == null) { 218 | return null; 219 | } 220 | 221 | List parameterMappings = new ArrayList(); 222 | for (Map.Entry entry : mapped.getParameterMappings().entrySet()) { 223 | parameterMappings.add(new ParamValue(entry.getKey(), ui.format(entry.getValue()))); 224 | } 225 | 226 | SimpleObject simple = new SimpleObject(); 227 | simple.put("mappings", parameterMappings); 228 | if (mapped.getParameterizable() != null) { 229 | simple.put("name", mapped.getParameterizable().getName()); 230 | simple.put("description", mapped.getParameterizable().getDescription()); 231 | } 232 | return simple; 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /omod/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | org.openmrs.module 7 | reportingui 8 | 1.0-SNAPSHOT 9 | 10 | 11 | reportingui-omod 12 | jar 13 | Reporting UI Module OMOD 14 | OMOD project for ReportingUI 15 | 16 | 17 | 18 | ${basedir}/.rubygems 19 | ${basedir}/.rubygems 20 | 21 | 22 | 23 | 24 | ${project.parent.groupId} 25 | ${project.parent.artifactId}-api 26 | ${project.parent.version} 27 | 28 | 29 | 30 | rubygems 31 | compass 32 | 0.11.7 33 | gem 34 | provided 35 | 36 | 37 | 38 | org.openmrs.web 39 | openmrs-web 40 | ${openmrsCoreVersion} 41 | jar 42 | provided 43 | 44 | 45 | org.openmrs.web 46 | openmrs-web 47 | ${openmrsCoreVersion} 48 | test-jar 49 | test 50 | 51 | 52 | 53 | org.openmrs.module 54 | reportingrest-omod 55 | ${reportingrestModuleVersion} 56 | provided 57 | 58 | 59 | 60 | org.openmrs.module 61 | webservices.rest-omod-common 62 | ${webservicesrestModuleVersion} 63 | provided 64 | 65 | 66 | 67 | 68 | ${project.parent.artifactId}-${project.parent.version} 69 | 70 | 71 | src/main/resources 72 | false 73 | 74 | **/*.xml 75 | **/*.properties 76 | **/*.json 77 | 78 | 79 | 80 | src/main/resources 81 | true 82 | 83 | **/*.xml 84 | **/*.properties 85 | **/*.json 86 | 87 | 88 | 89 | src/main/webapp 90 | false 91 | web/module 92 | 93 | 94 | 95 | 96 | 97 | src/main/resources 98 | false 99 | 100 | **/*.xml 101 | **/*.properties 102 | **/*.json 103 | 104 | 105 | 106 | src/main/resources 107 | true 108 | 109 | **/*.xml 110 | **/*.properties 111 | **/*.json 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | maven-resources-plugin 120 | 121 | true 122 | 123 | 124 | 126 | 127 | org.eclipse.m2e 128 | lifecycle-mapping 129 | 1.0.0 130 | 131 | 132 | 133 | 134 | 135 | org.openmrs.maven.plugins 136 | maven-openmrs-plugin 137 | [1.0.1,) 138 | 139 | initialize-module 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-dependency-plugin 150 | [2.4,) 151 | 152 | unpack-dependencies 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | org.openmrs.maven.plugins 169 | maven-openmrs-plugin 170 | true 171 | 172 | 173 | init 174 | initialize 175 | 176 | initialize-module 177 | 178 | 179 | 180 | pack 181 | package 182 | 183 | package-module 184 | 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-dependency-plugin 191 | 192 | 193 | Expand moduleApplicationContext and messages 194 | 195 | unpack-dependencies 196 | 197 | generate-resources 198 | 199 | ${project.parent.groupId} 200 | ${project.parent.artifactId}-api 201 | true 202 | **/* 203 | ${project.build.directory}/classes 204 | 205 | 206 | 207 | 208 | 209 | org.apache.maven.plugins 210 | maven-dependency-plugin 211 | 212 | 213 | Fetch SASS sources from uicommons 214 | 215 | unpack 216 | 217 | generate-resources 218 | 219 | 220 | 221 | org.openmrs.module 222 | uicommons-scss 223 | ${uicommonsModuleVersion} 224 | zip 225 | true 226 | src/main/compass/sass-external 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | de.saumya.mojo 235 | gem-maven-plugin 236 | true 237 | 238 | 239 | 240 | exec 241 | 242 | generate-resources 243 | 244 | 245 | 246 | 1.7.10 247 | ${gem.home}/bin/compass compile ${basedir}/src/main/compass 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | watch-sass 256 | 257 | 258 | 259 | de.saumya.mojo 260 | gem-maven-plugin 261 | true 262 | 263 | 264 | 265 | exec 266 | 267 | generate-resources 268 | 269 | 270 | 271 | 1.7.10 272 | ${gem.home}/bin/compass watch ${basedir}/src/main/compass 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /omod/src/main/webapp/pages/adHocAnalysis.gsp: -------------------------------------------------------------------------------- 1 | <% 2 | ui.decorateWith("appui", "standardEmrPage") 3 | ui.includeJavascript("uicommons", "moment.min.js") 4 | ui.includeJavascript("uicommons", "angular.js") 5 | ui.includeJavascript("uicommons", "angular-resource.min.js") 6 | ui.includeJavascript("uicommons", "angular-sanitize.min.js") 7 | ui.includeJavascript("uicommons", "angular-app.js") 8 | ui.includeJavascript("uicommons", "filters/display.js") 9 | ui.includeJavascript("uicommons", "services/encounterTypeService.js") 10 | ui.includeJavascript("uicommons", "services/locationService.js") 11 | 12 | ui.includeJavascript("reportingui", "app.js") 13 | ui.includeJavascript("reportingui", "adHocAnalysis.js") 14 | ui.includeJavascript("uicommons", "angular-ui/ui-bootstrap-tpls-0.6.0.min.js") 15 | ui.includeCss("reportingui", "adHocReport.css") 16 | 17 | ui.includeJavascript("reportingui", "directives/encounterTypeWidget.js") 18 | ui.includeJavascript("reportingui", "directives/locationWidget.js") 19 | 20 | def jsString = { 21 | it ? """ "${ ui.escapeJs(it) }" """ : "null" 22 | } 23 | %> 24 | 25 | <%= ui.includeFragment("appui", "messages", [ codes: [ 26 | "reportingui.adHocReport.timeframe.startDateLabel", 27 | "reportingui.adHocReport.timeframe.endDateLabel", 28 | "reportingui.parameter.type.java.util.Date", 29 | "reportingui.parameter.type.org.openmrs.VisitType", 30 | "reportingui.parameter.type.org.openmrs.Location", 31 | "reportingui.parameter.type.org.openmrs.EncounterType", 32 | context.encounterService.allEncounterTypes.collect { "ui.i18n.EncounterType.name." + it.uuid } 33 | ].flatten() 34 | ]) %> 35 | 36 | 54 | 55 |
56 | 57 |
58 | 59 | ${ ui.message("reportingui.adHocReport.parameters.label") }: 60 | {{ dataExport.parameters.length }} 61 | 62 | 63 | 64 | ${ ui.message("reportingui.adHocReport.searches") }: 65 | {{ dataExport.rowFilters.length }} 66 | 67 | 68 | 69 | ${ ui.message("reportingui.adHocReport.columns") }: 70 | {{ dataExport.columns.length }} 71 | 72 | 73 | 74 | ${ ui.message("reportingui.adHocReport.preview") } 75 | 76 | 77 | 78 | ${ ui.message("reportingui.adHocReport.description.label") } 79 | 80 |
81 | 82 |
83 | 84 |
85 |

${ ui.message("reportingui.adHocReport.parameters.label") }

86 |
87 |
    88 |
  • 89 | {{ \$index + 1 }}. 90 | {{ parameter.label | omrs.display }}: 91 | {{ parameter.collectionType }} of 92 | {{ parameter.type | omrs.display:'reportingui.parameter.type.'}} 93 |
  • 94 |
95 |
96 | 99 |
100 | 101 |
102 |

${ ui.message("reportingui.adHocReport.searchCriteria")}

103 | ${ ui.message("reportingui.adHocReport.searchCriteria.description") } 104 | 105 |
106 |
    107 |
    108 | 109 |
    110 |
  • 111 | {{ criteria.name }} 112 | 113 |
  • 114 |
115 | 116 |
    117 |
    118 | 119 | ${ ui.message("reportingui.adHocReport.searchCriteria.combination") } 120 | ${ ui.message("reportingui.adHocReport.searchCriteria.customRowFilterCombination") } 121 | 122 | 123 | 124 | ${ ui.message("reportingui.adHocReport.searchCriteria.combination.edit") }: 125 | 126 | 127 | 128 | 129 |
    130 |
  • 131 | 132 | 133 | {{ rowQuery.name }} 134 | 135 | 136 | 137 | 138 | 139 | {{ param.name }}: {{ rowQuery.parameterValues[param.name] | omrs.display }} 140 | 141 |
  • 142 |
143 |
144 | 145 | 149 |
150 | 151 |
152 |

${ ui.message("reportingui.adHocReport.columns") }

153 | ${ ui.message("reportingui.adHocReport.columns.description") } 154 | 155 |
156 |
    157 |
    158 | 159 |
    160 |
  • 161 | {{ column.name }} 162 | {{ column.description | insertParameterNames:column.parameters:dataExport.parameters }} 163 |
  • 164 |
165 | 166 |
    167 |
    {{ dataExport.columns.length }} selected columns
    168 |
  • 169 | 172 | {{ col.name }} 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | {{ param.name }}: {{ col.parameterValues[param.name] | omrs.display }} 181 | 182 |
  • 183 |
184 |
185 | 186 | 190 |
191 | 192 |
193 |

${ ui.message("reportingui.adHocReport.preview") }

194 | 195 |
196 | 197 |
198 | 199 | 200 | 201 |
202 |
203 | 204 | 205 |
206 | 207 | 208 | 209 |
210 |
211 | 212 | 213 |
214 |
${ ui.message("reportingui.adHocReport.noResults") }
215 |
216 |
217 | ${ ui.message("reportingui.adHocReport.preview.description") } 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 |
{{ col.label }}
{{ \$index + 1 }}{{ col }}
232 |
233 | 237 |
238 |
239 | 240 |
241 |

${ ui.message("reportingui.adHocReport.description.label") }

242 |
243 |

244 | 245 | 246 |

247 |

248 | 249 | 250 |

251 |
252 | 275 |
276 |
277 | -------------------------------------------------------------------------------- /omod/src/main/webapp/resources/scripts/adHocAnalysis.js: -------------------------------------------------------------------------------- 1 | window.adHocAnalysis = { 2 | queryPromises: {}, 3 | queryResults: {}, 4 | 5 | fetchData: function($http, target, type, afterSuccess) { 6 | var promise = $http.get(emr.fragmentActionLink('reportingui', 'definitionLibrary', 'getDefinitions', { type: type })). 7 | success(function(data, status, headers, config) { 8 | _.each(data, function(item) { 9 | item.label = item.name + ' (' + item.description + ')'; 10 | }); 11 | target.queryResults[type] = data; 12 | if (afterSuccess) { 13 | afterSuccess.call(); 14 | } 15 | }); 16 | target.queryPromises[type] = promise; 17 | } 18 | } 19 | 20 | angular.module('reportingui'). 21 | 22 | filter('insertParameterNames', ['$filter', function($filter) { 23 | function escapeRegExp(string) { 24 | return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); 25 | } 26 | 27 | function replaceAll(find, replace, str) { 28 | return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); 29 | } 30 | 31 | return function(input, localParams, globalParams) { 32 | input = _.escape(input); // input is a raw description, which might include something like "<= $startDate" 33 | // first, global parameters 34 | _.each(globalParams, function(p) { 35 | input = replaceAll("{{" + p.name + "}}", "" + $filter('omrs.display')(p.label) + "", input); 36 | }); 37 | // next, parameters specific to this filter/column 38 | _.each(localParams, function(p) { 39 | input = replaceAll("{{" + p.name + "}}", "" + $filter('omrs.display')(p.label) + "", input); 40 | }) 41 | return input; 42 | } 43 | }]). 44 | 45 | directive('definitionsearch', function($compile) { 46 | // expect { type: ..., key: ..., name: ..., description: ..., parameters: [ ... ] } 47 | 48 | return function(scope, element, attrs) { 49 | var allowedParameters = _.pluck(scope.parameters, 'name'); 50 | var onSelectAction = scope[attrs['action']]; 51 | element.autocomplete({ 52 | source: [ 'Loading...' ], 53 | select: function(event, ui) { 54 | scope.$apply(function() { 55 | onSelectAction(ui.item); 56 | }); 57 | element.val(''); 58 | return false; 59 | }, 60 | response: function(event, ui) { 61 | var i = ui.content.length - 1; 62 | while (i >= 0) { 63 | var paramNames = _.pluck(ui.content[i].parameters, 'name'); 64 | var notAllowed = _.without(paramNames, allowedParameters); 65 | if (notAllowed.length > 0) { 66 | ui.content.splice(i, 1); 67 | } 68 | --i; 69 | } 70 | }, 71 | change: function(event, ui) { 72 | element.val(''); 73 | return false; 74 | } 75 | }); 76 | var definitionType = attrs['definitionType']; 77 | window.adHocAnalysis.queryPromises[definitionType].success(function() { 78 | element.autocomplete( "option", "source", window.adHocAnalysis.queryResults[definitionType] ); 79 | }); 80 | }; 81 | }). 82 | 83 | controller('AdHocAnalysisController', ['$scope', '$http', '$timeout', '$filter', function($scope, $http, $timeout, $filter) { 84 | 85 | // ----- private helper functions ---------- 86 | 87 | function swap(array, idx1, idx2) { 88 | if (idx1 < 0 || idx2 < 0 || idx1 >= array.length || idx2 >= array.length) { 89 | return; 90 | } 91 | var temp = array[idx1]; 92 | array[idx1] = array[idx2]; 93 | array[idx2] = temp; 94 | } 95 | 96 | function filterAvailable(allDefinitions, currentDefinitions) { 97 | return _.filter(allDefinitions, function(candidate) { 98 | // if this takes run-time parameters, the user is allowed to add it multiple times 99 | if ($scope.requiresExtraParameters(candidate)) { 100 | return true; 101 | } 102 | // otherwise, skip anything we already have selected 103 | return ! _.findWhere(currentDefinitions, { key: candidate.key }); 104 | }); 105 | } 106 | 107 | function postJSON(url, dataObject) { 108 | return $http({ 109 | method: 'POST', 110 | url: url, 111 | data: dataObject 112 | }); 113 | } 114 | 115 | function post(url, dataObject) { 116 | return $http({ 117 | method: 'POST', 118 | url: url, 119 | data: $.param(dataObject), 120 | headers: {'Content-Type': 'application/x-www-form-urlencoded'} 121 | }); 122 | } 123 | 124 | function stripTimeFromParameters(dataExport) { 125 | var ret = angular.copy(dataExport); 126 | _.each(ret.parameters, function(it) { 127 | if (it.value instanceof Date) { 128 | it.value = it.value.toISOString().slice(0,10); 129 | } 130 | }); 131 | return ret; 132 | } 133 | 134 | function setDirty() { 135 | $scope.dirty = true; 136 | } 137 | 138 | function copyDefinitionWithParameterValues(definition, paramValues) { 139 | return $.extend( 140 | _.pick(definition, 'key', 'type', 'name', 'description', 'label', 'parameters'), 141 | { parameterValues: paramValues } 142 | ); 143 | } 144 | 145 | // ----- Model ---------- 146 | 147 | $scope.dataExport = window.adHocDataExport; // initialized in the gsp on page load 148 | 149 | var initialSetup = $scope.dataExport.initialSetup; 150 | delete $scope.dataExport.initialSetup; 151 | 152 | $scope.dirty = !window.adHocDataExport.uuid; 153 | 154 | $scope.editingCombination = false; 155 | 156 | $scope.dataExport.customRowFilterCombination = ''; 157 | 158 | $scope.dataExport.parameters = []; 159 | 160 | $scope.dataExport.rowFilters = []; 161 | 162 | $scope.dataExport.columns = []; 163 | 164 | if (initialSetup) { 165 | $scope.dataExport.uuid = initialSetup.uuid; 166 | $scope.dataExport.name = initialSetup.name; 167 | $scope.dataExport.description = initialSetup.description; 168 | $scope.dataExport.parameters = initialSetup.parameters; 169 | _.each($scope.dataExport.parameters, function(item) { 170 | if (item.type == "java.util.Date") { 171 | item.value = moment().startOf('day').toDate(); 172 | } 173 | }); 174 | $scope.dataExport.customRowFilterCombination = initialSetup.customRowFilterCombination; 175 | } 176 | else { 177 | $scope.dataExport.parameters = [ 178 | { 179 | name: "startDate", 180 | label: "reportingui.adHocReport.timeframe.startDateLabel", 181 | type: "java.util.Date", 182 | collectionType: null, 183 | value: moment().startOf('day').toDate() 184 | }, 185 | { 186 | name: "endDate", 187 | label: "reportingui.adHocReport.timeframe.endDateLabel", 188 | type: "java.util.Date", 189 | collectionType: null, 190 | value: moment().startOf('day').toDate() 191 | } 192 | ]; 193 | } 194 | 195 | $scope.initialRowSetup = function() { 196 | if (initialSetup) { 197 | _.each(initialSetup.rowFilters, function(item) { 198 | var rowFilter = _.findWhere(window.adHocAnalysis.queryResults['org.openmrs.module.reporting.cohort.definition.CohortDefinition'], { key: item.key }); 199 | if (rowFilter) { 200 | $scope.addDefinitionWithParameters(copyDefinitionWithParameterValues(rowFilter, item.parameterValues), 'rowFilters'); 201 | $scope.dirty = false; 202 | } else { 203 | console.log("Could not find row: " + item.key); 204 | } 205 | }); 206 | } 207 | } 208 | 209 | $scope.initialColumnSetup = function() { 210 | if (initialSetup) { 211 | _.each(initialSetup.columns, function(item) { 212 | var column = _.findWhere(window.adHocAnalysis.queryResults['org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition'], { key: item.key }); 213 | if (column) { 214 | $scope.addDefinitionWithParameters(copyDefinitionWithParameterValues(column, item.parameterValues), 'columns'); 215 | $scope.dirty = false; 216 | } else { 217 | console.log("Could not find column: " + item.key); 218 | } 219 | }); 220 | } 221 | } 222 | 223 | window.adHocAnalysis.fetchData($http, window.adHocAnalysis, 'org.openmrs.module.reporting.cohort.definition.CohortDefinition', $scope.initialRowSetup); 224 | window.adHocAnalysis.fetchData($http, window.adHocAnalysis, 'org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition', $scope.initialColumnSetup); 225 | 226 | $scope.dataExport.valid = function() { 227 | return $scope.dataExport.name && $scope.dataExport.columns.length > 0; 228 | } 229 | 230 | 231 | $scope.ModalCtrl = function ($scope, $modal, $log) { 232 | 233 | $scope.definition = null; 234 | $scope.listToAddTo = null; 235 | $scope.filters = []; 236 | 237 | $scope.$on("OPEN_MODAL", function(event, definition, listToAddTo) { 238 | $scope.definition = definition; 239 | $scope.listToAddTo = listToAddTo; 240 | for(i = 0; i < definition.parameters.length; i++) { 241 | var paramName = definition.parameters[i].name; 242 | if(!_.contains(_.pluck($scope.$parent.dataExport.parameters, 'name'), paramName)) { 243 | $scope.filters.push(paramName); 244 | } 245 | } 246 | $scope.open(); 247 | }); 248 | 249 | $scope.open = function () { 250 | var modalInstance = $modal.open({ 251 | templateUrl: 'adHocAnalysisParameterPopup.page', 252 | controller: $scope.ModalInstanceCtrl, 253 | resolve: { 254 | definition: function () { 255 | return $scope.definition; 256 | }, 257 | filters: function () { 258 | return $scope.filters; 259 | } 260 | } 261 | }); 262 | 263 | modalInstance.result.then(function(paramValues) { 264 | var withParameters = copyDefinitionWithParameterValues($scope.definition, paramValues); 265 | $scope.$parent.addDefinitionWithParameters(withParameters, $scope.listToAddTo); 266 | }, function () { 267 | $log.info('Modal dismissed at: ' + new Date()); 268 | }); 269 | }; 270 | }; 271 | 272 | $scope.ModalInstanceCtrl = function ($scope, $modalInstance, definition, filters) { 273 | $scope.definition = definition; 274 | $scope.filters = filters; 275 | $scope.paramValues = {}; 276 | $scope.dataExport = window.adHocDataExport; // we use this to access the global parameters 277 | 278 | $scope.paramFilter = function(param) { 279 | return _.contains(filters, param.name); 280 | } 281 | 282 | $scope.ok = function () { 283 | $modalInstance.close($scope.paramValues); 284 | }; 285 | 286 | $scope.cancel = function () { 287 | $modalInstance.dismiss('cancel'); 288 | }; 289 | }; 290 | 291 | $scope.addRow = function(definition) { 292 | if (jq.inArray(definition, $scope.dataExport.rowFilters) < 0) { 293 | if($scope.requiresExtraParameters(definition) && $scope.hasMissingParameters(definition)) { 294 | $scope.showModal(definition); 295 | } else { 296 | $scope.dataExport.rowFilters.push(definition); 297 | setDirty(); 298 | } 299 | } 300 | } 301 | 302 | $scope.addDefinitionWithParameters = function(definition, listToAddTo) { 303 | if(listToAddTo == 'columns') { 304 | $scope.dataExport.columns.push(definition); 305 | } 306 | else if(listToAddTo == 'rowFilters') { 307 | $scope.dataExport.rowFilters.push(definition); 308 | } 309 | setDirty(); 310 | } 311 | 312 | $scope.showModal = function(definition) { 313 | var listToAddTo = 'rowFilters' 314 | if($scope.currentView == 'columns') { 315 | listToAddTo = 'columns'; 316 | } 317 | // pass a copy of definition to the modal 318 | $scope.$broadcast("OPEN_MODAL", $.extend(true, {}, definition), listToAddTo); 319 | } 320 | 321 | $scope.removeRow = function(idx) { 322 | $scope.dataExport.rowFilters.splice(idx, 1); 323 | setDirty(); 324 | } 325 | 326 | $scope.addColumn = function(definition) { 327 | if(jq.inArray(definition, $scope.dataExport.columns) < 0) { 328 | if($scope.requiresExtraParameters(definition) && $scope.hasMissingParameters(definition)) { 329 | $scope.showModal(definition); 330 | } 331 | else { 332 | $scope.dataExport.columns.push(definition); 333 | setDirty(); 334 | } 335 | } 336 | } 337 | 338 | $scope.removeColumn = function(idx) { 339 | $scope.dataExport.columns.splice(idx, 1); 340 | setDirty(); 341 | } 342 | 343 | $scope.moveColumnUp = function(idx) { 344 | swap($scope.dataExport.columns, idx - 1, idx); 345 | setDirty(); 346 | } 347 | 348 | $scope.moveColumnDown = function(idx) { 349 | swap($scope.dataExport.columns, idx, idx + 1); 350 | setDirty(); 351 | } 352 | 353 | $scope.editCombination = function() { 354 | $('#custom-combination').val($scope.dataExport.customRowFilterCombination); 355 | $scope.editingCombination = true; 356 | } 357 | 358 | $scope.applyEditCombination = function() { 359 | $scope.dataExport.customRowFilterCombination = $('#custom-combination').val(); 360 | $scope.editingCombination = false; 361 | setDirty(); 362 | } 363 | 364 | $scope.cancelEditCombination = function() { 365 | $scope.editingCombination = false; 366 | } 367 | 368 | // ----- View and ViewModel ---------- 369 | 370 | $scope.currentView = 'parameters'; 371 | 372 | $scope.maxDay = moment().startOf('day').toDate(); 373 | 374 | $scope.results = null; 375 | 376 | $scope.focusFirstElement = function() { 377 | $timeout(function() { 378 | $('#' + $scope.currentView + ' .focus-first').focus(); 379 | }); 380 | } 381 | 382 | $scope.$watch('currentView', $scope.focusFirstElement); 383 | 384 | $scope.$watch('dataExport.parameters', function() { 385 | if ($scope.currentView == 'preview') { 386 | $scope.preview(); 387 | } 388 | }, true); 389 | 390 | $scope.openStartDatePicker = function() { 391 | $timeout(function() { 392 | $scope.isStartDatePickerOpen = true; 393 | }); 394 | }; 395 | 396 | $scope.openEndDatePicker = function() { 397 | $timeout(function() { 398 | $scope.isEndDatePickerOpen = true; 399 | }); 400 | }; 401 | 402 | // TODO remove this 403 | $scope.getFormattedStartDate = function() { 404 | if($scope.dataExport.parameters[0].value == null) { return; } 405 | return moment($scope.dataExport.parameters[0].value).format("DD MMM YYYY"); 406 | } 407 | 408 | // TODO remove this 409 | $scope.getFormattedEndDate = function() { 410 | if($scope.dataExport.parameters[1].value == null) { return; } 411 | return moment($scope.dataExport.parameters[1].value).format("DD MMM YYYY"); 412 | } 413 | 414 | $scope.availableSearches = function() { 415 | var allPossible = window.adHocAnalysis.queryResults['org.openmrs.module.reporting.cohort.definition.CohortDefinition']; 416 | return filterAvailable(allPossible, $scope.dataExport.rowFilters); 417 | } 418 | 419 | $scope.getColumns = function() { 420 | var allPossible = window.adHocAnalysis.queryResults['org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition']; 421 | return filterAvailable(allPossible, $scope.dataExport.columns); 422 | } 423 | 424 | $scope.requiresExtraParameters = function(definition) { 425 | var allowedParameters = _.pluck($scope.dataExport.parameters, 'name'); 426 | var paramNames = _.pluck(definition.parameters, 'name'); 427 | var notAllowed = _.difference(paramNames, allowedParameters); 428 | return notAllowed.length != 0; 429 | } 430 | 431 | $scope.isParameterGloballySet = function(param) { 432 | var allowedParameters = _.pluck($scope.dataExport.parameters, 'name'); 433 | return _.contains(allowedParameters, param.name); 434 | } 435 | 436 | $scope.hasMissingParameters = function(definition) { 437 | for(i = 0; i < definition.parameters.length; i++) { 438 | if (!$scope.isParameterGloballySet(definition.parameters[i]) 439 | && (definition.parameterValues == null || definition.parameterValues[definition.parameters[i].name] == null)) { 440 | return true; 441 | } 442 | } 443 | return false; 444 | } 445 | 446 | $scope.changeStep = function(stepName) { 447 | var steps = [ 448 | 'parameters', 449 | 'searches', 450 | 'columns', 451 | 'preview', 452 | 'description' 453 | ]; 454 | var isAfterCurrentStep = false; 455 | 456 | $scope.currentView = stepName; 457 | 458 | if(stepName == 'preview') { 459 | $scope.preview(); 460 | } 461 | 462 | for(var i=0; i < steps.length; i++) { 463 | if(!isAfterCurrentStep) { 464 | if(steps[i] == stepName) { 465 | isAfterCurrentStep = true; 466 | $('span[data-step="' + steps[i] + '"]').addClass('current').removeClass('done'); 467 | } else { 468 | $('span[data-step="' + steps[i] + '"]').addClass('done').removeClass('current'); 469 | } 470 | } else { 471 | $('span[data-step="' + steps[i] + '"]').removeClass('done').removeClass('current'); 472 | } 473 | } 474 | } 475 | 476 | $scope.next = function() { 477 | if($scope.currentView == 'parameters') { 478 | $scope.currentView = 'searches'; 479 | $('span[data-step="parameters"]').addClass('done').removeClass('current'); 480 | $('span[data-step="searches"]').addClass('current'); 481 | } 482 | 483 | else if($scope.currentView == 'searches') { 484 | $scope.currentView = 'columns'; 485 | $('span[data-step="columns"]').addClass('current'); 486 | $('span[data-step="searches"]').addClass('done').removeClass('current'); 487 | } 488 | 489 | else if($scope.currentView == 'columns') { 490 | $scope.currentView = 'preview'; 491 | $('span[data-step="preview"]').addClass('current'); 492 | $('span[data-step="columns"]').addClass('done').removeClass('current'); 493 | $scope.preview(); 494 | } 495 | 496 | else if($scope.currentView == 'preview') { 497 | $scope.currentView = 'description'; 498 | $('span[data-step="description"]').addClass('current'); 499 | $('span[data-step="preview"]').addClass('done').removeClass('current'); 500 | } 501 | } 502 | 503 | $scope.back = function() { 504 | if($scope.currentView == 'searches') { 505 | $scope.currentView = 'parameters'; 506 | $('span[data-step="searches"]').removeClass('current'); 507 | $('span[data-step="parameters"]').addClass('current').removeClass('done'); 508 | } 509 | 510 | else if($scope.currentView == 'columns') { 511 | $scope.currentView = 'searches'; 512 | $('span[data-step="columns"]').removeClass('current'); 513 | $('span[data-step="searches"]').addClass('current').removeClass('done'); 514 | } 515 | 516 | else if($scope.currentView == 'preview') { 517 | $scope.currentView = 'columns'; 518 | $('span[data-step="preview"]').removeClass('current'); 519 | $('span[data-step="columns"]').addClass('current').removeClass('done'); 520 | } 521 | 522 | else if($scope.currentView == 'description') { 523 | $scope.currentView = 'preview'; 524 | $('span[data-step="description"]').removeClass('current'); 525 | $('span[data-step="preview"]').addClass('current').removeClass('done'); 526 | $scope.preview(); 527 | } 528 | } 529 | 530 | $scope.preview = function() { 531 | $scope.results = { loading: true }; 532 | 533 | postJSON('/' + OPENMRS_CONTEXT_PATH + '/ws/rest/v1/reportingrest/adhocquery?v=preview', stripTimeFromParameters($scope.dataExport)). 534 | success(function(data, status, headers, config) { 535 | $scope.results = data; 536 | }). 537 | error(function(data, status, headers, config) { 538 | emr.handleParsedError(data); 539 | }); 540 | } 541 | 542 | $scope.canSave = function() { 543 | return $scope.dataExport.valid(); 544 | } 545 | 546 | $scope.saveDataExport = function() { 547 | $scope.dirty = { saving: true }; 548 | post(emr.fragmentActionLink('reportingui', 'adHocAnalysis', 'saveDataExport'), 549 | { 550 | dataSet: angular.toJson($scope.dataExport) 551 | }). 552 | success(function(data, status, headers, config) { 553 | $scope.dataExport.uuid = data.uuid; 554 | $scope.dataExport.name = data.name; 555 | $scope.dataExport.description = data.description; 556 | $scope.dirty = false; 557 | }); 558 | } 559 | 560 | $scope.canRun = function() { 561 | return $scope.dataExport.uuid && !$scope.dirty; 562 | } 563 | 564 | $scope.runDataExport = function() { 565 | var data = { 566 | dataset: $scope.dataExport.uuid 567 | }; 568 | _.each($scope.parameters, function(item) { 569 | data["parameterValues[" + item.name + "]"] = item.value; 570 | }); 571 | location.href = emr.pageLink('reportingui', 'adHocRun', data); 572 | } 573 | }]); 574 | 575 | --------------------------------------------------------------------------------