From 6c418f4c753045f8ea3709b2c8c848ff4cbd7c37 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 14 May 2020 15:35:53 +1000 Subject: [PATCH 01/17] Basic implementation of #2018 - progress commit. --- grails-app/assets/javascripts/projects.js | 59 ++++++++++++ grails-app/views/project/_projectAdmin.gsp | 7 +- grails-app/views/project/_riskReporting.gsp | 47 ++++++++++ grails-app/views/project/index.gsp | 7 +- .../ProjectSummaryReportCommand.groovy | 22 +++-- test/js/spec/RisksReportViewModelSpec.js | 92 +++++++++++++++++++ .../ProjectSummaryReportCommandSpec.groovy | 64 +++++++++++++ 7 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 grails-app/views/project/_riskReporting.gsp create mode 100644 test/js/spec/RisksReportViewModelSpec.js create mode 100644 test/unit/au/org/ala/merit/ProjectSummaryReportCommandSpec.groovy diff --git a/grails-app/assets/javascripts/projects.js b/grails-app/assets/javascripts/projects.js index 7b8451ba8..b13235676 100644 --- a/grails-app/assets/javascripts/projects.js +++ b/grails-app/assets/javascripts/projects.js @@ -1076,6 +1076,16 @@ function ProjectPageViewModel(project, sites, activities, organisations, userRol ko.applyBindings(self.meriPlan, announcementsSection); } + var risksChangesReport = document.getElementById(config.riskChangesReportElementId); + if (risksChangesReport) { + var reportOptions = { + riskChangesReportHtmlUrl: config.riskChangesReportHtmlUrl, + riskChangesReportPdfUrl: config.riskChangesReportPdfUrl + }; + var risksReportViewModel = new RisksReportViewModel(project, reportOptions); + ko.applyBindings(risksReportViewModel, risksChangesReport); + } + }; self.initialiseMeriPlan = function() { @@ -1420,4 +1430,53 @@ function ProjectService(project, options) { var url = config.documentDeleteUrl+'/'+documentId; return $.post(url); }; +}; + +/** + * This is the view model for the section of the risks & threats tab that can select dates and generate reports. + * @param project The project to generate risks reports for. + * @param options + * { + * riskChangesReportHtmlUrl: + * riskChangesReportPdfUrl: + * } + */ +var RisksReportViewModel = function(project, options) { + var self = this; + + + var projectEndDate = moment(project.plannedEndDate); + var projectStartDate = moment(project.plannedStartDate); + + function truncateToProjectDates(date) { + if (date.isAfter(projectEndDate)) { + date = projectEndDate; + } + if (date.isBefore(projectStartDate)) { + date = projectStartDate; + } + return date; + } + var defaultToDate = truncateToProjectDates(moment()); + var defaultFromDate = truncateToProjectDates(moment().subtract(3, 'months')); + + self.fromDate = ko.observable().extend({simpleDate:false}); + self.fromDate.date(defaultFromDate.toDate()); + self.toDate = ko.observable().extend({simpleDate:false}); + self.toDate.date(defaultToDate.toDate()); + self.orientation = ko.observable('portrait'); + + function displayReport(url) { + var paramString= '?fromDate'+self.fromDate()+'&toStage='+self.toDate(); + paramString+='§ions=Project+risks+changes'; + paramString+='&orientation='+self.orientation(); + + window.open(url+paramString, 'risks-changes-report'); + } + self.generateRisksReportHTML = function() { + displayReport(options.riskChangesReportHtmlUrl); + }; + self.generateRisksReportPDF = function() { + displayReport(options.riskChangesReportPdfUrl); + }; }; \ No newline at end of file diff --git a/grails-app/views/project/_projectAdmin.gsp b/grails-app/views/project/_projectAdmin.gsp index 567f351d0..60d9d4465 100644 --- a/grails-app/views/project/_projectAdmin.gsp +++ b/grails-app/views/project/_projectAdmin.gsp @@ -65,9 +65,10 @@
-
- -
+ +
+ +
diff --git a/grails-app/views/project/_riskReporting.gsp b/grails-app/views/project/_riskReporting.gsp new file mode 100644 index 000000000..85576a58e --- /dev/null +++ b/grails-app/views/project/_riskReporting.gsp @@ -0,0 +1,47 @@ + +
+

Changes to risks and threats

+ +

Select the date range over which to view the changes to the project risks and threats then press "Generate Report (PDF)" button

