From 2cb280c5e85ad9bdd00487785b81d7d32fa49458 Mon Sep 17 00:00:00 2001 From: chrisala Date: Wed, 12 Mar 2025 10:51:16 +1100 Subject: [PATCH 1/7] Allow updates to projects via project load mechanism #3466 --- .../au/org/ala/merit/ImportService.groovy | 8 +- grails-app/views/admin/import.gsp | 4 +- .../groovy/au/org/ala/merit/GmsMapper.groovy | 110 +++++++++++------- .../au/org/ala/merit/GmsMapperSpec.groovy | 2 +- .../au/org/ala/merit/ImportServiceSpec.groovy | 6 +- 5 files changed, 78 insertions(+), 52 deletions(-) diff --git a/grails-app/services/au/org/ala/merit/ImportService.groovy b/grails-app/services/au/org/ala/merit/ImportService.groovy index f8b3cc60c..337aa61d5 100644 --- a/grails-app/services/au/org/ala/merit/ImportService.groovy +++ b/grails-app/services/au/org/ala/merit/ImportService.groovy @@ -307,8 +307,7 @@ class ImportService { mu?.managementUnitId } refreshOrganisationList() - def mapper = new GmsMapper(metadataService.activitiesModel(), metadataService.programsModel(), organisations, abnLookupService, metadataService.getOutputTargetScores(), programs, managementUnits) - + def mapper = new GmsMapper(metadataService.activitiesModel(), metadataService.programsModel(), organisations, abnLookupService, metadataService.getOutputTargetScores(), programs, managementUnits, false, update) def action = preview?{rows -> mapProjectRows(rows, status, mapper, update)}:{rows -> importAll(rows, status, mapper, update)} Map result = [:] @@ -360,7 +359,7 @@ class ImportService { def mapProjectRows(projectRows, List status, GmsMapper mapper, Boolean update) { - Map mappingResults = mapper.mapProject(projectRows) + Map mappingResults = mapper.mapProject(projectRows, update) String grantId = mappingResults.project.grantId String externalId = mappingResults.project.externalId @@ -394,9 +393,6 @@ class ImportService { def editorEmail = projectDetails.project.remove('editorEmail') def editorEmail2 = projectDetails.project.remove('editorEmail2') - //When projects are loaded into MERIT via CSV upload, they are given a status of "Application". - projectDetails.project.status ?: 'application' - // Create the organisation first so we can link it to the project. if (projectDetails.organisation) { Map orgCreationResult = organisationService.update(null, projectDetails.organisation) diff --git a/grails-app/views/admin/import.gsp b/grails-app/views/admin/import.gsp index fad01a314..5df7ef77e 100644 --- a/grails-app/views/admin/import.gsp +++ b/grails-app/views/admin/import.gsp @@ -34,9 +34,7 @@

- Please note this function will replace all project information for each project.
- It is designed for use with grants hub data and to fix newly loaded projects. Please test changes - to existing MERIT projects in the staging system before running the update in production. + Please test changes to existing MERIT projects in the staging system before running the update in production.

