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 12 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 @@ -66,6 +66,10 @@ public enum ErrorCode {
E1119("{0} already exists: `{1}`"),
E1120("Update cannot be applied as it would make existing data values inaccessible"),

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 assocated with the default category combo"),

/* 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,117 @@
/*
* 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.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.getCategoryCombo().getUid().equals(other.getCategoryCombo().getUid())) {
return false;
}

if (one.getCategoryOptions() == null || other.getCategoryOptions() == null) {
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 checkNullCategoryComboReference(
CategoryOptionCombo categoryOptionCombo, Consumer<ErrorReport> addReports) {
if (categoryOptionCombo.getCategoryCombo() == null) {
addReports.accept(new ErrorReport(CategoryOptionCombo.class, ErrorCode.E1123));
}
}

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

List<CategoryOptionCombo> categoryOptionCombos = categoryService.getAllCategoryOptionCombos();

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 checkAlternativeDefaultCatOptionCombo(
CategoryOptionCombo categoryOptionCombo, Consumer<ErrorReport> addReports) {
if (categoryOptionCombo.getCategoryCombo().isDefault()) {
addReports.accept(new ErrorReport(CategoryOptionCombo.class, ErrorCode.E1124));
}
}

@Override
public void validate(
CategoryOptionCombo categoryOptionCombo,
ObjectBundle bundle,
Consumer<ErrorReport> addReports) {
checkNullCategoryComboReference(categoryOptionCombo, addReports);
checkAlternativeDefaultCatOptionCombo(categoryOptionCombo, addReports);
checkDuplicateCategoryOptionCombos(categoryOptionCombo, addReports);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.hisp.dhis.tracker.imports.domain.TrackerObjects;
import org.hisp.dhis.user.User;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

Expand Down Expand Up @@ -162,7 +163,8 @@ void testCategoryOptionIdentifiers() {
}
}

@Test
@Disabled
// Disabling this as we cannot import duplicate category option combos for default
void testCategoryOptionComboIdentifiers() {
List<Pair<String, TrackerIdSchemeParam>> data = buildDataSet("XXXvX50cXC0", "COCA", "COCAname");
for (Pair<String, TrackerIdSchemeParam> pair : data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ void testPreheatEvents() throws IOException {
assertFalse(preheat.getAll(OrganisationUnit.class).isEmpty());
assertFalse(preheat.getAll(ProgramStage.class).isEmpty());
assertFalse(preheat.getAll(CategoryOptionCombo.class).isEmpty());
/* Disabling this as we cannot import duplicate category option combos for default
https://dhis2.atlassian.net/browse/DHIS2-18401
assertNotNull(preheat.get(CategoryOptionCombo.class, "XXXvX50cXC0"));
*/
assertNotNull(preheat.get(CategoryOption.class, "XXXrKDKCefk"));
}
}
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 @@ -29,17 +29,22 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.Set;
import java.util.stream.Collectors;
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.feedback.ErrorCode;
import org.hisp.dhis.http.HttpStatus;
import org.hisp.dhis.jsontree.JsonArray;
import org.hisp.dhis.jsontree.JsonList;
import org.hisp.dhis.jsontree.JsonObject;
import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase;
import org.hisp.dhis.test.webapi.json.domain.JsonCategoryOptionCombo;
import org.hisp.dhis.test.webapi.json.domain.JsonErrorReport;
import org.hisp.dhis.test.webapi.json.domain.JsonIdentifiableObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -115,4 +120,33 @@ void catOptionCombosExcludingDefaultTest() {
assertFalse(
catOptionComboNames.contains("default"), "default catOptionCombo is not in payload");
}

@Test
@DisplayName("Duplicate CategoryOptionCombos should not be allowed")
void catOptionCombosDuplicatedTest() {

JsonObject response =
GET("/categoryOptionCombos?filter=id:eq:CocUid0001&fields=id,categoryCombo[id],categoryOptions[id]")
.content();
JsonList<JsonCategoryOptionCombo> catOptionCombos =
response.getList("categoryOptionCombos", JsonCategoryOptionCombo.class);
String catOptionComboAOptions = catOptionCombos.get(0).getCategoryOptions().get(0).getId();
String catOptionComboACatComboId = catOptionCombos.get(0).getCategoryCombo().getId();

JsonErrorReport error =
POST(
"/categoryOptionCombos/",
"""
{ "name": "A_1",
"categoryOptions" : [{"id" : "%s"}],
"categoryCombo" : {"id" : "%s"} }
"""
.formatted(catOptionComboAOptions, catOptionComboACatComboId))
.content(HttpStatus.CONFLICT)
.find(JsonErrorReport.class, report -> report.getErrorCode() == ErrorCode.E1122);
assertNotNull(error);
assertEquals(
"Category option combo A_1 already exists for category combo CatOptCombo A",
error.getMessage());
}
}
Loading
Loading