+
+ +
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+ +
+
+
+ +
+ + +
+ +
+ \ No newline at end of file diff --git a/grails-app/views/project/index.gsp b/grails-app/views/project/index.gsp index f3f254698..4cc0c4f02 100644 --- a/grails-app/views/project/index.gsp +++ b/grails-app/views/project/index.gsp @@ -84,6 +84,8 @@ leafletIconPath:"${assetPath(src:'leaflet-0.7.7/images')}", approvedMeriPlanHistoryUrl:"${createLink(action:"approvedMeriPlanHistory", id:project.projectId)}", viewHistoricalMeriPlanUrl:"${createLink(action:"viewMeriPlan", id:project.projectId)}", + riskChangesReportHtmlUrl:"${createLink(controller:'project', action:'projectReport', id:project.projectId)}", + riskChangesReportPdfUrl:"${createLink(controller:'project', action:'projectReportPDF', id:project.projectId)}", returnTo: "${createLink(controller: 'project', action: 'index', id: project.projectId)}" }, @@ -252,7 +254,10 @@ documentDeleteUrl: fcConfig.documentDeleteUrl, meriStorageKey:PROJECT_DETAILS_KEY, activityBasedReporting: ${Boolean.valueOf(projectContent.admin.config.activityBasedReporting)}, - minimumProjectEndDate: ${projectContent.admin.minimumProjectEndDate?'"'+projectContent.admin.minimumProjectEndDate+'"':'null'} + minimumProjectEndDate: ${projectContent.admin.minimumProjectEndDate?'"'+projectContent.admin.minimumProjectEndDate+'"':'null'}, + riskChangesReportElementId: 'risk-changes-report', + riskChangesReportHtmlUrl: fcConfig.riskChangesReportHtmlUrl, + riskChangesReportPdfUrl: fcConfig.riskChangesReportPdfUrl }; var programs = ; diff --git a/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy b/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy index 8c5887bfa..9cec091f3 100644 --- a/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy +++ b/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy @@ -31,11 +31,21 @@ class ProjectSummaryReportCommand { String id String fromStage String toStage + String fromDate + String toDate List sections - public Map call() { - projectReportModel(id, fromStage, toStage, sections) + Map call() { + Map project = projectService.get(id, 'all') + + if (fromStage && !fromDate) { + fromDate = project.reports?.find { it.name == fromStage }?.fromDate + } + if (toStage && !toDate) { + toDate = project.reports?.find { it.name == toStage }?.toDate + } + projectReportModel(project, fromDate, toDate, sections) } private List blog(Map project, String fromDate, String toDate) { @@ -99,13 +109,9 @@ class ProjectSummaryReportCommand { [latestStageReport:latestStageReport, stageReportModel: stageReportModel] } - Map projectReportModel(String id, String fromStage, String toStage, List contentToInclude) { - Map project = projectService.get(id, 'all') - Map model = [project:project, role:getUserRole(), content:contentToInclude] + Map projectReportModel(Map project, String fromDate, String toDate, List contentToInclude) { - // Determine date range for data to include. - String fromDate = project.reports?.find { it.name == fromStage }?.fromDate - String toDate = project.reports?.find { it.name == toStage }?.toDate + Map model = [project:project, role:getUserRole(), content:contentToInclude] List reportedStages = [] diff --git a/test/js/spec/RisksReportViewModelSpec.js b/test/js/spec/RisksReportViewModelSpec.js new file mode 100644 index 000000000..eeb618873 --- /dev/null +++ b/test/js/spec/RisksReportViewModelSpec.js @@ -0,0 +1,92 @@ +describe("", function () { + + var bootboxMessage; + + beforeAll(function () { + window.bootbox = { + alert: function (message) { + bootboxMessage = message; + } + } + }); + afterAll(function () { + delete window.bootbox; + }); + + it("will set the defaults based on the current date constrained to the project start and end dates", function() { + var project = { + plannedStartDate: '2018-01-01T13:00:00Z', + plannedEndDate: '2020-01-01T13:00:00Z' + }; + var options = { + riskChangesReportHtmlUrl: '/html', + riskChangesReportPdfUrl: '/pdf' + }; + jasmine.clock().withMock(function() { + + + jasmine.clock().mockDate(new Date('2019-01-01T13:00:00Z')); + // The jasmine clock().install() interferes with our augmentation of Date + Date.fromISO= function(s){ return new Date(s); }; + var viewModel = new RisksReportViewModel(project, options); + + expect(viewModel.fromDate()).toEqual("2018-10-01T14:00:00Z"); + expect(viewModel.toDate()).toEqual("2019-01-01T13:00:00Z"); + + jasmine.clock().mockDate(new Date('2020-02-01T13:00:00Z')); + Date.fromISO= function(s){ return new Date(s); }; + + var viewModel = new RisksReportViewModel(project, options); + + expect(viewModel.fromDate()).toEqual("2019-11-01T13:00:00Z"); + expect(viewModel.toDate()).toEqual("2020-01-01T13:00:00Z"); + + jasmine.clock().mockDate(new Date('2017-11-01T13:00:00Z')); + Date.fromISO= function(s){ return new Date(s); }; + + var viewModel = new RisksReportViewModel(project, options); + + expect(viewModel.fromDate()).toEqual("2018-01-01T13:00:00Z"); + expect(viewModel.toDate()).toEqual("2018-01-01T13:00:00Z"); + + }); + + + + }); + + + it("will generate a URL based on the supplied properties", function() { + var project = { + plannedStartDate: '2018-01-01T13:00:00Z', + plannedEndDate: '2020-01-01T13:00:00Z' + }; + var options = { + riskChangesReportHtmlUrl: '/html', + riskChangesReportPdfUrl: '/pdf' + }; + jasmine.clock().withMock(function() { + jasmine.clock().mockDate(new Date('2019-01-01T13:00:00Z')); + // The jasmine clock().install() interferes with our augmentation of Date + Date.fromISO= function(s){ return new Date(s); }; + var viewModel = new RisksReportViewModel(project, options); + + var previous = window.open; + try { + var suppliedUrl; + var suppliedName; + window.open = function(url, name) { + suppliedUrl = url; + suppliedName = name; + }; + viewModel.generateRisksReportHTML(); + expect(suppliedUrl).toEqual("/html?fromDate2018-10-01T14:00:00Z&toStage=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); + viewModel.generateRisksReportPDF(); + expect(suppliedUrl).toEqual("/pdf?fromDate2018-10-01T14:00:00Z&toStage=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); + } + finally { + window.open = previous; + } + }); + }); +}); \ No newline at end of file diff --git a/test/unit/au/org/ala/merit/ProjectSummaryReportCommandSpec.groovy b/test/unit/au/org/ala/merit/ProjectSummaryReportCommandSpec.groovy new file mode 100644 index 000000000..0471bd279 --- /dev/null +++ b/test/unit/au/org/ala/merit/ProjectSummaryReportCommandSpec.groovy @@ -0,0 +1,64 @@ +package au.org.ala.merit + +import au.org.ala.merit.command.ProjectSummaryReportCommand +import spock.lang.Specification + +class ProjectSummaryReportCommandSpec extends Specification { + + ProjectService projectService = Mock(ProjectService) + ProjectSummaryReportCommand command = new ProjectSummaryReportCommand() + UserService userService = Mock(UserService) + MetadataService metadataService = Mock(MetadataService) + + void setup() { + command.projectService = projectService + command.userService = userService + command.metadataService = metadataService + } + + void "The project summary report can accept dates for the report"() { + setup: + command.id = "p1" + command.sections = ["Section 1"] + command.fromDate = "2020-01-01T14:00:00Z" + command.toDate = "2020-04-01T14:00:00Z" + + when: + Map model = command() + + then: + 1 * projectService.get(command.id, 'all') >> [projectId:command.id] + 1 * metadataService.activitiesModel() >> [:] + model.project == [projectId:command.id] + model.role == "MERIT user" + model.content == command.sections + + + } + + void "The project summary report will infer dates from stage names if supplied"() { + setup: + command.id = "p1" + command.sections = ["Section 1"] + command.fromStage = "Stage 1" + command.toStage = "Stage 3" + List reports = [ + [name:"Stage 1", fromDate:"2017-06-01T14:00:00Z", toDate:"2018-01-01T13:00:00Z"], + [name:"Stage 2", fromDate:"2018-01-01T13:00:00Z", toDate:"2018-06-01T14:00:00Z"], + [name:"Stage 3", fromDate:"2018-06-01T14:00:00Z", toDate:"2019-01-01T14:00:00Z"] + ] + Map project = [projectId:command.id, reports:reports] + + when: + Map model = command() + + then: + 1 * projectService.get(command.id, 'all') >> project + 1 * metadataService.activitiesModel() >> [:] + model.project == project + model.role == "MERIT user" + model.content == command.sections + + + } +} From a55a13a89971d45afb919d631f997cb5d6f74ac5 Mon Sep 17 00:00:00 2001 From: Thakur Adhikari Date: Mon, 22 Jun 2020 13:37:34 +1000 Subject: [PATCH 02/17] #2047 fixed NPE and added to support functional test --- grails-app/views/program/edit.gsp | 2 +- .../ala/fieldcapture/EditProgramSpec.groovy | 34 ++++++++++++++++++- test/functional/pages/ProgramPage.groovy | 29 ++++++++++++++++ .../resources/dataset_mu/loadDataSet.js | 6 ++-- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/grails-app/views/program/edit.gsp b/grails-app/views/program/edit.gsp index c56d9c03a..8160286af 100644 --- a/grails-app/views/program/edit.gsp +++ b/grails-app/views/program/edit.gsp @@ -46,7 +46,7 @@ $(function () { var program = ; - program.parentProgramId = "${program.parent.programId}" + program.parentProgramId = "${program.parent?.programId}" var programViewModel = new ProgramViewModel(program, fcConfig); diff --git a/test/functional/au/org/ala/fieldcapture/EditProgramSpec.groovy b/test/functional/au/org/ala/fieldcapture/EditProgramSpec.groovy index 7537175dd..7cbb3bae1 100644 --- a/test/functional/au/org/ala/fieldcapture/EditProgramSpec.groovy +++ b/test/functional/au/org/ala/fieldcapture/EditProgramSpec.groovy @@ -1,6 +1,7 @@ package au.org.ala.fieldcapture import pages.EditProgram +import pages.RLPEditPageWithNoParent import pages.RLPProgramPage class EditProgramSpec extends StubbedCasSpec { @@ -13,7 +14,7 @@ class EditProgramSpec extends StubbedCasSpec { logout(browser) } - def "As a user with admin permissions, I can edit a program"() { + def "As a user with admin permissions, I can edit a program with parent id"() { setup: "log in as userId=1 who is a program admin for the program with programId=test_program" login([userId:'1', role:"ROLE_FC_ADMIN", email:'fc-admin@nowhere.com', firstName: "FC", lastName:'Admin'], browser) @@ -45,4 +46,35 @@ class EditProgramSpec extends StubbedCasSpec { "The program details have been updated" } + def "As a user with admin permissions, I can edit a program with No parent"() { + setup: "log in as userId=1 who is a program admin for the program with programId=test_programId" + login([userId:'1', role:"ROLE_FC_ADMIN", email:'fc-admin@nowhere.com', firstName: "FC", lastName:'Admin'], browser) + + when: + to RLPEditPageWithNoParent + + and: + edit() + + then: + waitFor { at EditProgram } + + when: + details.newParentProgramId.click() + details.newParentProgramId.find("span").find{it.text() == "No Parent"}.click() + details.description= "Testing" + details.url = "http://ala.org.au" + details.save() + + + then: + at RLPEditPageWithNoParent + overviewTab.click() + overviewTab.displayed + description.text() == "Testing" + visitUs.text() == "http://ala.org.au" + + and: + "The program details have been updated" + } } diff --git a/test/functional/pages/ProgramPage.groovy b/test/functional/pages/ProgramPage.groovy index f9267a2cd..46ac4c37a 100644 --- a/test/functional/pages/ProgramPage.groovy +++ b/test/functional/pages/ProgramPage.groovy @@ -69,5 +69,34 @@ class RLPProgramPage extends Page{ waitFor { adminTabContent.addSubProgramButton.displayed } adminTabContent. addSubProgramButton.click() } +} + +class RLPEditPageWithNoParent extends Page{ + static url = 'rlp/index/test_programId' + + static at = { waitFor {name.text() !=null}} + + static content= { + name {$('h2')} + grantIdsTable{$('td.grantId')} + projectNameTable{$('td.projectName')} + muInStatesTable{$('div[id^=state-mu-] li a')} + showAllStatesMuButton {$('#showAllStatesMu')} + overviewTab{$('a#about-tab',0)} + adminTab { $('a#admin-tab') } + adminTabContent { module ProgramAdminTab } + visitUs {$('#weburl span')} + description {$('.row .col-md-4 span[data-bind*="html:description"] p')} + subProgramTabContent(required:false) {$("div#subProgramWrapper").moduleList(subProgramContent)} + + } + + void edit() { + adminTab.click() + waitFor { adminTabContent.displayed } + adminTabContent.editTab.click() + waitFor { adminTabContent.editButton.displayed} + adminTabContent.editButton.click() + } } diff --git a/test/functional/resources/dataset_mu/loadDataSet.js b/test/functional/resources/dataset_mu/loadDataSet.js index b12dc0bc9..e71c00285 100644 --- a/test/functional/resources/dataset_mu/loadDataSet.js +++ b/test/functional/resources/dataset_mu/loadDataSet.js @@ -19,7 +19,9 @@ createProject({name:'project 1', projectId:"project_1", programId:'test_program' createProject({name:'project 2', projectId:"project_2", programId:'test_program',managementUnitId:"test_mu_2",siteId:'test_site_2', grantId:"RLP-Test-Program-Project-2"}) createProject({name:'project in ACT', projectId:"project_3", programId:'test_program',managementUnitId:"test_mu_3",siteId:'test_site_3', grantId:"RLP-Test-Program-Project-3"}) +createProgram({name:'A test program', programId:'test_programId'}) +db.userPermission.insert({entityType:'au.org.ala.ecodata.Program', entityId:'test_programId', userId:'1', accessLevel:'admin'}); createSite(site1) @@ -37,9 +39,9 @@ var blog_program = { "stockIcon" : "" } -createProgram({name:'New Test Program', parent:null ,programId:'new_test_Program'}) +createProgram({name:'New Test Program', parent:null, programId:'new_test_Program'}) createProgram({name:'New Second Test program', parent:null ,programId:'second_test_program'}) -createProgram({name:'Regional Land Partnerships', parent:null ,programId:'test_program', blog:[blog_program]}) +createProgram({name:'Regional Land Partnerships', parent: null ,programId:'test_program', blog:[blog_program]}) createOrganisation({ name:'Test Organisation', From 4a74b04742c12fa2376d54b5023f62752371147b Mon Sep 17 00:00:00 2001 From: Thakur Adhikari Date: Tue, 23 Jun 2020 16:03:25 +1000 Subject: [PATCH 03/17] #2048 Added abn warning message on project load and update functional test --- src/groovy/au/org/ala/merit/GmsMapper.groovy | 30 +++++++++++-------- .../ala/fieldcapture/AddSubProgramSpec.groovy | 8 ++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/groovy/au/org/ala/merit/GmsMapper.groovy b/src/groovy/au/org/ala/merit/GmsMapper.groovy index e4560b68a..41129dac8 100644 --- a/src/groovy/au/org/ala/merit/GmsMapper.groovy +++ b/src/groovy/au/org/ala/merit/GmsMapper.groovy @@ -208,22 +208,28 @@ class GmsMapper { project.organisationId = organisation.organisationId }else { String abn = project.abn - abnLookup = abnLookupService.lookupOrganisationNameByABN(abn) - if (abnLookup){ - organisation = organisations.find{it.organisationName == abnLookup.entityName} - if (organisation){ - project.organisationId = organisation.organisationId - }else{ - if(abnLookup.entityName == ""){ - errors << "${project.abn} is invalid abn number. Please Enter the correct one" + if (!abn){ + if (!organisation && project.organisationName){ + errors << "No organisation exists with organisation name ${project.organisationName}" + } + }else{ + abnLookup = abnLookupService.lookupOrganisationNameByABN(abn) + if (abnLookup){ + organisation = organisations.find{it.organisationName == abnLookup.entityName} + if (organisation){ + project.organisationId = organisation.organisationId }else{ - project.organisationName = abnLookup.entityName + if(abnLookup.entityName == ""){ + errors << "${project.abn} is invalid abn number. Please Enter the correct one" + }else{ + project.organisationName = abnLookup.entityName + } } - + }else{ + errors << "${project.abn} is invalid. Please Enter the correct one" } - }else{ - errors << "${project.abn} is invalid. Please Enter the correct one" } + } }else{ errors << "No organisation exists with abn ${project.abn} number and organisation name ${project.organisationName}" diff --git a/test/functional/au/org/ala/fieldcapture/AddSubProgramSpec.groovy b/test/functional/au/org/ala/fieldcapture/AddSubProgramSpec.groovy index b53d4b07a..ad5cb450f 100644 --- a/test/functional/au/org/ala/fieldcapture/AddSubProgramSpec.groovy +++ b/test/functional/au/org/ala/fieldcapture/AddSubProgramSpec.groovy @@ -25,10 +25,10 @@ class AddSubProgramSpec extends StubbedCasSpec { addSubProgram() then: - waitFor( 10, { at AddSubProgram }) // This has occasionally timed out. + waitFor{ at AddSubProgram } // This has occasionally timed out. when: - program.name = "A test program" + program.name = "add new sub program" program.description = "A test description" program.save() @@ -48,7 +48,7 @@ class AddSubProgramSpec extends StubbedCasSpec { then: subContent !=null - waitFor {subContent[0].subProgramTitle.text() == "A test program"} + waitFor {subContent[0].subProgramTitle.text() == "add new sub program"} when: subContent[0].subProgramTitle.click() @@ -57,7 +57,7 @@ class AddSubProgramSpec extends StubbedCasSpec { waitFor( 10,{subContent.displayed}) and: - subContent[0].subProgramName.text() == "A test program" + subContent[0].subProgramName.text() == "add new sub program" subContent[0].subProgramDescription.text() == "A test description" } From 1c476d0629eb5c50c2e45f51e193c408c2c44c21 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 25 Jun 2020 13:19:09 +1000 Subject: [PATCH 04/17] Exclude secodary outcomes from MU summary #2052 --- .../ala/merit/ManagementUnitController.groovy | 24 +++++++++++------- .../au/org/ala/merit/ProjectService.groovy | 8 ++++++ grails-app/views/managementUnit/_about.gsp | 4 +-- .../merit/ManagementUnitControllerSpec.groovy | 25 ++++++++++++++++++- .../org/ala/merit/ProjectServiceSpec.groovy | 7 ++++++ 5 files changed, 56 insertions(+), 12 deletions(-) diff --git a/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy b/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy index 378a14a22..3f1ac8a3f 100644 --- a/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy +++ b/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy @@ -20,6 +20,7 @@ class ManagementUnitController { ActivityService activityService PdfGenerationService pdfGenerationService BlogService blogService + ProjectService projectService def index(String id) { def mu = managementUnitService.get(id) @@ -94,8 +95,8 @@ class ManagementUnitController { projectsByCategory.each { String programId, List projectsInProgramGroup -> Map program = mu.programs.find{it.programId == programId} - aggregateOutcomes(program, projectsInProgramGroup) - displayedPrograms << [program:program, projects: projectsInProgramGroup, servicesWithScores:servicesWithScores[programId]] + List outcomes = findTargetedPrimaryOutcomes(program, projectsInProgramGroup) + displayedPrograms << [program:program, projects: projectsInProgramGroup, servicesWithScores:servicesWithScores[programId], primaryOutcomes:outcomes] } List reportOrder = mu.config?.managementUnitReports?.collect{[category:it.category, description:it.description]} ?: [] @@ -149,19 +150,24 @@ class ManagementUnitController { } - //Aggregate all targeted outcomes of projects in the given program - private def aggregateOutcomes(Map program, List projects){ + /** + * Returns a list of program primary outcomes, with an extra entry (targeted) specifying whether any project + * has targeted that outcome as the primary outcome of the project. + * @param program the program. + * @param projects all projects run under the program in the management unit + */ + private List findTargetedPrimaryOutcomes(Map program, List projects) { + List outcomes = program.outcomes.findAll {it.type != 'secondary'}.collect{[outcome:it.outcome, shortDescription:it.shortDescription]} for(Map project in projects){ - //Verify project.outcomes (from program config) with primaryOutcome and secondaryOutcomes in project.custom.details.outcomes - Map primaryOutcome = project.custom?.details?.outcomes?.primaryOutcome + String primaryOutcome = projectService.getPrimaryOutcome(project) if (primaryOutcome){ - Map oc = program.outcomes.find {oc -> oc.outcome == primaryOutcome.description} + Map oc = outcomes.find {it.outcome == primaryOutcome} if (oc) { - oc['targeted'] = true //set program outcomes - primaryOutcome.shortDescription = oc['shortDescription'] + oc['targeted'] = true // at least on project is targeting this outcome as the primary outcome. } } } + outcomes } diff --git a/grails-app/services/au/org/ala/merit/ProjectService.groovy b/grails-app/services/au/org/ala/merit/ProjectService.groovy index 071c50ba8..055194324 100644 --- a/grails-app/services/au/org/ala/merit/ProjectService.groovy +++ b/grails-app/services/au/org/ala/merit/ProjectService.groovy @@ -1683,4 +1683,12 @@ class ProjectService { List secondaryOutcomes = project?.custom?.details?.outcomes?.secondaryOutcomes?.collect{it.assets}?.flatten() ?: [] primaryOutcomes + secondaryOutcomes } + + /** + * Returns the primary outcome specified in the project MERI plan, null if none is specified. + * @param project the project of interest + */ + String getPrimaryOutcome(Map project) { + project?.custom?.details?.outcomes?.primaryOutcome?.description + } } diff --git a/grails-app/views/managementUnit/_about.gsp b/grails-app/views/managementUnit/_about.gsp index ebbcdbf4b..0f6275dea 100644 --- a/grails-app/views/managementUnit/_about.gsp +++ b/grails-app/views/managementUnit/_about.gsp @@ -48,11 +48,11 @@
- +
The Service Provider is addressing these ${program.acronym ?: program.name} outcomes
- +
diff --git a/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy b/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy index aa594a34f..f7d8cbaca 100644 --- a/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy +++ b/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy @@ -15,7 +15,7 @@ class ManagementUnitControllerSpec extends Specification { RoleService roleService = Mock(RoleService) BlogService blogService = Mock(BlogService) ProgramService programService = Mock(ProgramService) - + ProjectService projectService = Mock(ProjectService) String adminUserId = 'admin' String editorUserId = 'editor' @@ -30,6 +30,7 @@ class ManagementUnitControllerSpec extends Specification { controller.blogService = blogService controller.managementUnitService = managementUnitService controller.programService = programService + controller.projectService = projectService roleService.getRoles() >> [] } @@ -327,6 +328,28 @@ class ManagementUnitControllerSpec extends Specification { } + def "The management unit controller supports the display of program outcomes that are targeted by projects in that management unit"() { + String managementUnitId = 'mu1' + userService.getUser() >> [userId:'u1'] + managementUnitService.get(managementUnitId) >> [managementUnitId:managementUnitId, name:"test"] + userService.getMembersOfManagementUnit(managementUnitId) >> [members:[[userId:'u1', role:'admin']]] + managementUnitService.getProjects(managementUnitId) >> [projects:[[projectId:'p1', programId:"program1"]]] + managementUnitService.serviceScores(managementUnitId, _, _) >> [:] + projectService.getPrimaryOutcome(_) >> "Outcome 1" + programService.get("program1") >> [[programId:'program1', outcomes:[[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"]]]] + + when: + Map model = controller.index(managementUnitId) + + then: + 1 * userService.canEditManagementUnitBlog("u1", managementUnitId) >> true + 1 * userService.canUserEditManagementUnit("u1", managementUnitId) >> true + + model.content.about.displayedPrograms.size() == 1 + model.content.about.displayedPrograms[0].primaryOutcomes == [[outcome:"Outcome 1", shortDescription:"o1", targeted:true], [outcome:"Outcome 2", shortDescription:"o2"]] + + } + private Map testManagementUnit(String id, boolean includeReports) { Map program = [managementUnitId:id, name:'name', config:[:], config:[:]] if (includeReports) { diff --git a/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy b/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy index ed16d22ef..97cff54f7 100644 --- a/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy +++ b/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy @@ -965,6 +965,13 @@ class ProjectServiceSpec extends Specification { activities == [] } + def "The service can return the primary outcome for a project"() { + expect: + service.getPrimaryOutcome([custom:[details:[outcomes:[primaryOutcome:[description:'Outcome 1']]]]]) == 'Outcome 1' + service.getPrimaryOutcome([custom:[details:[:]]]) == null + service.getPrimaryOutcome(null) == null + } + private Map buildApprovalDocument(int i, String projectId) { Map approval = [ dateApproved:"2019-07-01T00:00:0${i}Z", From 9c271d29dfc0256a3ae138dcec445b3ea902cbbc Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 25 Jun 2020 13:19:58 +1000 Subject: [PATCH 05/17] 1.56-SNAPSHOT --- application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.properties b/application.properties index 0367cb989..40f9e659c 100644 --- a/application.properties +++ b/application.properties @@ -2,5 +2,5 @@ #Tue Jun 20 09:06:54 EST 2017 app.grails.version=2.5.6 app.name=fieldcapture -app.version=1.55 +app.version=1.56-SNAPSHOT From 51fc83ee300266585467b8894676e1a4275e7466 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 29 Jun 2020 12:32:17 +1000 Subject: [PATCH 06/17] Risks and threats changes report #2018 --- grails-app/assets/javascripts/projects.js | 2 +- grails-app/views/project/_projectAdmin.gsp | 7 +++++-- .../ala/merit/command/ProjectSummaryReportCommand.groovy | 5 ++++- test/js/spec/RisksReportViewModelSpec.js | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/grails-app/assets/javascripts/projects.js b/grails-app/assets/javascripts/projects.js index b13235676..dc495ba2e 100644 --- a/grails-app/assets/javascripts/projects.js +++ b/grails-app/assets/javascripts/projects.js @@ -1467,7 +1467,7 @@ var RisksReportViewModel = function(project, options) { self.orientation = ko.observable('portrait'); function displayReport(url) { - var paramString= '?fromDate'+self.fromDate()+'&toStage='+self.toDate(); + var paramString= '?fromDate'+self.fromDate()+'&toDate='+self.toDate(); paramString+='§ions=Project+risks+changes'; paramString+='&orientation='+self.orientation(); diff --git a/grails-app/views/project/_projectAdmin.gsp b/grails-app/views/project/_projectAdmin.gsp index 60d9d4465..7a76b7abd 100644 --- a/grails-app/views/project/_projectAdmin.gsp +++ b/grails-app/views/project/_projectAdmin.gsp @@ -8,7 +8,8 @@
  • MERI Plan
  • -
  • Risks and threats
  • +
  • Risks and threats
  • +
  • Risks and threats changes
  • @@ -65,11 +66,13 @@
    -
    +
    + +
    diff --git a/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy b/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy index 9cec091f3..31b5b7f56 100644 --- a/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy +++ b/src/groovy/au/org/ala/merit/command/ProjectSummaryReportCommand.groovy @@ -155,7 +155,10 @@ class ProjectSummaryReportCommand { activity.reason = document?.notes } Map activityModel = activitiesModel.activities.find{it.name == activity.type} - activityModels << activityModel + if (activityModel) { + activityModels << activityModel + } + Map report = reportService.findReportForDate(activity.plannedEndDate, project.reports) if (report && report.name) { activitiesByStage[report.name] << activity diff --git a/test/js/spec/RisksReportViewModelSpec.js b/test/js/spec/RisksReportViewModelSpec.js index eeb618873..b97540e9e 100644 --- a/test/js/spec/RisksReportViewModelSpec.js +++ b/test/js/spec/RisksReportViewModelSpec.js @@ -80,9 +80,9 @@ describe("", function () { suppliedName = name; }; viewModel.generateRisksReportHTML(); - expect(suppliedUrl).toEqual("/html?fromDate2018-10-01T14:00:00Z&toStage=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); + expect(suppliedUrl).toEqual("/html?fromDate2018-10-01T14:00:00Z&toDate=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); viewModel.generateRisksReportPDF(); - expect(suppliedUrl).toEqual("/pdf?fromDate2018-10-01T14:00:00Z&toStage=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); + expect(suppliedUrl).toEqual("/pdf?fromDate2018-10-01T14:00:00Z&toDate=2019-01-01T13:00:00Z§ions=Project+risks+changes&orientation=portrait"); } finally { window.open = previous; From 2223d05ec6c2437e0b78d46cfd0d1636fe9b14c0 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 29 Jun 2020 15:57:30 +1000 Subject: [PATCH 07/17] Increased unit test coverage requirement --- grails-app/conf/BuildConfig.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 274e8f13f..7d1e8d5a6 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -56,7 +56,7 @@ clover { } } } - ant.'clover-check'(target: "28.3%", haltOnFailure: true) { } + ant.'clover-check'(target: "29.5%", haltOnFailure: true) { } } } From 716f799ce442073bbc584768910875055f3ef5eb Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 09:12:28 +1000 Subject: [PATCH 08/17] Implemented secondary outcome display on MU #2052 --- grails-app/conf/BuildConfig.groovy | 2 +- .../ala/merit/ManagementUnitController.groovy | 32 +++++++--- .../au/org/ala/merit/ProgramService.groovy | 10 ++++ .../au/org/ala/merit/ProjectService.groovy | 8 +++ grails-app/views/managementUnit/_about.gsp | 21 ++----- grails-app/views/managementUnit/_outcomes.gsp | 15 +++++ .../fieldcapture/ManagementUnitSpec.groovy | 59 +++++++++++++++++-- .../pages/ManagementUnitPage.groovy | 20 ++++++- .../resources/dataset_mu/loadDataSet.js | 49 ++++++++------- .../resources/wiremock/mappings/favicon.json | 12 ++++ .../merit/ManagementUnitControllerSpec.groovy | 10 +++- .../org/ala/merit/ProgramServiceSpec.groovy | 10 ++++ .../org/ala/merit/ProjectServiceSpec.groovy | 7 +++ 13 files changed, 201 insertions(+), 54 deletions(-) create mode 100644 grails-app/views/managementUnit/_outcomes.gsp create mode 100644 test/functional/resources/wiremock/mappings/favicon.json diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 7d1e8d5a6..6ccde6c38 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -56,7 +56,7 @@ clover { } } } - ant.'clover-check'(target: "29.5%", haltOnFailure: true) { } + ant.'clover-check'(target: "29.7%", haltOnFailure: true) { } } } diff --git a/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy b/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy index 3f1ac8a3f..e6216a705 100644 --- a/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy +++ b/grails-app/controllers/au/org/ala/merit/ManagementUnitController.groovy @@ -95,8 +95,9 @@ class ManagementUnitController { projectsByCategory.each { String programId, List projectsInProgramGroup -> Map program = mu.programs.find{it.programId == programId} - List outcomes = findTargetedPrimaryOutcomes(program, projectsInProgramGroup) - displayedPrograms << [program:program, projects: projectsInProgramGroup, servicesWithScores:servicesWithScores[programId], primaryOutcomes:outcomes] + List primaryOutcomes = findTargetedPrimaryOutcomes(program, projectsInProgramGroup) + List secondaryOutcomes = findTargetedSecondaryOutcomes(program, projectsInProgramGroup) + displayedPrograms << [program:program, projects: projectsInProgramGroup, servicesWithScores:servicesWithScores[programId], primaryOutcomes:primaryOutcomes, secondaryOutcomes:secondaryOutcomes] } List reportOrder = mu.config?.managementUnitReports?.collect{[category:it.category, description:it.description]} ?: [] @@ -157,19 +158,36 @@ class ManagementUnitController { * @param projects all projects run under the program in the management unit */ private List findTargetedPrimaryOutcomes(Map program, List projects) { - List outcomes = program.outcomes.findAll {it.type != 'secondary'}.collect{[outcome:it.outcome, shortDescription:it.shortDescription]} + List outcomes = programService.getPrimaryOutcomes(program).collect{[outcome:it.outcome, shortDescription:it.shortDescription]} for(Map project in projects){ - String primaryOutcome = projectService.getPrimaryOutcome(project) - if (primaryOutcome){ - Map oc = outcomes.find {it.outcome == primaryOutcome} + String outcome = projectService.getPrimaryOutcome(project) + if (outcome){ + Map oc = outcomes.find {it.outcome == outcome} if (oc) { - oc['targeted'] = true // at least on project is targeting this outcome as the primary outcome. + oc['targeted'] = true // at least one project is targeting this outcome as the primary outcome. } } } outcomes } + /** + * Returns a list of program primary outcomes, with an extra entry (targeted) specifying whether any project + * has targeted that outcome as the primary outcome of the project. + * @param program the program. + * @param projects all projects run under the program in the management unit + */ + private List findTargetedSecondaryOutcomes(Map program, List projects) { + List outcomes = programService.getSecondaryOutcomes(program).collect{[outcome:it.outcome, shortDescription:it.shortDescription]} + for(Map project in projects){ + List projectOutcomes = projectService.getSecondaryOutcomes(project) + outcomes.findAll { it.outcome in projectOutcomes }.each { + it['targeted'] = true + } + } + outcomes + } + @PreAuthorise(accessLevel='siteAdmin') def create() { diff --git a/grails-app/services/au/org/ala/merit/ProgramService.groovy b/grails-app/services/au/org/ala/merit/ProgramService.groovy index 07b9a8eaa..7362b4daf 100644 --- a/grails-app/services/au/org/ala/merit/ProgramService.groovy +++ b/grails-app/services/au/org/ala/merit/ProgramService.groovy @@ -10,6 +10,9 @@ import org.codehaus.groovy.util.ListHashMap class ProgramService { private static final String PROGRAM_DOCUMENT_FILTER = "className:au.org.ala.ecodata.Program" + public static final String OUTCOME_TYPE_PRIMARY_ONLY = 'primary' + public static final String OUTCOME_TYPE_SECONDARY_ONLY = 'secondary' + GrailsApplication grailsApplication WebService webService @@ -276,6 +279,13 @@ class ProgramService { parent && parent.programId == programId } + List getPrimaryOutcomes(Map program) { + program?.outcomes.findAll {it.type != OUTCOME_TYPE_SECONDARY_ONLY} + } + List getSecondaryOutcomes(Map program) { + program?.outcomes.findAll {it.type != OUTCOME_TYPE_PRIMARY_ONLY} + } + List listOfAllPrograms(){ return webService.getJson("${grailsApplication.config.ecodata.baseUrl}program/listOfAllPrograms") } diff --git a/grails-app/services/au/org/ala/merit/ProjectService.groovy b/grails-app/services/au/org/ala/merit/ProjectService.groovy index 055194324..f9b61039d 100644 --- a/grails-app/services/au/org/ala/merit/ProjectService.groovy +++ b/grails-app/services/au/org/ala/merit/ProjectService.groovy @@ -1691,4 +1691,12 @@ class ProjectService { String getPrimaryOutcome(Map project) { project?.custom?.details?.outcomes?.primaryOutcome?.description } + + /** + * Returns the secondary outcome(s) specified in the project MERI plan, or an empty list if none are specified. + * @param project the project of interest + */ + List getSecondaryOutcomes(Map project) { + project?.custom?.details?.outcomes?.secondaryOutcomes?.collect{it.description} ?: [] + } } diff --git a/grails-app/views/managementUnit/_about.gsp b/grails-app/views/managementUnit/_about.gsp index 0f6275dea..4f204cf2b 100644 --- a/grails-app/views/managementUnit/_about.gsp +++ b/grails-app/views/managementUnit/_about.gsp @@ -49,21 +49,12 @@
    -
    -
    The Service Provider is addressing these ${program.acronym ?: program.name} outcomes
    -
    - - -
    -
    -
    - ${outcome.shortDescription} -
    -
    -
    -
    -
    -
    + ${programDetails.primaryOutcomes} + +
    +
    + +
    diff --git a/grails-app/views/managementUnit/_outcomes.gsp b/grails-app/views/managementUnit/_outcomes.gsp new file mode 100644 index 000000000..4e3b1640f --- /dev/null +++ b/grails-app/views/managementUnit/_outcomes.gsp @@ -0,0 +1,15 @@ +
    +
    ${title}
    +
    + + +
    +
    +
    + ${outcome.shortDescription} +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/test/functional/au/org/ala/fieldcapture/ManagementUnitSpec.groovy b/test/functional/au/org/ala/fieldcapture/ManagementUnitSpec.groovy index cb9e8fb05..e475d02f1 100644 --- a/test/functional/au/org/ala/fieldcapture/ManagementUnitSpec.groovy +++ b/test/functional/au/org/ala/fieldcapture/ManagementUnitSpec.groovy @@ -5,6 +5,11 @@ import pages.AdminReportsPage class ManagementUnitSpec extends StubbedCasSpec { + static final String NO_PERMISSIONS_USER = "10" + static final String EDITOR_USER = "4" + static final String ADMIN_USER = "1" + static final String GRANT_MANAGER_USER = "3" + def setup() { useDataSet('dataset_mu') } @@ -13,9 +18,51 @@ class ManagementUnitSpec extends StubbedCasSpec { logout(browser) } - def "As a user, I can view a management unit"() { + def "Only the about tab is visible to unauthenticated users"() { + when: + to ManagementUnitPage + + then: + waitFor {at ManagementUnitPage} + overviewBtn.displayed + !reportsTab.displayed + !sitesTab.displayed + !adminTab.displayed + } + + + def "Only the about tab is visible to users with no permissions for the management unit"( + String userId, boolean aboutVisible, boolean reportsVisible, boolean sitesVisible, boolean adminVisible) { + + setup: + String role = "ROLE_USER" + if (userId == GRANT_MANAGER_USER) { + role = "ROLE_FC_OFFICER" + } + login([userId:userId, role:role, email:'user@nowhere.com', firstName: "MERIT", lastName:'User'], browser) + + when: + to ManagementUnitPage + + then: + waitFor {at ManagementUnitPage} + overviewBtn.displayed == aboutVisible + reportsTab.displayed == reportsVisible + sitesTab.displayed == sitesVisible + adminTab.displayed == adminVisible + + where: + userId | aboutVisible | reportsVisible | sitesVisible | adminVisible + NO_PERMISSIONS_USER | true | false | false | false + EDITOR_USER | true | true | true | false + ADMIN_USER | true | true | true | true + GRANT_MANAGER_USER | true | true | true | true + + } + + def "The management unit about tab displays information about programs and projects with activity in the management unit"() { setup: - login([userId:'1', role:"ROLE_USER", email:'user@nowhere.com', firstName: "MERIT", lastName:'User'], browser) + login([userId:ADMIN_USER, role:"ROLE_USER", email:'user@nowhere.com', firstName: "MERIT", lastName:'User'], browser) when: to ManagementUnitPage @@ -34,12 +81,14 @@ class ManagementUnitSpec extends StubbedCasSpec { then: - // grantIds() == ['RLP-Test-Program-Project-1'] will fail when using phantomjs - grantIds().size() ==1 + grantIds() == ['RLP-Test-Program-Project-1'] projectLinks().size()>=1 gotoProgram().size() >= 1 - + primaryOutcomes() == ['o1', 'o2'] + targetedPrimaryOutcomes() == ['o1'] + secondaryOutcomes() == ['o2', 'o3'] + targetedSecondaryOutcomes() == ['o2', 'o3'] } diff --git a/test/functional/pages/ManagementUnitPage.groovy b/test/functional/pages/ManagementUnitPage.groovy index 80a43821b..79915a359 100644 --- a/test/functional/pages/ManagementUnitPage.groovy +++ b/test/functional/pages/ManagementUnitPage.groovy @@ -16,9 +16,6 @@ class ManagementUnitPage extends ReloadablePage { grantIdsTable{$('td.grantId')} projectLinksTd{$('td.grantId a')} gotoProgramLinks{$('a.gotoProgram')} - blogTab{$('#blog-tab')} - blogContentDiv {$('div.muBlogContent')} - blogModule {module BlogPageModule} editManagementUnitBlogPane{$('div#editManagementUnitBlog')} adminTabPane(required: false) { module ManagementUnitAdminTab } editMUBlogTab{$('a#editManagementUnitBlog-tab')} @@ -26,6 +23,7 @@ class ManagementUnitPage extends ReloadablePage { adminTab(required: false) { $('#admin-tab') } reportsTab(required: false) { $('#projects-tab') } reportsTabPane(required: false) { module ManagementUnitReports } + sitesTab(required:false) { $('#sites-tab') } } List grantIds() { @@ -40,6 +38,22 @@ class ManagementUnitPage extends ReloadablePage { gotoProgramLinks.collect{it} } + List primaryOutcomes() { + $('div.outcomes.primary .outcome').collect{it.text().trim()} + } + + List targetedPrimaryOutcomes() { + $('div.outcomes.primary .outcome.targeted').collect{it.text().trim()} + } + + List secondaryOutcomes() { + $('div.outcomes.secondary .outcome').collect{it.text().trim()} + } + + List targetedSecondaryOutcomes() { + $('div.outcomes.secondary .outcome.targeted').collect{it.text().trim()} + } + void openDocumentDialog() { adminTab.click() waitFor { adminTabPane.displayed } diff --git a/test/functional/resources/dataset_mu/loadDataSet.js b/test/functional/resources/dataset_mu/loadDataSet.js index e71c00285..6bc6d9cc3 100644 --- a/test/functional/resources/dataset_mu/loadDataSet.js +++ b/test/functional/resources/dataset_mu/loadDataSet.js @@ -14,8 +14,14 @@ var blog1 = { "stockIcon" : "" } +var meriPlan = { + outcomes: { + primaryOutcome: { description: 'outcome 1'}, + secondaryOutcomes: [ {description: 'outcome 2'}, {description: 'outcome 3'} ] + } +}; createProject({name:'project 1', projectId:"project_1", programId:'test_program',managementUnitId:"test_mu",siteId:'test_site_1', grantId:"RLP-Test-Program-Project-1", - blog:[blog1]}) + blog:[blog1], custom:{details:meriPlan}}); createProject({name:'project 2', projectId:"project_2", programId:'test_program',managementUnitId:"test_mu_2",siteId:'test_site_2', grantId:"RLP-Test-Program-Project-2"}) createProject({name:'project in ACT', projectId:"project_3", programId:'test_program',managementUnitId:"test_mu_3",siteId:'test_site_3', grantId:"RLP-Test-Program-Project-3"}) @@ -39,9 +45,25 @@ var blog_program = { "stockIcon" : "" } +var outcomes = [ + { + outcome:'outcome 1', + shortDescription: 'o1', + type:'primary' + }, + { + outcome:'outcome 2', + shortDescription: 'o2' + }, + { + outcome:'outcome 3', + shortDescription: 'o3', + type:'secondary' + } +]; createProgram({name:'New Test Program', parent:null, programId:'new_test_Program'}) -createProgram({name:'New Second Test program', parent:null ,programId:'second_test_program'}) -createProgram({name:'Regional Land Partnerships', parent: null ,programId:'test_program', blog:[blog_program]}) +createProgram({name:'New Second Test program', parent:null, programId:'second_test_program'}) +createProgram({name:'Regional Land Partnerships', parent: null, programId:'test_program', blog:[blog_program], outcomes:outcomes}) createOrganisation({ name:'Test Organisation', @@ -51,24 +73,9 @@ createOrganisation({ acronym:'TSTORG' }) -var blog_mu = { - "content": "blog test", - "keepOnTop" : true, - "title" : "BlogTest", - "blogEntryId" : "0", - "managementUnitId" : "test_mu", - "date" : "2017-01-03T13:00:00Z", - "type" : "Management Unit Stories", - "stockIcon" : "" -} - - -createMu({name:'test mu', managementUnitId:"test_mu",managementUnitSiteId:'test_site_1', blog:[blog_mu]}); -createMu({name:'test mu 2', managementUnitId:"test_mu_2",managementUnitSiteId:'test_site_2'}) -createMu({name:'test mu in ACT', managementUnitId:"test_mu_3",managementUnitSiteId:'test_site_3'}) - -// createProgram({programId:"test_program_2", name:"test program 2"}) -// createProject({projectId:"test_project_2", name:"test project 2", programId:'test_program_2', managementUnitId:"test_mu"}) +createMu({name:'test mu', managementUnitId:"test_mu",managementUnitSiteId:'test_site_1'}); +createMu({name:'test mu 2', managementUnitId:"test_mu_2",managementUnitSiteId:'test_site_2'}); +createMu({name:'test mu in ACT', managementUnitId:"test_mu_3",managementUnitSiteId:'test_site_3'}); db.userPermission.insert({entityType:'au.org.ala.ecodata.Program', entityId:'test_program', userId:'1', accessLevel:'admin'}); db.userPermission.insert({entityType:'au.org.ala.ecodata.Project', entityId:'project_1', userId:'1', accessLevel:'admin'}); diff --git a/test/functional/resources/wiremock/mappings/favicon.json b/test/functional/resources/wiremock/mappings/favicon.json new file mode 100644 index 000000000..9a42a0668 --- /dev/null +++ b/test/functional/resources/wiremock/mappings/favicon.json @@ -0,0 +1,12 @@ +{ + "request": { + "urlPath": "/favicon.ico", + "method": "GET" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "image/png" + } + } +} \ No newline at end of file diff --git a/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy b/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy index f7d8cbaca..14ea06aab 100644 --- a/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy +++ b/test/unit/au/org/ala/merit/ManagementUnitControllerSpec.groovy @@ -329,6 +329,7 @@ class ManagementUnitControllerSpec extends Specification { } def "The management unit controller supports the display of program outcomes that are targeted by projects in that management unit"() { + setup: String managementUnitId = 'mu1' userService.getUser() >> [userId:'u1'] managementUnitService.get(managementUnitId) >> [managementUnitId:managementUnitId, name:"test"] @@ -336,17 +337,22 @@ class ManagementUnitControllerSpec extends Specification { managementUnitService.getProjects(managementUnitId) >> [projects:[[projectId:'p1', programId:"program1"]]] managementUnitService.serviceScores(managementUnitId, _, _) >> [:] projectService.getPrimaryOutcome(_) >> "Outcome 1" - programService.get("program1") >> [[programId:'program1', outcomes:[[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"]]]] + projectService.getSecondaryOutcomes(_) >> ["Outcome 2", "Outcome 4"] + Map program = [programId:'program1'] + programService.get(["program1"]) >> [program] when: Map model = controller.index(managementUnitId) then: + 1 * programService.getPrimaryOutcomes(program) >> [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 3", shortDescription:"o3", type:"primary"]] + 1 * programService.getSecondaryOutcomes(program) >> [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 4", shortDescription:"o4", type:"secondary"]] 1 * userService.canEditManagementUnitBlog("u1", managementUnitId) >> true 1 * userService.canUserEditManagementUnit("u1", managementUnitId) >> true model.content.about.displayedPrograms.size() == 1 - model.content.about.displayedPrograms[0].primaryOutcomes == [[outcome:"Outcome 1", shortDescription:"o1", targeted:true], [outcome:"Outcome 2", shortDescription:"o2"]] + model.content.about.displayedPrograms[0].primaryOutcomes == [[outcome:"Outcome 1", shortDescription:"o1", targeted:true], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 3", shortDescription:"o3"]] + model.content.about.displayedPrograms[0].secondaryOutcomes == [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2", targeted:true], [outcome:"Outcome 4", shortDescription:"o4", targeted:true]] } diff --git a/test/unit/au/org/ala/merit/ProgramServiceSpec.groovy b/test/unit/au/org/ala/merit/ProgramServiceSpec.groovy index 01bdb15d7..19f8a314c 100644 --- a/test/unit/au/org/ala/merit/ProgramServiceSpec.groovy +++ b/test/unit/au/org/ala/merit/ProgramServiceSpec.groovy @@ -157,4 +157,14 @@ class ProgramServiceSpec extends Specification { service.isInProgramHierarchy(p1, p2.programId) == false } + def "The program service can return primary and secondary outcomes for a program"() { + setup: + List outcomes = [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 3", shortDescription:"o3", type:"primary"], [outcome:"Outcome 4", shortDescription:"o4", type:"secondary"]] + Map program = [outcomes:outcomes] + + expect: + service.getPrimaryOutcomes(program) == [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 3", shortDescription:"o3", type:"primary"]] + service.getSecondaryOutcomes(program) == [[outcome:"Outcome 1", shortDescription:"o1"], [outcome:"Outcome 2", shortDescription:"o2"], [outcome:"Outcome 4", shortDescription:"o4", type:"secondary"]] + } + } diff --git a/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy b/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy index 97cff54f7..bb224759b 100644 --- a/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy +++ b/test/unit/au/org/ala/merit/ProjectServiceSpec.groovy @@ -972,6 +972,13 @@ class ProjectServiceSpec extends Specification { service.getPrimaryOutcome(null) == null } + def "The service can return the secondary outcomes for a project"() { + expect: + service.getSecondaryOutcomes([custom:[details:[outcomes:[secondaryOutcomes:[[description:'Outcome 1'], [description:'Outcome 2']]]]]]) == ['Outcome 1', 'Outcome 2'] + service.getSecondaryOutcomes([custom:[details:[:]]]) == [] + service.getSecondaryOutcomes(null) == [] + } + private Map buildApprovalDocument(int i, String projectId) { Map approval = [ dateApproved:"2019-07-01T00:00:0${i}Z", From 9c7183fa324b8bd570b3416a78e594bb5ad26942 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 12:20:02 +1000 Subject: [PATCH 09/17] Temporarily removed the PDF support for #2059 --- grails-app/views/project/_riskReporting.gsp | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/grails-app/views/project/_riskReporting.gsp b/grails-app/views/project/_riskReporting.gsp index 85576a58e..83544cca0 100644 --- a/grails-app/views/project/_riskReporting.gsp +++ b/grails-app/views/project/_riskReporting.gsp @@ -24,23 +24,25 @@
    -
    - +%{-- Commenting this out as the PDF report needs work--}% +%{--
    --}% +%{-- --}% -
    - -
    -
    +%{--
    --}% +%{-- --}% +%{--
    --}% +%{--
    --}%
    - + data-bind="click:generateRisksReportHTML">Generate Report + %{-- Commenting this out as the PDF report needs work--}% +%{-- --}%
    From aa2235ca6d879384882466198d1ad451220e10ba Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 13:00:00 +1000 Subject: [PATCH 10/17] Deleted debugging content #2052 --- grails-app/views/managementUnit/_about.gsp | 1 - 1 file changed, 1 deletion(-) diff --git a/grails-app/views/managementUnit/_about.gsp b/grails-app/views/managementUnit/_about.gsp index 4f204cf2b..5bcb00d82 100644 --- a/grails-app/views/managementUnit/_about.gsp +++ b/grails-app/views/managementUnit/_about.gsp @@ -49,7 +49,6 @@
    - ${programDetails.primaryOutcomes}
    From 7337a5b43ecf7b777d69c94a215cdd470171bd84 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 13:34:49 +1000 Subject: [PATCH 11/17] De-emphasized secondary outcomes #2052 --- grails-app/assets/stylesheets/managementUnit.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/grails-app/assets/stylesheets/managementUnit.css b/grails-app/assets/stylesheets/managementUnit.css index 86ec3410c..f7f6956e4 100644 --- a/grails-app/assets/stylesheets/managementUnit.css +++ b/grails-app/assets/stylesheets/managementUnit.css @@ -82,10 +82,19 @@ form #start-date, form #end-date { } -.outcome.targeted { +.outcomes.secondary { + font-size: small; +} + +.outcomes.primary .outcome.targeted { background: #D9EDF6; } +.outcomes.secondary .outcome.targeted { + background: #D9EDF699; +} + + #projectOverviewList th { white-space: normal; } From 978cc455f105e9dacf6431f223dcaf57a368e008 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 13:35:12 +1000 Subject: [PATCH 12/17] Deleted unused GSP fragment. --- grails-app/views/project/_admin.gsp | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 grails-app/views/project/_admin.gsp diff --git a/grails-app/views/project/_admin.gsp b/grails-app/views/project/_admin.gsp deleted file mode 100644 index d5b973775..000000000 --- a/grails-app/views/project/_admin.gsp +++ /dev/null @@ -1,10 +0,0 @@ -

    Administrator actions

    -
    -

    Edit the project details and content

    -

    Delete this project. This cannot be undone

    -
    - -

    Project Access

    - - - From e24761d13e4e87898acc4ede8d5eebf08479f001 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 13:51:22 +1000 Subject: [PATCH 13/17] Updated expected number of reports in functional test as it's now the new quarter. --- test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy b/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy index 5f055dace..a3e902baf 100644 --- a/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy +++ b/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy @@ -56,7 +56,7 @@ class RlpReportingSpec extends StubbedCasSpec { and: "The new reports are displayed" waitFor { - projectReports.reports.size() == 12 + projectReports.reports.size() == 14 projectReports.reports[1].name != "" } projectReports.reports[0].name == "Year 2018/2019 - Quarter 1 Outputs Report" From 38dbdf62e509d6ee3051e7864a4c09b72c3ba512 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 6 Jul 2020 14:08:35 +1000 Subject: [PATCH 14/17] Updated expected number of reports in functional test as it's now the new quarter. --- .../au/org/ala/fieldcapture/RlpReportingSpec.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy b/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy index a3e902baf..452b8c6a7 100644 --- a/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy +++ b/test/functional/au/org/ala/fieldcapture/RlpReportingSpec.groovy @@ -64,9 +64,9 @@ class RlpReportingSpec extends StubbedCasSpec { projectReports.reports[0].toDate == "30-09-2018" and: "The end date of the report finishing on the same day of the project is not the day before like other reports" - projectReports.reports[11].name == "Outcomes Report 2 for Project 1" - projectReports.reports[11].fromDate == "01-07-2018" - projectReports.reports[11].toDate == "01-07-2023" + projectReports.reports[13].name == "Outcomes Report 2 for Project 1" + projectReports.reports[13].fromDate == "01-07-2018" + projectReports.reports[13].toDate == "01-07-2023" } From 5e78f9ed5f7ab5f9ca544bea5ea3c36bd73ce746 Mon Sep 17 00:00:00 2001 From: Thakur Adhikari Date: Mon, 6 Jul 2020 16:18:30 +1000 Subject: [PATCH 15/17] #2048 GmsMapper - added functional test and fixed org search by name in project upload --- src/groovy/au/org/ala/merit/GmsMapper.groovy | 4 +- .../au/org/ala/merit/GmsMapperSpec.groovy | 93 ++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/groovy/au/org/ala/merit/GmsMapper.groovy b/src/groovy/au/org/ala/merit/GmsMapper.groovy index 41129dac8..02a1690ef 100644 --- a/src/groovy/au/org/ala/merit/GmsMapper.groovy +++ b/src/groovy/au/org/ala/merit/GmsMapper.groovy @@ -203,7 +203,7 @@ class GmsMapper { def organisation Map abnLookup if (project.organisationName || project.abn){ - organisation = organisations.find{ it.abn == project.abn || it.organisationName == project.organisationName} + organisation = organisations.find{ it.abn == project.abn || it.name == project.organisationName} if (organisation){ project.organisationId = organisation.organisationId }else { @@ -215,7 +215,7 @@ class GmsMapper { }else{ abnLookup = abnLookupService.lookupOrganisationNameByABN(abn) if (abnLookup){ - organisation = organisations.find{it.organisationName == abnLookup.entityName} + organisation = organisations.find{it.name == abnLookup.entityName} if (organisation){ project.organisationId = organisation.organisationId }else{ diff --git a/test/unit/au/org/ala/merit/GmsMapperSpec.groovy b/test/unit/au/org/ala/merit/GmsMapperSpec.groovy index cabe27513..72e20ed3e 100644 --- a/test/unit/au/org/ala/merit/GmsMapperSpec.groovy +++ b/test/unit/au/org/ala/merit/GmsMapperSpec.groovy @@ -14,7 +14,7 @@ import spock.lang.Specification class GmsMapperSpec extends Specification { def gmsMapper = new GmsMapper() - + AbnLookupService abnLookupService = Mock(AbnLookupService) def scores = [ [scoreId:'1', label:'Area of revegetation works (Ha)', units:'Ha', externalId:'RVA', isOutputTarget:true], [scoreId:'2', label:'Number of plants planted', units:'', externalId:'RVN', isOutputTarget:true], @@ -26,8 +26,7 @@ class GmsMapperSpec extends Specification { def setup() { activitiesModel = JSON.parse(new InputStreamReader(getClass().getResourceAsStream('/resources/activities-model.json'))) Map programModel = [programs:[[name:'Green Army']]] - List organisations = [[name:'Test org 1', abn:'12345678901']] - AbnLookupService abnLookupService = Mock(AbnLookupService) + List organisations = [[ organisationId: "123", name:'Test org 1', abn:'12345678901']] gmsMapper = new GmsMapper(activitiesModel, programModel, organisations, abnLookupService,scores) } @@ -272,4 +271,92 @@ class GmsMapperSpec extends Specification { !result.errors } + def "Organisation can be map using organisation name"(){ + setup: + Map projectData = [APP_ID:'g1', ORG_TRADING_NAME:'Test org 1', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + result.errors[1].toString() != "No organisation exists with organisation name Test org 1" + + } + + def "An Error will raised organisation unable to map using organisation name"(){ + setup: + Map projectData = [APP_ID:'g1', ORG_TRADING_NAME:'Test org 2', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + result.errors[1].toString() == "No organisation exists with organisation name Test org 2" + } + + + def "organisation to map using abn lookup"(){ + setup: + Map projectData = [APP_ID:'g1', ABN: '12345678900', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + String abn = "12345678900" + Map abnValue = [abn:"12345678900", entityName:"Test org 1"] + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + 1 * abnLookupService.lookupOrganisationNameByABN(abn) >> abnValue + + and: + result.errors[1].toString() != "12345678900 is invalid. Please Enter the correct one" + } + + def "An error is raise when abn number is not provided"(){ + setup: + Map projectData = [APP_ID:'g1', ABN: '12345678900', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + String abn = "12345678900" + Map abnValue = null + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + 1 * abnLookupService.lookupOrganisationNameByABN(abn) >> abnValue + + and: + result.errors[1].toString() == "12345678900 is invalid. Please Enter the correct one" + } + + def "An error is raise when abn number return the empty map"(){ + setup: + Map projectData = [APP_ID:'g1', ABN: '12345678900', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + String abn = "12345678900" + Map abnValue = [abn: "", entityName: ""] + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + 1 * abnLookupService.lookupOrganisationNameByABN(abn) >> abnValue + + and: + result.errors[1].toString() == "12345678900 is invalid abn number. Please Enter the correct one" + } + + + def "Assign entity name to the organisation "(){ + setup: + Map projectData = [APP_ID:'g1', ABN: '12345678900', START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + String abn = "12345678900" + Map abnValue = [abn: "12345678900", entityName: "Org name"] + + when: + def result = gmsMapper.mapProject([projectData]) + + then: + 1 * abnLookupService.lookupOrganisationNameByABN(abn) >> abnValue + + and: + result.errors[1].toString() != "12345678900 is invalid abn number. Please Enter the correct one" + } } From e4381cbba48490c1901e3c1e287b9fb9584b0fd8 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 7 Jul 2020 11:10:52 +1000 Subject: [PATCH 16/17] Removed "PDF" from explanatory text #2059 --- grails-app/views/project/_riskReporting.gsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/views/project/_riskReporting.gsp b/grails-app/views/project/_riskReporting.gsp index 83544cca0..509a48ac8 100644 --- a/grails-app/views/project/_riskReporting.gsp +++ b/grails-app/views/project/_riskReporting.gsp @@ -2,7 +2,7 @@

    Changes to risks and threats

    -

    Select the date range over which to view the changes to the project risks and threats then press "Generate Report (PDF)" button

    +

    Select the date range over which to view the changes to the project risks and threats then press "Generate Report" button


    From 6e85595eb08a651fddac97af2d44c7998097c27c Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 7 Jul 2020 11:17:11 +1000 Subject: [PATCH 17/17] Corrected explanatory text again... #2059 --- grails-app/views/project/_riskReporting.gsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/views/project/_riskReporting.gsp b/grails-app/views/project/_riskReporting.gsp index 509a48ac8..cbc536bec 100644 --- a/grails-app/views/project/_riskReporting.gsp +++ b/grails-app/views/project/_riskReporting.gsp @@ -2,7 +2,7 @@

    Changes to risks and threats

    -

    Select the date range over which to view the changes to the project risks and threats then press "Generate Report" button

    +

    Select the date range over which to view the changes to the project risks and threats then press the "Generate Report" button