diff --git a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy index 1e58bd763..8a8fd4f59 100644 --- a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy +++ b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy @@ -2,7 +2,7 @@ package au.org.ala.merit import au.com.bytecode.opencsv.CSVWriter import org.apache.commons.lang.WordUtils -import org.apache.commons.validator.EmailValidator +import org.apache.commons.validator.routines.EmailValidator import java.text.DecimalFormat import java.text.SimpleDateFormat @@ -95,6 +95,7 @@ class GmsMapper { def geographicInfoMapping = [ NATIONWIDE:[name:'nationwide', type:'boolean', description:'If true, this project does not have a primary state'], + STATEWIDE:[name:'statewide', type:'boolean', description:'If true, this project does not have a primary electorate'], PRIMARY_STATE:[name:'primaryState', type:'string', description:'The primary state to be manually assigned to this project'], PRIMARY_ELECTORATE:[name:'primaryElectorate', type:'string', description:'The primary electorate to be manually assigned to this project'], OTHER_ELECTORATES:[name:'otherElectorates', type:'list', description:'Other electorates to be manually assigned to this project. Enter as a comma separated list'], @@ -135,6 +136,8 @@ class GmsMapper { ] private boolean includeProgress + /** If the mapping is being done to update a project, mandatory fields aren't required as they won't be updated if absent */ + private boolean mapForUpdate = false public GmsMapper() { this.activitiesModel = [] @@ -144,9 +147,10 @@ class GmsMapper { this.programs = [:] this.managementUnits = [:] includeProgress = false + mapForUpdate = false } - GmsMapper(activitiesModel, programModel, organisations, abnLookup, List scores, Map programs = [:], Map managementUnits = [:], includeProgress = false) { + GmsMapper(activitiesModel, programModel, organisations, abnLookup, List scores, Map programs = [:], Map managementUnits = [:], includeProgress = false, mapForUpdate = false) { this.activitiesModel = activitiesModel this.programModel = programModel this.includeProgress = includeProgress @@ -155,6 +159,7 @@ class GmsMapper { this.programs = programs this.managementUnits = managementUnits this.abnLookupService = abnLookup + this.mapForUpdate = mapForUpdate } /** Creates a CSV file in the format required to import projects into MERIT with additional instructions */ @@ -222,29 +227,21 @@ class GmsMapper { return errors } - def mapProject(projectRows) { + def mapProject(projectRows, boolean update = false) { List errors = [] List messages = [] - Map result = gmsToMerit(projectRows[0], projectMapping) // All project rows have the project details. + Map result = csvToProjectProperties(projectRows[0], projectMapping, update) // All project rows have the project details. def project = result.mappedData - project.projectType = 'works' - project.isMERIT = true + if (!update) { + // Set some default project properties for the MERIT hub + project.projectType = 'works' + project.isMERIT = true + } - mapGeographicInfo(projectRows[0], project, errors) + mapGeographicInfo(projectRows[0], project, update, errors) - - String programName = project.associatedSubProgram ?: project.associatedProgram - Map program = programs[programName] - String programId = program?.programId - if (programId) { - project.remove('associatedProgram') - project.remove('associatedSubProgram') - project.programId = programId - } - else { - errors << "Program ${programName} doesn't match an existing MERIT programme" - } + mapProgram(project, update, errors) if (project.managementUnitName) { String managementUnitName = project.remove('managementUnitName') @@ -254,10 +251,9 @@ class GmsMapper { } } - Map organisation = mapOrganisation(project, program, errors, messages) + Map organisation = mapOrganisation(project, update, errors, messages) errors.addAll(result.errors) - project.planStatus = project.planStatus ?: 'not approved' mapRisks(projectRows, project, errors) @@ -270,10 +266,36 @@ class GmsMapper { project.custom = [details:meriPlan] } + if (update) { + Set propertiesToUpdate = new HashSet(project.keySet()) + propertiesToUpdate.remove('grantId') // This identifies the project and won't be updated. + messages << "Update: ${propertiesToUpdate}" + } + [project:project, sites:sites, activities:activities, errors:errors, messages:messages, organisation:organisation] } + private Map mapProgram(LinkedHashMap project, boolean update, ArrayList errors) { + String programName = project.associatedSubProgram ?: project.associatedProgram + if (!programName) { + if (!update) { + errors << "Please supply a program name (PROGRAM_NM) or sub-program name (ROUND_NM) for the project" + } + return + } + Map program = programs[programName] + String programId = program?.programId + if (programId) { + project.remove('associatedProgram') + project.remove('associatedSubProgram') + project.programId = programId + } else { + errors << "Program ${programName} doesn't match an existing MERIT programme" + } + program + } + private Map findExistingOrganisation(String organisationId, String abn) { organisations.find{ (abn && (it.abn == abn)) || @@ -281,7 +303,7 @@ class GmsMapper { } - private Map mapOrganisation(Map project, Map program, List errors, List messages) { + private Map mapOrganisation(Map project, boolean update, List errors, List messages) { Map organisation Map abnLookup String error = null @@ -339,7 +361,7 @@ class GmsMapper { project.associatedOrgs = [ [organisationId:organisation.organisationId, name: contractName ?: organisation.name, description:description]] } - } else { + } else if (!update) { error = "Please supply an organisationId (ORG_ID) or ABN (ABN) for the project" } if (error) { @@ -395,7 +417,7 @@ class GmsMapper { def count = siteRows.size() siteRows.eachWithIndex {siteRow, i -> - def siteResult = gmsToMerit(siteRow, siteMapping) + def siteResult = csvToProjectProperties(siteRow, siteMapping) def site = siteResult.mappedData errors.addAll(siteResult.errors) @@ -432,7 +454,7 @@ class GmsMapper { def activityRows = projectRows.findAll{it[DATA_TYPE_COLUMN] == ACTIVITY_DATA_TYPE && it[DATA_SUB_TYPE_COLUMN] == ACTIVITY_DATA_SUB_TYPE} def activities = [] activityRows.eachWithIndex { activityRow, i -> - def activityResult = gmsToMerit(activityRow, activityMapping) + def activityResult = csvToProjectProperties(activityRow, activityMapping) def mappedActivity = activityResult.mappedData errors.addAll(activityResult.errors) @@ -526,7 +548,7 @@ class GmsMapper { def risks = [] riskRows.eachWithIndex { riskRow, i -> - def result = gmsToMerit(riskRow, riskMapping) + def result = csvToProjectProperties(riskRow, riskMapping) errors.addAll(result.errors) def risk = [ "threat" : "", @@ -546,19 +568,19 @@ class GmsMapper { } } - private void mapGeographicInfo(Map rowData, Map project, List errors) { + private void mapGeographicInfo(Map rowData, Map project, boolean update, List errors) { - Map result = gmsToMerit(rowData, geographicInfoMapping) + Map result = csvToProjectProperties(rowData, geographicInfoMapping, update) if (result.mappedData) { project.geographicInfo = result.mappedData } - errors.addAll(errors) + errors.addAll(result.errors) } private def mapTarget(rowMap) { def errors = [] - def map = gmsToMerit(rowMap, outputTargetColumnMapping) + def map = csvToProjectProperties(rowMap, outputTargetColumnMapping) def target = map.mappedData errors.addAll(map.errors) @@ -594,7 +616,7 @@ class GmsMapper { [mappedData:result, errors: errors] } - private def gmsToMerit(Map rowMap, Map mapping) { + private def csvToProjectProperties(Map rowMap, Map mapping, boolean update = false) { def result = [:] def errors = [] mapping.each { Map.Entry e -> @@ -605,16 +627,20 @@ class GmsMapper { String propertyName = columnMapping.name if (columnMapping.multipleColumnsSupported) { List values = findMultiValue(mappingColumnName, rowMap) - if (!result[propertyName]) { + if (!result[propertyName] && !update) { result[propertyName] = [] } values = values.findAll{it} - result[propertyName].addAll(values.collect{convertByType(it, columnMapping)}.findAll{it}) + if (values || !update) { + result[propertyName].addAll(values.collect{convertByType(it, columnMapping, update)}.findAll{it}) + } } else { - result[propertyName] = convertByType(rowMap[mappingColumnName], columnMapping) + def value = rowMap[mappingColumnName]?.trim() + if (value || !update) { // If we are doing an update, ignore empty values. + result[propertyName] = convertByType(rowMap[mappingColumnName], columnMapping, update) + } } - } catch (Exception ex) { errors << "Error converting value: ${rowMap[mappingColumnName]} from row ${rowMap.index} column: ${mappingColumnName}, ${ex.getMessage()}" @@ -627,22 +653,25 @@ class GmsMapper { private List findMultiValue(String mappingColumnName, Map rowMap) { rowMap.findAll { String key, def value -> key.startsWith(mappingColumnName) - }.collect{it.value} + }.collect{it.value?.trim()} } - private def convertByType(String value, Map mapping) { + private def convertByType(String value, Map mapping, boolean update) { String type = mapping.type value = value?value.trim():'' def result = null switch (type) { case 'date': - result = convertDate(value, mapping.mandatory) + result = convertDate(value, mapping.mandatory && !update) break case 'decimal': result = convertDecimal(value) break case 'string': - result = value ?: (mapping['default']?:value) + result = value + if (!result && !update) { + result = mapping['default']?:value + } break case 'url': URI.create(value) // validation purposes only @@ -669,6 +698,9 @@ class GmsMapper { case 'list': result = value?.split(',').collect{it.trim()}.findAll{it} break + case 'boolean': + result = value?.toBoolean() // accept "y"/"true"/"1" as true + break default: throw new IllegalArgumentException("Unsupported type: ${type}") } diff --git a/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy b/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy index 2ecd6d5e6..9cbda64cd 100644 --- a/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy +++ b/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy @@ -30,7 +30,7 @@ class GmsMapperSpec extends Specification{ activitiesModel = JSON.parse(new InputStreamReader(getClass().getResourceAsStream('/activities-model.json'))) Map programModel = [programs:[[name:'Green Army']]] List organisations = [[ organisationId: "123", name:'Test org 1', abn:'12345678901'], [organisationId:'2', name:"Org 2", abn:""]] - gmsMapper = new GmsMapper(activitiesModel, programModel, organisations, abnLookupService, scores, programs) + gmsMapper = new GmsMapper(activitiesModel, programModel, organisations, abnLookupService, scores, programs, [:], false, false) } /** diff --git a/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy b/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy index 298ee7847..6f2ff5cea 100644 --- a/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy +++ b/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy @@ -114,7 +114,7 @@ class ImportServiceSpec extends Specification implements ServiceUnitTest Date: Fri, 14 Mar 2025 08:55:04 +1100 Subject: [PATCH 2/7] Fixing functional test that started failing #3476 --- .../groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy | 3 ++- .../groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy b/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy index f3a586c1b..f83d2a156 100644 --- a/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy +++ b/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy @@ -11,13 +11,14 @@ import pages.RlpProjectPage @Slf4j class ImportProjectsSpec extends StubbedCasSpec { - def setup() { + def setupSpec() { useDataSet('dataset2') loginAsAlaAdmin(browser) to AdminTools clearMetadata() to AdminClearCachePage clearProgramListCache() + at AdminClearCachePage // reset at check time clearServiceListCache() } diff --git a/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy b/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy index c00bc3430..1dd160a46 100644 --- a/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy +++ b/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy @@ -201,7 +201,7 @@ class StubbedCasSpec extends FieldcaptureFunctionalTest { private String loggedInUser = null def login(Map userDetails, Browser browser) { - if (loggedInUser != userDetails.userId) { + if (loggedInUser && loggedInUser != userDetails.userId) { logout(browser) } oidcLogin(userDetails, browser) From c548c176a2c7539e59b2d50e190d0ceee7b533a6 Mon Sep 17 00:00:00 2001 From: chrisala Date: Fri, 14 Mar 2025 10:29:57 +1100 Subject: [PATCH 3/7] Fixing functional test that started failing #3476 --- .../groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy b/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy index 1dd160a46..c00bc3430 100644 --- a/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy +++ b/src/integration-test/groovy/au/org/ala/fieldcapture/StubbedCasSpec.groovy @@ -201,7 +201,7 @@ class StubbedCasSpec extends FieldcaptureFunctionalTest { private String loggedInUser = null def login(Map userDetails, Browser browser) { - if (loggedInUser && loggedInUser != userDetails.userId) { + if (loggedInUser != userDetails.userId) { logout(browser) } oidcLogin(userDetails, browser) From d0012023e6a9df1cd29ab19f038b08f799180e87 Mon Sep 17 00:00:00 2001 From: chrisala Date: Fri, 14 Mar 2025 16:04:34 +1100 Subject: [PATCH 4/7] Update project override settings for ones missed with isDefault #1078 --- .../adhoc/migrateProjectOverrideSettings.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/scripts/releases/4.3/adhoc/migrateProjectOverrideSettings.js diff --git a/src/main/scripts/releases/4.3/adhoc/migrateProjectOverrideSettings.js b/src/main/scripts/releases/4.3/adhoc/migrateProjectOverrideSettings.js new file mode 100644 index 000000000..cbc51e720 --- /dev/null +++ b/src/main/scripts/releases/4.3/adhoc/migrateProjectOverrideSettings.js @@ -0,0 +1,20 @@ +load('../../../utils/audit.js'); +let projects = db.project.find({$or:[{'geographicInfo.primaryState':{$ne:null}}, {'geographicInfo.primaryElectorate':{$ne:null}}]}); +while (projects.hasNext()) { + let project = projects.next(); + let changed = false; + if (project.geographicInfo.primaryState && !project.geographicInfo.overridePrimaryState) { + project.geographicInfo.overridePrimaryState = true; + changed = true; + } + if (project.geographicInfo.primaryElectorate && !project.geographicInfo.overridePrimaryElectorate) { + project.geographicInfo.overridePrimaryElectorate = true; + changed = true; + } + if (changed) { + print("Updating project " + project.projectId); + db.project.updateOne({projectId: project.projectId}, {$set: {geographicInfo: project.geographicInfo}}); + audit(project, project.projectId, 'au.org.ala.ecodata.Project', "system"); + } + +} \ No newline at end of file From 12348c59c92afc339473e2711b0ddcf0b2eac3cb Mon Sep 17 00:00:00 2001 From: chrisala Date: Wed, 19 Mar 2025 10:04:52 +1100 Subject: [PATCH 5/7] Added test, refactored function name improved update message #3466 --- .../au/org/ala/merit/AdminController.groovy | 2 +- .../au/org/ala/merit/ImportService.groovy | 6 +- .../fieldcapture/ImportProjectsSpec.groovy | 59 +++++++++++++++++++ .../groovy/pages/ProjectImport.groovy | 14 ++++- .../groovy/au/org/ala/merit/GmsMapper.groovy | 17 +++++- .../au/org/ala/merit/ImportServiceSpec.groovy | 4 +- 6 files changed, 94 insertions(+), 8 deletions(-) diff --git a/grails-app/controllers/au/org/ala/merit/AdminController.groovy b/grails-app/controllers/au/org/ala/merit/AdminController.groovy index a8d54a126..f1c5f23ab 100644 --- a/grails-app/controllers/au/org/ala/merit/AdminController.groovy +++ b/grails-app/controllers/au/org/ala/merit/AdminController.groovy @@ -418,7 +418,7 @@ class AdminController { session.status = status def fileIn = new FileInputStream(file) try { - def result = importService.gmsImport(fileIn, status.projects, preview, update) + def result = importService.projectImport(fileIn, status.projects, preview, update) status.finished = true status.error = result.error } diff --git a/grails-app/services/au/org/ala/merit/ImportService.groovy b/grails-app/services/au/org/ala/merit/ImportService.groovy index 337aa61d5..ca9e4a0fb 100644 --- a/grails-app/services/au/org/ala/merit/ImportService.groovy +++ b/grails-app/services/au/org/ala/merit/ImportService.groovy @@ -297,7 +297,7 @@ class ImportService { organisations += metadataService.organisationList()?.list } - Map gmsImport(InputStream csv, List status, Boolean preview, Boolean update, String charEncoding = 'Cp1252') { + Map projectImport(InputStream csv, List status, Boolean preview, Boolean update, String charEncoding = 'Cp1252') { Map programs = [:].withDefault{name -> programService.getByName(name) @@ -379,7 +379,7 @@ class ImportService { void importAll(projectRows, List status, GmsMapper mapper, Boolean update) { - def projectDetails = mapper.mapProject(projectRows) + def projectDetails = mapper.mapProject(projectRows, update) def grantId = projectDetails.project.grantId?:'' def externalId = projectDetails.project.externalId?:'' @@ -405,7 +405,7 @@ class ImportService { } } - def result = importProject(projectDetails.project, update) // Do not overwrite existing projects because of the impacts to sites / activities etc. + def result = importProject(projectDetails.project, update) if (result.project == 'existing' && !update) { status << [grantId:grantId, externalId:externalId, success:false, errors:['Project already exists in MERIT, skipping']] diff --git a/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy b/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy index f83d2a156..12fef4bc9 100644 --- a/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy +++ b/src/integration-test/groovy/au/org/ala/fieldcapture/ImportProjectsSpec.groovy @@ -49,6 +49,7 @@ class ImportProjectsSpec extends StubbedCasSpec { and: List rows2 = projectResults() rows2.size() == 2 + rows2[1].success == 'Yes' when: to Organisation, 'test_organisation' @@ -75,6 +76,8 @@ class ImportProjectsSpec extends StubbedCasSpec { and: "The data is relevant to the projects loaded" List rows = projectResults() rows.size() == 3 + rows[1].success == 'Yes' + rows[2].success == 'Yes' when: importProjects() @@ -125,6 +128,7 @@ class ImportProjectsSpec extends StubbedCasSpec { and: List rows2 = projectResults() rows2.size() == 2 + rows2[1].success == 'Yes' when: "We navigate to the program page to find the new imported project, then open it" to ProgramPage, 'configurable_meri_plan' @@ -152,4 +156,59 @@ class ImportProjectsSpec extends StubbedCasSpec { adminContent.meriPlan.budget[0].budgetAmounts()*.value() == ["20000", "10000"] } + + def "Projects can be updated via import with only the fields provided in the spreadsheet being updated"() { + + setup: + File csv = new File(getClass().getResource("/grants-hub-update-data.csv").toURI()) + loginAsMeritAdmin(browser) + + when: + to ProjectImport + checkUpdateCheckbox() + attachFile(csv) + + then: "The projects are validated and the validation results are displayed" + waitFor{validateComplete()} + + and: "The data is relevant to the projects loaded" + projectResults().size() == 2 + + when: + importProjects() + + then: + waitFor{loadComplete()} + and: + List rows2 = projectResults() + rows2.size() == 2 + rows2[1].success == 'Yes' + + when: "We navigate to the program page to find the new imported project, then open it" + to ProgramPage, 'configurable_meri_plan' + openProjectByGrantId('cep-1') + + then: + at RlpProjectPage + + when: + displayOverview() + + then: + overview.program.text() == "Configurable MERI Plan Program" + overview.projectId.text() == "cep-1" + overview.status.text().equalsIgnoreCase("Active") + overview.externalIds*.text() == ["1234"] + overview.description.text() == "Grants project description - updated" + + when: + openMeriPlanEditTab() + + then: + adminContent.meriPlan.budget.size() == 1 + adminContent.meriPlan.budget[0].description.value() == "Project funding" + adminContent.meriPlan.budget[0].budgetAmounts()*.value() == ["1", "2"] + + } + } diff --git a/src/integration-test/groovy/pages/ProjectImport.groovy b/src/integration-test/groovy/pages/ProjectImport.groovy index 67aa11223..ab7cde215 100644 --- a/src/integration-test/groovy/pages/ProjectImport.groovy +++ b/src/integration-test/groovy/pages/ProjectImport.groovy @@ -1,6 +1,7 @@ package pages import geb.Page +import geb.module.Checkbox class ProjectImport extends Page { @@ -12,6 +13,7 @@ class ProjectImport extends Page { fileInput { $('#fileUpload') } importButton { $('button[data-bind*=doImport]')} progressSummary { $('span[data-bind*=progressSummary]') } + updateCheckbox { $('#update').module(Checkbox) } } def attachFile(File file) { @@ -22,7 +24,13 @@ class ProjectImport extends Page { importButton.click() } + def checkUpdateCheckbox() { + updateCheckbox.check() + } + List projectResults() { + + List columns = ["grantId", "externalId", "success", "errors", "messages"] List rows = [] def progressTable = $('table.table') progressTable.find("tbody tr").each { @@ -30,7 +38,11 @@ class ProjectImport extends Page { it.find("td").each { col -> cols << col.text() } - rows << cols + Map row = [:] + columns.eachWithIndex{ col, index -> + row[col] = cols[index] + } + rows << row } rows diff --git a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy index 8a8fd4f59..e575f3268 100644 --- a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy +++ b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy @@ -267,15 +267,30 @@ class GmsMapper { } if (update) { - Set propertiesToUpdate = new HashSet(project.keySet()) + Map flatProperties = flattenMap(project) + Set propertiesToUpdate = flatProperties.keySet() propertiesToUpdate.remove('grantId') // This identifies the project and won't be updated. messages << "Update: ${propertiesToUpdate}" + } [project:project, sites:sites, activities:activities, errors:errors, messages:messages, organisation:organisation] } + private Map flattenMap(Map map, String separator = '.') { + map.collectEntries { k, v -> + if (v instanceof Map) { + flattenMap(v, separator).collectEntries { k1, v1 -> + String newKey = k+separator+k1 + [(newKey): v1] + } + } else { + [(k): v] + } + } + } + private Map mapProgram(LinkedHashMap project, boolean update, ArrayList errors) { String programName = project.associatedSubProgram ?: project.associatedProgram if (!programName) { diff --git a/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy b/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy index 6f2ff5cea..e453557a0 100644 --- a/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy +++ b/src/test/groovy/au/org/ala/merit/ImportServiceSpec.groovy @@ -165,7 +165,7 @@ class ImportServiceSpec extends Specification implements ServiceUnitTest> [list:[[name:"Test Organisation 2", organisationId:'org2Id', abn:'12345678901']]] @@ -190,7 +190,7 @@ class ImportServiceSpec extends Specification implements ServiceUnitTest> [list:[[name:"Test Organisation 2", organisationId:'org2Id', abn:'12345678901']]] From 46400e0f72335dfc3d2fd37f974021997d998809 Mon Sep 17 00:00:00 2001 From: chrisala Date: Wed, 19 Mar 2025 10:50:24 +1100 Subject: [PATCH 6/7] Fixed updating of multi columns values #3466 --- src/main/groovy/au/org/ala/merit/GmsMapper.groovy | 4 ++-- .../groovy/au/org/ala/merit/GmsMapperSpec.groovy | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy index e575f3268..b3580394b 100644 --- a/src/main/groovy/au/org/ala/merit/GmsMapper.groovy +++ b/src/main/groovy/au/org/ala/merit/GmsMapper.groovy @@ -642,10 +642,10 @@ class GmsMapper { String propertyName = columnMapping.name if (columnMapping.multipleColumnsSupported) { List values = findMultiValue(mappingColumnName, rowMap) - if (!result[propertyName] && !update) { + values = values.findAll{it} + if (!result[propertyName] && (values || !update)) { result[propertyName] = [] } - values = values.findAll{it} if (values || !update) { result[propertyName].addAll(values.collect{convertByType(it, columnMapping, update)}.findAll{it}) } diff --git a/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy b/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy index 9cbda64cd..258ecc811 100644 --- a/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy +++ b/src/test/groovy/au/org/ala/merit/GmsMapperSpec.groovy @@ -412,4 +412,19 @@ class GmsMapperSpec extends Specification{ [idType:'GRANT_AWARD', externalId: 'g1'], [idType:'GRANT_AWARD', externalId: 'g2']] !result.errors } + + def "The GMSMapper can update multi-column values such as the externalIds"() { + when: + Map project = [APP_ID:'g1', PROGRAM_NM:"Green Army", ORG_TRADING_NAME:'Test org 1', ABN:'12345678901', FUNDING_TYPE:"RLP", START_DT:'2019/07/01', FINISH_DT:'2020/07/01'] + Map idData = [ORDER_NO:'o1', ORDER_NO_2:'o2', WORK_ORDER_ID:'w1', GRANT_AWARD_ID:'g1', GRANT_AWARD_ID_2:'g2', TECH_ONE_ID:'t1', TECH_ONE_ID_2:'t2'] + project += idData + Map result = gmsMapper.mapProject([project], true) + + then: + result.project.externalIds == [[idType:'INTERNAL_ORDER_NUMBER', externalId: 'o1'], [idType:'INTERNAL_ORDER_NUMBER', externalId: 'o2'], + [idType:'TECH_ONE_CODE', externalId: 't1'], [idType:'TECH_ONE_CODE', externalId: 't2'], + [idType:'WORK_ORDER', externalId: 'w1'], + [idType:'GRANT_AWARD', externalId: 'g1'], [idType:'GRANT_AWARD', externalId: 'g2']] + !result.errors + } } From 1b2064230e685ede9c98b68f9e3839941d879c91 Mon Sep 17 00:00:00 2001 From: chrisala Date: Wed, 19 Mar 2025 11:16:18 +1100 Subject: [PATCH 7/7] Added test file missed in previous commit #3466 --- src/integration-test/resources/grants-hub-update-data.csv | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/integration-test/resources/grants-hub-update-data.csv diff --git a/src/integration-test/resources/grants-hub-update-data.csv b/src/integration-test/resources/grants-hub-update-data.csv new file mode 100644 index 000000000..66c5c3b95 --- /dev/null +++ b/src/integration-test/resources/grants-hub-update-data.csv @@ -0,0 +1,2 @@ +APP_ID,EXTERNAL_ID,APP_NM,APP_DESC,PROGRAM_NM,ABN,ORG_ID,START_DT,FINISH_DT,ORDER_NO,FUNDING,AUTHORISEDP_EMAIL,GRANT_MGR_EMAIL,GRANT_MGR_EMAIL_2,APPLICANT_EMAIL,ADMIN_EMAIL,EDITOR_EMAIL,EDITOR_EMAIL_2,TAGS,PROJECT_STATUS,MERI_PLAN_STATUS,FUNDING_TYPE,ORIGIN_SYSTEM,FINANCIAL_YEAR_FUNDING_DESCRIPTION,FUNDING_21_22,FUNDING_22_23 +cep-1,,,Grants project description - updated,,,,,,,,,,,,,,,,Active,,,,Project funding,1,2 \ No newline at end of file