Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent import of duplicate COCs #19101

Draft
wants to merge 30 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7fad320
Prevent import of duplicate COCs
jason-p-pickering Nov 8, 2024
8320c5d
Fix code smells
jason-p-pickering Nov 8, 2024
7b4a904
Linting
jason-p-pickering Nov 8, 2024
f293278
feat: Update error code & remove redundant equals method
david-mackessy Nov 8, 2024
09a1221
Merge branch 'master' into duplicate-cocs-prevent-import
david-mackessy Nov 8, 2024
2ec7568
Rework some code
jason-p-pickering Nov 10, 2024
f4380dd
Fix code smells
jason-p-pickering Nov 11, 2024
52c4050
Minor
jason-p-pickering Nov 11, 2024
bd13939
Partially fix test
jason-p-pickering Nov 11, 2024
ce0b902
Fix/disable more tests
jason-p-pickering Nov 11, 2024
1ae338e
Add additional checks for COCs
jason-p-pickering Nov 11, 2024
aed074e
Linting
jason-p-pickering Nov 11, 2024
1ef9b79
Update tests
jason-p-pickering Nov 12, 2024
3191aba
Linting
jason-p-pickering Nov 12, 2024
2f3a45c
Rework tests
jason-p-pickering Nov 13, 2024
db6ace7
Rework methods a bit
jason-p-pickering Nov 13, 2024
7455160
Linting
jason-p-pickering Nov 13, 2024
2f0e00b
Fix various issues
jason-p-pickering Nov 18, 2024
ab8615d
Linting
jason-p-pickering Nov 18, 2024
5e6aaed
More sonar cube stuff
jason-p-pickering Nov 18, 2024
630a9e3
Linting
jason-p-pickering Nov 18, 2024
04d06e7
More sonar cube
jason-p-pickering Nov 18, 2024
169b266
Merge branch 'master' of github.com:dhis2/dhis2-core into duplicate-c…
jason-p-pickering Nov 19, 2024
d704a18
fix: Remove custom COC defaults
enricocolasante Nov 19, 2024
40ac6b2
Remove custom default
jason-p-pickering Nov 19, 2024
c570fb7
Linting
jason-p-pickering Nov 19, 2024
0a3dea4
Merge branch 'remove_coc_custom_default' of github.com:dhis2/dhis2-co…
jason-p-pickering Nov 19, 2024
654ea8c
Try and fix tracker test
jason-p-pickering Nov 19, 2024
65730b1
Linting
jason-p-pickering Nov 19, 2024
f854eae
Try and fix tracker tests
jason-p-pickering Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public enum ErrorCode {
E1120("Update cannot be applied as it would make existing data values inaccessible"),
E1121("Data element `{0}` value type cannot be changed as it has associated data values"),

E1122("Category option combo {0} already exists for category combo {1}"),
jason-p-pickering marked this conversation as resolved.
Show resolved Hide resolved
E1123("Category option combo {0} must be associated with a category combo"),
E1124("Category option combo {0} cannot be associated with the default category combo"),

E1125("Category option combo {0} contains options not associated with category combo {1}"),
/* Org unit merge */
E1500("At least two source orgs unit must be specified"),
E1501("Target org unit must be specified"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright (c) 2004-2024, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.dxf2.metadata.objectbundle.hooks;

import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.hisp.dhis.category.CategoryCombo;
import org.hisp.dhis.category.CategoryOption;
import org.hisp.dhis.category.CategoryOptionCombo;
import org.hisp.dhis.category.CategoryService;
import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle;
import org.hisp.dhis.feedback.ErrorCode;
import org.hisp.dhis.feedback.ErrorReport;
import org.springframework.stereotype.Component;

@Component
@AllArgsConstructor
public class CategoryOptionComboObjectBundleHook
extends AbstractObjectBundleHook<CategoryOptionCombo> {
private final CategoryService categoryService;

static boolean haveEqualCatComboCatOptionReferenceIds(
CategoryOptionCombo one, CategoryOptionCombo other) {
if (one == null || other == null) {
return false;
}

if (one.getCategoryCombo() == null || other.getCategoryCombo() == null) {
return false;
}

if (one.getCategoryOptions() == null || other.getCategoryOptions() == null) {
return false;
}

if (!one.getCategoryCombo().getUid().equals(other.getCategoryCombo().getUid())) {
return false;
}

Set<String> oneCategoryOptionUids =
one.getCategoryOptions().stream().map(CategoryOption::getUid).collect(Collectors.toSet());

Set<String> otherCategoryOptionUids =
other.getCategoryOptions().stream().map(CategoryOption::getUid).collect(Collectors.toSet());

return oneCategoryOptionUids.equals(otherCategoryOptionUids);
}

private void checkDuplicateCategoryOptionCombos(
CategoryOptionCombo categoryOptionCombo,
ObjectBundle bundle,
Consumer<ErrorReport> addReports) {

if (bundle.isPersisted(categoryOptionCombo)) {
return; // Only check for duplicates if the object is not persisted
}

List<CategoryOptionCombo> categoryOptionCombos =
categoryService.getAllCategoryOptionCombos().stream()
.filter(
coc ->
coc.getCategoryCombo()
.getUid()
.equals(categoryOptionCombo.getCategoryCombo().getUid()))
.toList();

// Check if the categoryOptionCombo is already in the list. This could be an update or re-import
// of the same object.
if (categoryOptionCombos.stream()
.anyMatch(coc -> coc.getUid().equals(categoryOptionCombo.getUid()))) {
return;
}
// Check to see if the COC already exists in the list of COCs
// If it does, then it is a duplicate
for (CategoryOptionCombo existingCategoryOptionCombo : categoryOptionCombos) {
jason-p-pickering marked this conversation as resolved.
Show resolved Hide resolved
if (haveEqualCatComboCatOptionReferenceIds(categoryOptionCombo, existingCategoryOptionCombo)
&& !categoryOptionCombo.getUid().equals(existingCategoryOptionCombo.getUid())) {
addReports.accept(
new ErrorReport(
CategoryOptionCombo.class,
ErrorCode.E1122,
categoryOptionCombo.getName(),
existingCategoryOptionCombo.getName()));
}
}
}

private void checkCategoryOptionsExistInCategoryCombo(
CategoryOptionCombo categoryOptionCombo, Consumer<ErrorReport> addReports) {

Set<String> categoryOptionUids =
categoryOptionCombo.getCategoryOptions().stream()
.map(CategoryOption::getUid)
.collect(Collectors.toSet());

CategoryCombo existingCategoryCombo =
categoryService.getCategoryCombo(categoryOptionCombo.getCategoryCombo().getUid());

if (existingCategoryCombo == null) {
return;
}

Set<String> existingCategoryOptionsInCombo =
existingCategoryCombo.getCategoryOptions().stream()
.map(CategoryOption::getUid)
.collect(Collectors.toSet());

categoryOptionUids.removeAll(existingCategoryOptionsInCombo);

if (!categoryOptionUids.isEmpty()) {
addReports.accept(
new ErrorReport(
CategoryOptionCombo.class,
ErrorCode.E1125,
categoryOptionCombo.getName(),
existingCategoryCombo.getName()));
}
}

private void checkNonStandardDefaultCatOptionCombo(
CategoryOptionCombo categoryOptionCombo, Consumer<ErrorReport> addReports) {

CategoryCombo categoryCombo = categoryOptionCombo.getCategoryCombo();
CategoryCombo defaultCombo = categoryService.getDefaultCategoryCombo();
if (!categoryCombo.getUid().equals(defaultCombo.getUid())) {
return;
}

CategoryOptionCombo defaultCatOptionCombo = categoryService.getDefaultCategoryOptionCombo();

if (!categoryOptionCombo.getUid().equals(defaultCatOptionCombo.getUid())) {
addReports.accept(
new ErrorReport(
CategoryOptionCombo.class, ErrorCode.E1124, categoryOptionCombo.getName()));
}
}

@Override
public void validate(
CategoryOptionCombo categoryOptionCombo,
ObjectBundle bundle,
Consumer<ErrorReport> addReports) {

checkNonStandardDefaultCatOptionCombo(categoryOptionCombo, addReports);
checkDuplicateCategoryOptionCombos(categoryOptionCombo, bundle, addReports);
checkCategoryOptionsExistInCategoryCombo(categoryOptionCombo, addReports);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@
],
"categoryCombos": [
{

"code": "TA_CATEGORY_COMBO_ATTRIBUTE",
"name": "TA Category combo attribute",
"created": "2022-05-30T11:40:03.717",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ void testCategoryOptionIdentifiers() {

@Test
void testCategoryOptionComboIdentifiers() {
List<Pair<String, TrackerIdSchemeParam>> data = buildDataSet("XXXvX50cXC0", "COCA", "COCAname");
List<Pair<String, TrackerIdSchemeParam>> data =
buildDataSet("HllvX50cXC0", "default", "default");
for (Pair<String, TrackerIdSchemeParam> pair : data) {
String id = pair.getLeft();
TrackerIdSchemeParam param = pair.getRight();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ void testPreheatEvents() throws IOException {
assertFalse(preheat.getAll(OrganisationUnit.class).isEmpty());
assertFalse(preheat.getAll(ProgramStage.class).isEmpty());
assertFalse(preheat.getAll(CategoryOptionCombo.class).isEmpty());
assertNotNull(preheat.get(CategoryOptionCombo.class, "XXXvX50cXC0"));
assertNotNull(preheat.get(CategoryOptionCombo.class, "HllvX50cXC0"));
assertNotNull(preheat.get(CategoryOption.class, "XXXrKDKCefk"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,7 @@ void testCantWriteAccessCatCombo() throws IOException {
ImportReport importReport = trackerImportService.importTracker(params, trackerObjects);

assertHasOnlyErrors(
importReport,
ValidationCode.E1096,
ValidationCode.E1099,
ValidationCode.E1104,
ValidationCode.E1095);
importReport, ValidationCode.E1096, ValidationCode.E1104, ValidationCode.E1095);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1820,24 +1820,6 @@
"id": "xYerKDKCefk"
}
]
},
{
"lastUpdated": "2019-01-28T11:01:30.775",
"code": "COCA",
"created": "2019-01-28T11:01:30.771",
"name": "COCAname",
"id": "XXXvX50cXC0",
"ignoreApproval": false,
"categoryCombo": {
"id": "bjDvmb4bfuf"
},
"translations": [],
"attributeValues": [],
"categoryOptions": [
{
"id": "xYerKDKCefk"
}
]
}
],
"categories": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,23 +204,6 @@
"id": "xYerKDKCefk"
}
]
},
{
"lastUpdated": "2019-01-28T11:01:30.775",
"code": "COCA",
"created": "2019-01-28T11:01:30.771",
"name": "COCAname",
"id": "XXXvX50cXC0",
"ignoreApproval": false,
"categoryCombo": {
"id": "bjDvmb4bfuf"
},
"attributeValues": [],
"categoryOptions": [
{
"id": "xYerKDKCefk"
}
]
}
],
"categories": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2690,6 +2690,25 @@
"userGroups": {}
}
},
{
"id": "WAFwwid9TzE",
"code": "default2",
"name": "default2",
"shortName": "default2",
"startDate": "2019-01-01T00:00:00.000",
"attributeValues": [],
"lastUpdated": "2019-03-25T13:40:24.783",
"organisationUnits": [],
"created": "2019-03-25T13:40:24.722",
"translations": [],
"sharing": {
"public": "rwrw----",
"external": false,
"owner": null,
"users": {},
"userGroups": {}
}
},
{
"id": "LYerKDKCefk",
"code": "catopt1",
Expand Down Expand Up @@ -2835,6 +2854,30 @@
"userGroups": {}
}
},
{
"id": "Vk8PHR8HBAe",
"name": "default2",
"shortName": "default2",
"code": "default2",
"dataDimensionType": "DISAGGREGATION",
"categoryOptions": [
{
"id": "WAFwwid9TzE"
}
],
"dataDimension": false,
"attributeValues": [],
"translations": [],
"created": "2019-03-25T13:40:24.762",
"lastUpdated": "2019-03-29T08:54:32.360",
"sharing": {
"public": "rw------",
"external": false,
"owner": null,
"users": {},
"userGroups": {}
}
},
{
"id": "LC4ZMxxHtBA",
"name": "nondef1",
Expand Down Expand Up @@ -2907,6 +2950,28 @@
"userGroups": {}
}
},
{
"id": "WG3syvpSE9o",
"code": "default2",
"name": "default2",
"dataDimensionType": "DISAGGREGATION",
"categories": [
{
"id": "Vk8PHR8HBAe"
}
],
"created": "2019-03-25T13:40:24.771",
"lastUpdated": "2019-03-25T13:40:24.781",
"skipTotal": false,
"translations": [],
"sharing": {
"public": "rw------",
"external": false,
"owner": null,
"users": {},
"userGroups": {}
}
},
{
"id": "pFpCRzMVAbO",
"code": "nondef1",
Expand Down Expand Up @@ -3007,7 +3072,7 @@
}
],
"categoryCombo": {
"id": "bjDvmb4bfuf"
"id": "WG3syvpSE9o"
},
"translations": [],
"startDate": "2019-01-01T00:00:00.000",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"deleted": false,
"attributeOptionCombo": {
"idScheme": "UID",
"identifier": "KKKKX50cXC0"
"identifier": "HllvX50cXC0"
},
"attributeCategoryOptions": [],
"dataValues": [],
Expand Down
Loading
Loading