Skip to content

Commit

Permalink
Bulk Measurable Ratings
Browse files Browse the repository at this point in the history
- preview shows removals etc
- apply does changes and creates changelog entries

#CTCTOWALTZ-3335
#7145
  • Loading branch information
db-waltz committed Sep 20, 2024
1 parent f65a299 commit 0b11adc
Show file tree
Hide file tree
Showing 11 changed files with 677 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
import org.finos.waltz.model.tally.MeasurableRatingTally;
import org.finos.waltz.model.tally.Tally;
import org.finos.waltz.schema.Tables;
import org.finos.waltz.schema.tables.EntityHierarchy;
import org.finos.waltz.schema.tables.Measurable;
import org.finos.waltz.schema.tables.records.AllocationRecord;
import org.finos.waltz.schema.tables.records.MeasurableRatingPlannedDecommissionRecord;
import org.finos.waltz.schema.tables.records.MeasurableRatingRecord;
Expand Down Expand Up @@ -812,7 +810,7 @@ public boolean saveRatingItem(EntityReference entityRef,
long measurableId,
String ratingCode,
String username) {
return MeasurableRatingHelper.saveRatingItem(
return MeasurableRatingUtilities.saveRatingItem(
dsl,
entityRef,
measurableId,
Expand All @@ -822,7 +820,7 @@ public boolean saveRatingItem(EntityReference entityRef,


public boolean saveRatingIsPrimary(EntityReference entityRef, long measurableId, boolean isPrimary, String username) {
return dsl.transactionResult(ctx -> MeasurableRatingHelper.saveRatingIsPrimary(
return dsl.transactionResult(ctx -> MeasurableRatingUtilities.saveRatingIsPrimary(
ctx.dsl(),
entityRef,
measurableId,
Expand All @@ -832,7 +830,7 @@ public boolean saveRatingIsPrimary(EntityReference entityRef, long measurableId,


public boolean saveRatingDescription(EntityReference entityRef, long measurableId, String description, String username) {
return dsl.transactionResult(ctx -> MeasurableRatingHelper.saveRatingDescription(
return dsl.transactionResult(ctx -> MeasurableRatingUtilities.saveRatingDescription(
ctx.dsl(),
entityRef,
measurableId,
Expand All @@ -844,7 +842,7 @@ public boolean saveRatingDescription(EntityReference entityRef, long measurableI
public MeasurableRatingChangeSummary resolveLoggingContextForRatingChange(EntityReference entityRef,
long measurableId,
String desiredRatingCode) {
return MeasurableRatingHelper.resolveLoggingContextForRatingChange(dsl, entityRef, measurableId, desiredRatingCode);
return MeasurableRatingUtilities.resolveLoggingContextForRatingChange(dsl, entityRef, measurableId, desiredRatingCode);
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import static org.finos.waltz.schema.tables.MeasurableRating.MEASURABLE_RATING;
import static org.jooq.lambda.tuple.Tuple.tuple;

public class MeasurableRatingHelper {
public class MeasurableRatingUtilities {

/**
* Updates the given measurable rating to be set as primary.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
package org.finos.waltz.integration_test.inmem.service;


import jdk.jfr.internal.Logger;
import org.finos.waltz.common.ListUtilities;
import org.finos.waltz.common.CollectionUtilities;
import org.finos.waltz.data.measurable_category.MeasurableCategoryDao;
import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest;
import org.finos.waltz.model.EntityKind;
import org.finos.waltz.model.EntityReference;
import org.finos.waltz.model.bulk_upload.BulkUpdateMode;
import org.finos.waltz.model.bulk_upload.ChangeOperation;
import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingValidatedItem;
import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingValidationResult;
import org.finos.waltz.model.bulk_upload.taxonomy.BulkTaxonomyValidationResult;
import org.finos.waltz.model.bulk_upload.measurable_rating.ChangeOperation;
import org.finos.waltz.model.bulk_upload.measurable_rating.ValidationError;
import org.finos.waltz.model.measurable_category.MeasurableCategory;
import org.finos.waltz.service.measurable.MeasurableService;
import org.finos.waltz.service.measurable_rating.BulkMeasurableItemParser;
import org.finos.waltz.service.measurable_rating.MeasurableRatingService;
import org.finos.waltz.service.measurable_rating.BulkMeasurableRatingService;
import org.finos.waltz.test_common.helpers.AppHelper;
import org.finos.waltz.test_common.helpers.MeasurableHelper;
import org.finos.waltz.test_common.helpers.MeasurableRatingHelper;
import org.finos.waltz.test_common.helpers.RatingSchemeHelper;
import org.finos.waltz.test_common.helpers.UserHelper;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

import static java.lang.String.format;
import static java.util.Collections.emptySet;
import static org.finos.waltz.common.CollectionUtilities.all;
import static org.finos.waltz.common.CollectionUtilities.first;
import static org.finos.waltz.common.CollectionUtilities.isEmpty;
import static org.finos.waltz.common.ListUtilities.asList;
import static org.finos.waltz.model.EntityReference.mkRef;
import static org.finos.waltz.common.ListUtilities.map;
import static org.finos.waltz.common.SetUtilities.asSet;
import static org.finos.waltz.test_common.helpers.NameHelper.mkName;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest {
@Autowired
private UserHelper userHelper;

@Autowired
private AppHelper appHelper;

@Autowired
private MeasurableHelper measurableHelper;

@Autowired
private MeasurableRatingHelper measurableRatingHelper;

@Autowired
private MeasurableCategoryDao measurableCategoryDao;

Expand All @@ -50,31 +62,100 @@ public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest
private MeasurableService measurableService;

@Autowired
private MeasurableRatingService measurableRatingService;
private BulkMeasurableRatingService bulkMeasurableRatingService;

private static final String stem = "BMRST";
private static final Logger LOG = LoggerFactory.getLogger(BulkMeasurableRatingServiceTest.class);

@Test
public void previewUpdatesSuccess() {
public void previewAdds() {
MeasurableCategory category = measurableCategoryDao.getById(setupCategory());
measurableHelper.createMeasurable("CT-001", "M1", category.id().get());
String app1AssetCode = mkName("assetCode1", "previewUpdates");
String app1AssetCode = mkName(stem, "previewUpdatesCode");
EntityReference app1Ref = appHelper.createNewApp(
mkName("app1", "previewUpdates"),
mkName(stem, "previewUpdatesApp"),
ouIds.root,
app1AssetCode);

ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), "Rating1", 0, "#111", "R");
BulkMeasurableRatingValidationResult result = measurableRatingService.bulkPreview(
ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), mkName(stem, "Rating1"), 0, "#111", "R", mkName(stem, "R"));
BulkMeasurableRatingValidationResult result = bulkMeasurableRatingService.bulkPreview(
category.entityReference(),
mkSimpleTsv(app1AssetCode),
BulkMeasurableItemParser.InputFormat.CSV,
mkGoodTsv(app1AssetCode),
BulkMeasurableItemParser.InputFormat.TSV,
BulkUpdateMode.ADD_ONLY);

assertNotNull(result, "Expected a result");
assertNoErrors(result);
assertTaxonomyExternalIdMatch(result, asList("CT-001"));
}


@Test
public void previewUpdates() {
LOG.info("Setup app, measurable and rating scheme item");
MeasurableCategory category = measurableCategoryDao.getById(setupCategory());
long measurableId = measurableHelper.createMeasurable("CT-001", "M1", category.id().get());
String app1AssetCode = mkName(stem, "previewUpdatesCode");
EntityReference appRef = appHelper.createNewApp(
mkName(stem, "previewUpdatesApp"),
ouIds.root,
app1AssetCode);
ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), mkName(stem, "Rating1"), 0, "#111", "R", mkName(stem, "R"));

LOG.info("Create an existing rating for the app");
measurableRatingHelper.saveRatingItem(appRef, measurableId, "R", "user");

BulkMeasurableRatingValidationResult result = bulkMeasurableRatingService.bulkPreview(
category.entityReference(),
mkGoodTsv(app1AssetCode),
BulkMeasurableItemParser.InputFormat.TSV,
BulkUpdateMode.ADD_ONLY);

assertNotNull(result, "Expected a result");
assertNoErrors(result);
assertTaxonomyExternalIdMatch(result, asList("CT-001"));
assertEquals(1, result.validatedItems().size(), "Only one parsed row expected");
assertEquals(
ChangeOperation.UPDATE,
first(result.validatedItems()).changeOperation());
}


@Test
public void previewComprehensive() {
LOG.info("Setup app, measurable and rating scheme item");
MeasurableCategory category = measurableCategoryDao.getById(setupCategory());
long measurable1Id = measurableHelper.createMeasurable("CT-001", "M1", category.id().get());
measurableHelper.createMeasurable("CT-002", "M2", category.id().get());

String app1AssetCode = mkName(stem, "previewUpdatesCode");
EntityReference appRef = appHelper.createNewApp(
mkName(stem, "previewUpdatesApp"),
ouIds.root,
app1AssetCode);
ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), mkName(stem, "Rating1"), 0, "#111", "R", mkName(stem, "R"));

LOG.info("Create an existing rating for the app");
measurableRatingHelper.saveRatingItem(appRef, measurable1Id, "R", "user");

BulkMeasurableRatingValidationResult result = bulkMeasurableRatingService.bulkPreview(
category.entityReference(),
mkComprehensiveTsv(app1AssetCode, "CT-002", "CT-001"),
BulkMeasurableItemParser.InputFormat.TSV,
BulkUpdateMode.ADD_ONLY);

assertNotNull(result, "Expected a result");
assertEquals(3, result.validatedItems().size(), "Only one parsed row expected");
assertEquals(
asList(ChangeOperation.ADD, ChangeOperation.NONE, ChangeOperation.UPDATE),
map(result.validatedItems(), BulkMeasurableRatingValidatedItem::changeOperation));
assertEquals(
asList(emptySet(), asSet(ValidationError.APPLICATION_NOT_FOUND), emptySet()),
map(result.validatedItems(), BulkMeasurableRatingValidatedItem::errors));
}



@Test
public void previewUpdatesError() {
MeasurableCategory category = measurableCategoryDao.getById(setupCategory());
Expand All @@ -85,43 +166,83 @@ public void previewUpdatesError() {
ouIds.root,
app1AssetCode);
ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), "Rating1", 0, "#111", "R");
BulkMeasurableRatingValidationResult result = measurableRatingService.bulkPreview(
BulkMeasurableRatingValidationResult result = bulkMeasurableRatingService.bulkPreview(
category.entityReference(),
mkSimpleTsv(),
BulkMeasurableItemParser.InputFormat.CSV,
mkBadRefsTsv(app1AssetCode, "CT-001", 'R'),
BulkMeasurableItemParser.InputFormat.TSV,
BulkUpdateMode.ADD_ONLY);

assertNotNull(result, "Expected a result");
assertNoErrors(result);
assertTaxonomyExternalIdMatch(result, asList("CT-001"));

result
.validatedItems()
.forEach(d -> {
if (d.parsedItem().assetCode().equals("badApp")) {
assertTrue(d.errors().contains(ValidationError.APPLICATION_NOT_FOUND), "Should be complaining about the bad app (assetCode)");
}
if (d.parsedItem().taxonomyExternalId().equals("badMeasurable")) {
assertTrue(d.errors().contains(ValidationError.MEASURABLE_NOT_FOUND), "Should be complaining about the bad measurable ref (taxonomyExtId)");
}
if (d.parsedItem().ratingCode() == '_') {
assertTrue(d.errors().contains(ValidationError.RATING_NOT_FOUND), "Should be complaining about the bad rating code");
}
});

assertEquals(5, result.validatedItems().size(), "Expected 5 items");
assertTrue(CollectionUtilities.all(result.validatedItems(), d -> ! d.errors().isEmpty()));
}



// --- helpers -------------------


private long setupCategory() {
String categoryName = mkName("MeasurableRatingChangeServiceTest", "category");
String categoryName = mkName(stem, "category");
return measurableHelper.createMeasurableCategory(categoryName);
}

private String mkSimpleTsv(String assetCode) {
return "assetCode, taxonomyExternalId, ratingCode, isPrimary, comment\n"
+assetCode+", CT-001, R, true, comment\n";

private String mkGoodTsv(String assetCode) {
return "assetCode\ttaxonomyExternalId\tratingCode\tisPrimary\tcomment\n"
+ assetCode + "\tCT-001\tR\ttrue\tcomment\n";
}


private void assertTaxonomyExternalIdMatch(BulkMeasurableRatingValidationResult result,
List<String> taxonomyExternalId) {
assertEquals(
taxonomyExternalId,
ListUtilities.map(result.validatedItems(), d -> d.parsedItem().taxonomyExternalId()),
map(result.validatedItems(), d -> d.parsedItem().taxonomyExternalId()),
"Expected taxonomyExternalId do not match");
}


private void assertNoErrors(BulkMeasurableRatingValidationResult result) {
assertTrue(
all(result.validatedItems(), d -> isEmpty(d.errors())),
"Should have no errors");
}

private String mkSimpleTsv() {
return "assetCode, taxonomyExternalId, ratingCode, isPrimary, comment\n" +
"assetCode1, CT-001, R, true, comment\n" +
"assetCode2, CT-001, R, true, comment\n" ;

private String mkBadRefsTsv(String goodApp,
String goodMeasurable,
char goodRating) {
return "assetCode\ttaxonomyExternalId\tratingCode\tisPrimary\tcomment\n" +
format("%s\t%s\t%s\ttrue\tcomment\n", goodApp, "badMeasurable", goodRating) +
format("%s\t%s\t%s\ttrue\tcomment\n", "badApp", goodMeasurable, goodRating) +
format("%s\t%s\t%s\ttrue\tcomment\n", goodApp, goodMeasurable, '_') +
format("%s\t%s\t%s\ttrue\tcomment\n", goodApp, "badMeasurable", '_') +
format("%s\t%s\t%s\ttrue\tcomment\n", "badApp", "badMeasurable", '_');
}


private String mkComprehensiveTsv(String goodApp,
String goodMeasurable,
String existingMeasurable) {
return "assetCode\ttaxonomyExternalId\tratingCode\tisPrimary\tcomment\n" +
format("%s\t%s\t%s\ttrue\tcomment\n", goodApp, goodMeasurable, 'R') + // should be an 'add'
format("%s\t%s\t%s\ttrue\tcomment\n", "badApp", goodMeasurable, 'R') + // should be 'none' (with errors)
format("%s\t%s\t%s\ttrue\tcomment\n", goodApp, existingMeasurable, 'R'); // should be 'update'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.finos.waltz.model.bulk_upload.measurable_rating;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;

@Value.Immutable
@JsonSerialize(as = ImmutableBulkMeasurableRatingApplyResult.class)
public interface BulkMeasurableRatingApplyResult {

int recordsAdded();
int recordsUpdated();
int recordsRemoved();

int skippedRows();

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.finos.waltz.model.bulk_upload.measurable_rating;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.finos.waltz.model.Nullable;
import org.finos.waltz.model.application.Application;
import org.finos.waltz.model.measurable.Measurable;
import org.finos.waltz.model.rating.RatingSchemeItem;
import org.immutables.value.Value;

import java.util.Set;
Expand All @@ -10,7 +14,19 @@
public interface BulkMeasurableRatingValidatedItem {

BulkMeasurableRatingItem parsedItem();

ChangeOperation changeOperation();

Set<ChangedFieldType> changedFields();

Set<ValidationError> errors();

@Nullable
Application application();

@Nullable
Measurable measurable();

@Nullable
RatingSchemeItem ratingSchemeItem();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@


import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.finos.waltz.model.EntityReference;
import org.finos.waltz.model.Nullable;
import org.finos.waltz.model.measurable.Measurable;
import org.immutables.value.Value;
import org.jooq.lambda.tuple.Tuple2;

import java.util.List;
import java.util.Set;
Expand All @@ -18,4 +19,11 @@ public interface BulkMeasurableRatingValidationResult {
@Nullable
BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError error();

@Value.Derived
default int removalCount() {
return removals().size();
}

Set<Tuple2<EntityReference, Long>> removals();

}
Loading

0 comments on commit 0b11adc

Please sign in to comment.