diff --git a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java index 126d48eb91..72f23513c3 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java @@ -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; @@ -812,7 +810,7 @@ public boolean saveRatingItem(EntityReference entityRef, long measurableId, String ratingCode, String username) { - return MeasurableRatingHelper.saveRatingItem( + return MeasurableRatingUtilities.saveRatingItem( dsl, entityRef, measurableId, @@ -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, @@ -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, @@ -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); } /* diff --git a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingHelper.java b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingUtilities.java similarity index 99% rename from waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingHelper.java rename to waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingUtilities.java index d937573d73..b633642821 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingHelper.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingUtilities.java @@ -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. diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java index 3b805f0056..ab9dd03c7f 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java @@ -1,35 +1,43 @@ 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 @@ -37,9 +45,13 @@ public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest @Autowired private AppHelper appHelper; + @Autowired private MeasurableHelper measurableHelper; + @Autowired + private MeasurableRatingHelper measurableRatingHelper; + @Autowired private MeasurableCategoryDao measurableCategoryDao; @@ -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()); @@ -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 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' } } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingApplyResult.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingApplyResult.java new file mode 100644 index 0000000000..3342cba7eb --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingApplyResult.java @@ -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(); + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.java index 752d47e136..60e5333ce1 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.java @@ -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; @@ -10,7 +14,19 @@ public interface BulkMeasurableRatingValidatedItem { BulkMeasurableRatingItem parsedItem(); + ChangeOperation changeOperation(); + Set changedFields(); + Set errors(); + + @Nullable + Application application(); + + @Nullable + Measurable measurable(); + + @Nullable + RatingSchemeItem ratingSchemeItem(); } \ No newline at end of file diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java index 005f6000e8..5bc3f85df0 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java @@ -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; @@ -18,4 +19,11 @@ public interface BulkMeasurableRatingValidationResult { @Nullable BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError error(); + @Value.Derived + default int removalCount() { + return removals().size(); + } + + Set> removals(); + } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingService.java new file mode 100644 index 0000000000..77407b8972 --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingService.java @@ -0,0 +1,392 @@ +package org.finos.waltz.service.measurable_rating; + +import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.common.StringUtilities; +import org.finos.waltz.data.application.ApplicationDao; +import org.finos.waltz.data.measurable_rating.MeasurableRatingDao; +import org.finos.waltz.model.DiffResult; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.Operation; +import org.finos.waltz.model.Severity; +import org.finos.waltz.model.application.Application; +import org.finos.waltz.model.bulk_upload.BulkUpdateMode; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingApplyResult; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingItem; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingParseResult; +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.measurable_rating.ChangeOperation; +import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingApplyResult; +import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingValidatedItem; +import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingValidationResult; +import org.finos.waltz.model.bulk_upload.measurable_rating.ValidationError; +import org.finos.waltz.model.exceptions.NotAuthorizedException; +import org.finos.waltz.model.external_identifier.ExternalIdValue; +import org.finos.waltz.model.measurable.Measurable; +import org.finos.waltz.model.measurable_category.MeasurableCategory; +import org.finos.waltz.model.measurable_rating.ImmutableMeasurableRating; +import org.finos.waltz.model.measurable_rating.MeasurableRating; +import org.finos.waltz.model.rating.RatingSchemeItem; +import org.finos.waltz.model.user.SystemRole; +import org.finos.waltz.schema.Tables; +import org.finos.waltz.schema.tables.records.ChangeLogRecord; +import org.finos.waltz.schema.tables.records.MeasurableRatingRecord; +import org.finos.waltz.service.measurable.MeasurableService; +import org.finos.waltz.service.measurable_category.MeasurableCategoryService; +import org.finos.waltz.service.rating_scheme.RatingSchemeService; +import org.finos.waltz.service.user.UserRoleService; +import org.jooq.DSLContext; +import org.jooq.DeleteConditionStep; +import org.jooq.UpdateConditionStep; +import org.jooq.impl.DSL; +import org.jooq.lambda.tuple.Tuple2; +import org.jooq.lambda.tuple.Tuple5; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static org.finos.waltz.common.MapUtilities.indexBy; +import static org.finos.waltz.common.SetUtilities.asSet; +import static org.finos.waltz.data.JooqUtilities.summarizeResults; +import static org.jooq.lambda.tuple.Tuple.tuple; + +@Service +public class BulkMeasurableRatingService { + + private static final Logger LOG = LoggerFactory.getLogger(MeasurableRatingService.class); + private static final String PROVENANCE = "bulkMeasurableRatingUpdate"; + private static final String DUMMY_USER = "test"; + + private final UserRoleService userRoleService; + private final MeasurableRatingDao measurableRatingDao; + private final RatingSchemeService ratingSchemeService; + private final MeasurableService measurableService; + private final MeasurableCategoryService measurableCategoryService; + private final ApplicationDao applicationDao; + private final DSLContext dsl; + + private final org.finos.waltz.schema.tables.MeasurableRating mr = Tables.MEASURABLE_RATING; + + @Autowired + public BulkMeasurableRatingService(UserRoleService userRoleService, + MeasurableRatingDao measurableRatingDao, + RatingSchemeService ratingSchemeService, + MeasurableService measurableService, + MeasurableCategoryService measurableCategoryService, + ApplicationDao applicationDao, + DSLContext dsl) { + this.userRoleService = userRoleService; + this.measurableRatingDao = measurableRatingDao; + this.ratingSchemeService = ratingSchemeService; + this.measurableService = measurableService; + this.measurableCategoryService = measurableCategoryService; + this.applicationDao = applicationDao; + this.dsl = dsl; + } + + + public BulkMeasurableRatingValidationResult bulkPreview(EntityReference categoryRef, + String inputStr, + BulkMeasurableItemParser.InputFormat format, + BulkUpdateMode mode) { + + BulkMeasurableRatingParseResult result = new BulkMeasurableItemParser().parse(inputStr, format); + if (result.error() != null) { + return ImmutableBulkMeasurableRatingValidationResult + .builder() + .error(result.error()) + .build(); + } + MeasurableCategory category = measurableCategoryService.getById(categoryRef.id()); + List existingMeasurables = measurableService.findByCategoryId(categoryRef.id()); + Map existingByExtId = indexBy(existingMeasurables, m -> m.externalId().orElse(null)); + + List allApplications = applicationDao.findAll(); + + Map allApplicationsByAssetCode = indexBy(allApplications, a -> a.assetCode() + .map(ExternalIdValue::value) + .map(StringUtilities::lower) + .orElse("")); + + Set ratingSchemeItemsBySchemeIds = ratingSchemeService.findRatingSchemeItemsBySchemeIds(asSet(category.ratingSchemeId())); + Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); + + List>> validatedEntries = result + .parsedItems() + .stream() + .map(d -> { + Application application = allApplicationsByAssetCode.get(d.assetCode().toLowerCase()); + Measurable measurable = existingByExtId.get(d.taxonomyExternalId()); + RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); + return tuple(d, application, measurable, ratingSchemeItem); + }) + .map(t -> { + Set validationErrors = new HashSet<>(); + if (t.v2 == null) { + validationErrors.add(ValidationError.APPLICATION_NOT_FOUND); + } + if (t.v3 == null) { + validationErrors.add(ValidationError.MEASURABLE_NOT_FOUND); + } + if (t.v4 == null) { + validationErrors.add(ValidationError.RATING_NOT_FOUND); + } + if (t.v3 != null && !t.v3.concrete()) { + validationErrors.add(ValidationError.MEASURABLE_NOT_CONCRETE); + } + if (t.v4 != null && !t.v4.userSelectable()) { + validationErrors.add(ValidationError.RATING_NOT_USER_SELECTABLE); + } + + return t.concat(validationErrors); + }) + .collect(Collectors.toList()); + + Collection existingRatings = measurableRatingDao.findByCategory(category.id().get()); + + List requiredRatings = validatedEntries + .stream() + .filter(t -> t.v2 != null && t.v3 != null && t.v4 != null) + .map(t -> ImmutableMeasurableRating + .builder() + .entityReference(t.v2.entityReference()) + .measurableId(t.v3.id().get()) + .description(t.v1.comment()) + .rating(t.v1.ratingCode()) + .isPrimary(t.v1.isPrimary()) + .lastUpdatedBy(DUMMY_USER) + .provenance(PROVENANCE) + .build()) + .collect(Collectors.toList()); + + DiffResult diff = DiffResult + .mkDiff( + existingRatings, + requiredRatings, + d -> tuple(d.entityReference(), d.measurableId()), + (a, b) -> a.isPrimary() == b.isPrimary() + && StringUtilities.safeEq(a.description(), b.description()) + && a.rating() == b.rating()); + + Set> toAdd = SetUtilities.map(diff.otherOnly(), d -> tuple(d.entityReference(), d.measurableId())); + Set> toUpdate = SetUtilities.map(diff.differingIntersection(), d -> tuple(d.entityReference(), d.measurableId())); + Set> toRemove = SetUtilities.map(diff.waltzOnly(), d -> tuple(d.entityReference(), d.measurableId())); + + List validatedItems = validatedEntries + .stream() + //.filter(t -> t.v2 != null && t.v3 != null) + .map(t -> { + boolean eitherAppOrMeasurableIsMissing = t.v2 == null || t.v3 == null; + + if (eitherAppOrMeasurableIsMissing) { + return t.concat(ChangeOperation.NONE); + } else { + Tuple2 recordKey = tuple(t.v2.entityReference(), t.v3.id().get()); + if (toAdd.contains(recordKey)) { + return t.concat(ChangeOperation.ADD); + } + if (toUpdate.contains(recordKey)) { + return t.concat(ChangeOperation.UPDATE); + } + return t.concat(ChangeOperation.NONE); + } + }) + .map(t -> ImmutableBulkMeasurableRatingValidatedItem + .builder() + .changeOperation(t.v6) + .errors(t.v5) + .application(t.v2) + .measurable(t.v3) + .ratingSchemeItem(t.v4) + .parsedItem(t.v1) + .build()) + .collect(Collectors.toList()); + + return ImmutableBulkMeasurableRatingValidationResult + .builder() + .validatedItems(validatedItems) + .removals(mode == BulkUpdateMode.REPLACE + ? toRemove + : emptySet()) + .build(); + } + + + public BulkMeasurableRatingApplyResult apply(EntityReference categoryRef, + BulkMeasurableRatingValidationResult preview, + BulkUpdateMode mode, + String userId) { + + verifyUserHasPermissions(userId); + + if (preview.error() != null) { + throw new IllegalStateException("Cannot apply changes with formatting errors"); + } + + Timestamp now = DateTimeUtilities.nowUtcTimestamp(); + + Set toInsert = preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() == ChangeOperation.ADD && d.errors().isEmpty()) + .map(d -> { + MeasurableRatingRecord r = new MeasurableRatingRecord(); + r.setEntityKind(EntityKind.APPLICATION.name()); + r.setEntityId(d.application().id().get()); + r.setMeasurableId(d.measurable().id().get()); + r.setRating(d.ratingSchemeItem().rating()); + r.setDescription(d.parsedItem().comment()); + r.setIsPrimary(d.parsedItem().isPrimary()); + r.setLastUpdatedBy(userId); + r.setLastUpdatedAt(now); + r.setProvenance(PROVENANCE); + return r; + }) + .collect(Collectors.toSet()); + + Set> toUpdate = preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() == ChangeOperation.UPDATE && d.errors().isEmpty()) + .map(d -> DSL + .update(mr) + .set(mr.RATING, d.ratingSchemeItem().rating()) + .set(mr.DESCRIPTION, d.parsedItem().comment()) + .set(mr.IS_PRIMARY, d.parsedItem().isPrimary()) + .set(mr.LAST_UPDATED_AT, now) + .set(mr.LAST_UPDATED_BY, userId) + .where(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()) + .and(mr.ENTITY_ID.eq(d.application().id().get())) + .and(mr.MEASURABLE_ID.eq(d.measurable().id().get())))) + .collect(Collectors.toSet()); + + Set> toRemove = preview + .removals() + .stream() + .map(d -> DSL + .delete(mr) + .where(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()) + .and(mr.ENTITY_ID.eq(d.v1.id())) + .and(mr.MEASURABLE_ID.eq(d.v2)))) + .collect(Collectors.toSet()); + + Map measurablesById = indexBy( + measurableService.findByCategoryId(categoryRef.id()), + m -> m.id().get()); + + Set auditLogs = Stream.concat( + preview + .removals() + .stream() + .map(t -> { + Measurable m = measurablesById.get(t.v2); + ChangeLogRecord r = new ChangeLogRecord(); + r.setMessage(format( + "Bulk Rating Update - Removed measurable rating for: %s/%s (%d)", + m == null ? "?" : m.name(), + m == null ? "?" : m.externalId().orElse("-"), + t.v2)); + r.setOperation(Operation.REMOVE.name()); + r.setParentKind(EntityKind.APPLICATION.name()); + r.setParentId(t.v1().id()); + r.setCreatedAt(now); + r.setUserId(userId); + r.setSeverity(Severity.INFORMATION.name()); + return r; + }), + preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() != ChangeOperation.NONE) + .map(d -> { + ChangeLogRecord r = new ChangeLogRecord(); + r.setMessage(mkChangeMessage(d.measurable(), d.changeOperation())); + r.setOperation(toChangeLogOperation(d.changeOperation()).name()); + r.setParentKind(EntityKind.APPLICATION.name()); + r.setParentId(d.application().id().get()); + r.setCreatedAt(now); + r.setUserId(userId); + r.setSeverity(Severity.INFORMATION.name()); + return r; + })) + .collect(Collectors.toSet()); + + long skipCount = preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() == ChangeOperation.NONE || !d.errors().isEmpty()) + .count(); + + return dsl + .transactionResult(ctx -> { + DSLContext tx = ctx.dsl(); + int insertCount = summarizeResults(tx.batchInsert(toInsert).execute()); + int updateCount = summarizeResults(tx.batch(toUpdate).execute()); + int removalCount = mode == BulkUpdateMode.REPLACE + ? summarizeResults(tx.batch(toRemove).execute()) + : 0; + int changeLogCount = summarizeResults(tx.batchInsert(auditLogs).execute()); + + LOG.info( + "Batch measurable rating: {} adds, {} updates, {} removes, {} changeLogs", + insertCount, + updateCount, + removalCount, + changeLogCount); + + return ImmutableBulkMeasurableRatingApplyResult + .builder() + .recordsAdded(insertCount) + .recordsUpdated(updateCount) + .recordsRemoved(removalCount) + .skippedRows((int) skipCount) + .build(); + }); + } + + + private String mkChangeMessage(Measurable measurable, + ChangeOperation changeOperation) { + return format( + "Bulk Rating Update - Operation: %s, measurable rating for: %s/%s", + changeOperation, + measurable.name(), + measurable.externalId().orElse("?")); + } + + + private Operation toChangeLogOperation(ChangeOperation changeOperation) { + switch (changeOperation) { + case ADD: + return Operation.ADD; + case UPDATE: + return Operation.UPDATE; + case REMOVE: + return Operation.REMOVE; + default: + return Operation.UNKNOWN; + } + } + + + private void verifyUserHasPermissions(String userId) { + if (!userRoleService.hasRole(userId, SystemRole.TAXONOMY_EDITOR.name())) { + throw new NotAuthorizedException(); + } + } + +} \ No newline at end of file diff --git a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java index 1ef309467d..ea222c5ae3 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java @@ -19,8 +19,6 @@ package org.finos.waltz.service.measurable_rating; import org.finos.waltz.common.DateTimeUtilities; -import org.finos.waltz.common.SetUtilities; -import org.finos.waltz.common.StringUtilities; import org.finos.waltz.data.EntityReferenceNameResolver; import org.finos.waltz.data.GenericSelector; import org.finos.waltz.data.application.ApplicationDao; @@ -30,16 +28,21 @@ import org.finos.waltz.data.measurable_category.MeasurableCategoryDao; import org.finos.waltz.data.measurable_rating.MeasurableRatingDao; import org.finos.waltz.data.measurable_rating.MeasurableRatingIdSelectorFactory; -import org.finos.waltz.model.*; -import org.finos.waltz.model.application.Application; -import org.finos.waltz.model.bulk_upload.BulkUpdateMode; -import org.finos.waltz.model.bulk_upload.measurable_rating.*; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.IdSelectionOptions; +import org.finos.waltz.model.Operation; +import org.finos.waltz.model.Severity; +import org.finos.waltz.model.UserTimestamp; import org.finos.waltz.model.changelog.ImmutableChangeLog; -import org.finos.waltz.model.external_identifier.ExternalIdValue; import org.finos.waltz.model.measurable.Measurable; import org.finos.waltz.model.measurable_category.MeasurableCategory; -import org.finos.waltz.model.measurable_rating.*; -import org.finos.waltz.model.rating.RatingScheme; +import org.finos.waltz.model.measurable_rating.MeasurableRating; +import org.finos.waltz.model.measurable_rating.MeasurableRatingChangeSummary; +import org.finos.waltz.model.measurable_rating.MeasurableRatingCommand; +import org.finos.waltz.model.measurable_rating.MeasurableRatingStatParams; +import org.finos.waltz.model.measurable_rating.RemoveMeasurableRatingCommand; +import org.finos.waltz.model.measurable_rating.SaveMeasurableRatingCommand; import org.finos.waltz.model.rating.RatingSchemeItem; import org.finos.waltz.model.tally.MeasurableRatingTally; import org.finos.waltz.model.tally.Tally; @@ -51,29 +54,25 @@ import org.jooq.Record1; import org.jooq.Select; import org.jooq.lambda.tuple.Tuple2; -import org.jooq.lambda.tuple.Tuple4; -import org.jooq.lambda.tuple.Tuple5; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Collection; +import java.util.List; +import java.util.Set; import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; -import static java.util.Optional.ofNullable; -import static org.finos.waltz.common.Checks.*; -import static org.finos.waltz.common.MapUtilities.indexBy; -import static org.finos.waltz.common.SetUtilities.asSet; -import static org.finos.waltz.model.EntityReference.mkRef; -import static org.jooq.lambda.tuple.Tuple.tuple; +import static org.finos.waltz.common.Checks.checkFalse; +import static org.finos.waltz.common.Checks.checkNotNull; @Service public class MeasurableRatingService { private static final Logger LOG = LoggerFactory.getLogger(MeasurableRatingService.class); + private static final String PROVENANCE = "bulkMeasurableRatingUpdate"; + private static final String DUMMY_USER = "test"; private final MeasurableRatingDao measurableRatingDao; private final MeasurableDao measurableDao; private final MeasurableCategoryDao measurableCategoryDao; @@ -472,119 +471,4 @@ public Set findPrimaryRatingsForMeasurableIdSelector(Select existingMeasurables = measurableService.findByCategoryId(measurableRatingRef.id()); - Map existingByExtId = indexBy(existingMeasurables, m -> m.externalId().orElse(null)); - - List allApplications = applicationDao.findAll(); - Map allApplicationsByAssetCode = indexBy(allApplications, a -> a.assetCode() - .map(ExternalIdValue::value) - .map(StringUtilities::lower) - .orElse("")); - - Set ratingSchemeItemsBySchemeIds = ratingSchemeService.findRatingSchemeItemsBySchemeIds(asSet(category.ratingSchemeId())); - Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); - - - List>> validatedEntries = result - .parsedItems() - .stream() - .map(d -> { - Application application = allApplicationsByAssetCode.get(d.assetCode().toLowerCase()); - Measurable measurable = existingByExtId.get(d.taxonomyExternalId()); - RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); - return tuple(d, application, measurable, ratingSchemeItem); - }) - .map(t -> { - Set validationErrors = new HashSet<>(); - if (t.v2 == null) { - validationErrors.add(ValidationError.APPLICATION_NOT_FOUND); - } - if (t.v3 == null) { - validationErrors.add(ValidationError.MEASURABLE_NOT_FOUND); - } - if (t.v4 == null) { - validationErrors.add(ValidationError.RATING_NOT_FOUND); - } - if (t.v3 != null && !t.v3.concrete()) { - validationErrors.add(ValidationError.MEASURABLE_NOT_CONCRETE); - } - if (t.v4 != null && !t.v4.userSelectable()) { - validationErrors.add(ValidationError.RATING_NOT_USER_SELECTABLE); - } - - return t.concat(validationErrors); - }) - .collect(Collectors.toList()); - - Collection existingRatings = measurableRatingDao.findByCategory(category.id().get()); - //Optional EntityRef = Optional.empty(); - List requiredRatings = validatedEntries - .stream() - .filter(t -> t.v1 !=null && t.v2 !=null && t.v4 !=null ) - .map(t -> ImmutableMeasurableRating - .builder() - .entityReference(t.v2.entityReference()) - //.entityReference("null") - .measurableId(t.v3.id().get()) - .description(t.v1.comment()) - .rating(t.v1.ratingCode()) - .isPrimary(t.v1.isPrimary()) - .lastUpdatedBy("test") - .provenance("bulkMeasurableRatingUpdate") - .build()) - .collect(Collectors.toList()); - - DiffResult diff = DiffResult - .mkDiff( - existingRatings, - requiredRatings, - d -> tuple(d.entityReference(), d.measurableId()), - (a, b) -> a.isPrimary() == b.isPrimary() - && StringUtilities.safeEq(a.description(), b.description()) - && a.rating() == b.rating()); - - Set> toAdd = SetUtilities.map(diff.otherOnly(), d -> tuple(d.entityReference(), d.measurableId())); - Set> toRemove = SetUtilities.map(diff.waltzOnly(), d -> tuple(d.entityReference(), d.measurableId())); - Set> toUpdate = SetUtilities.map(diff.differingIntersection(), d -> tuple(d.entityReference(), d.measurableId())); - - - List validatedItems = validatedEntries - .stream() - //.filter(t -> t.v2 != null && t.v3 != null) - .map(t -> { - Tuple2 recordKey = tuple(t.v2.entityReference(), t.v3.id().get()); - if (toAdd.contains(recordKey)) { - return t.concat(ChangeOperation.ADD); - } - if (toUpdate.contains(recordKey)) { - return t.concat(ChangeOperation.UPDATE); - } - return t.concat(ChangeOperation.NONE); - }) - .map(t -> ImmutableBulkMeasurableRatingValidatedItem - .builder() - .changeOperation(t.v6) - .errors(t.v5) - .parsedItem(t.v1) - .build()) - .collect(Collectors.toList()); - - return ImmutableBulkMeasurableRatingValidationResult - .builder() - .validatedItems(validatedItems) - .build(); - } } diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableHelper.java index 142b45da0a..fd939b1eee 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableHelper.java @@ -22,6 +22,7 @@ import static org.finos.waltz.schema.Tables.MEASURABLE_CATEGORY; import static org.finos.waltz.schema.Tables.MEASURABLE_RATING; import static org.finos.waltz.schema.Tables.MEASURABLE_RATING_PLANNED_DECOMMISSION; +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; @Service public class MeasurableHelper { @@ -49,7 +50,7 @@ public long createMeasurableCategory(String name, String ratingEditorRole) { .maybeFirst(categories) .map(c -> c.id().get()) .orElseGet(() -> { - long schemeId = ratingSchemeHelper.createEmptyRatingScheme("test"); + long schemeId = ratingSchemeHelper.createEmptyRatingScheme(mkName("measurableHelper", "defaultScheme")); MeasurableCategoryRecord record = dsl.newRecord(MEASURABLE_CATEGORY); record.setDescription(name); record.setName(name); diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableRatingHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableRatingHelper.java new file mode 100644 index 0000000000..b90e265f3f --- /dev/null +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableRatingHelper.java @@ -0,0 +1,27 @@ +package org.finos.waltz.test_common.helpers; + +import org.finos.waltz.data.measurable_rating.MeasurableRatingUtilities; +import org.finos.waltz.model.EntityReference; +import org.jooq.DSLContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MeasurableRatingHelper { + + @Autowired + private DSLContext dsl; + + public boolean saveRatingItem(EntityReference entityRef, + long measurableId, + String ratingCode, + String username) { + + return MeasurableRatingUtilities.saveRatingItem( + dsl, + entityRef, + measurableId, + ratingCode, + username); + } +} diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java index 1c6ce6910b..1986db3b28 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java @@ -18,6 +18,7 @@ package org.finos.waltz.web.endpoints.api; +import org.finos.waltz.common.EnumUtilities; import org.finos.waltz.common.exception.InsufficientPrivelegeException; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityReference; @@ -26,6 +27,7 @@ import org.finos.waltz.model.UserTimestamp; import org.finos.waltz.model.application.MeasurableRatingsView; import org.finos.waltz.model.bulk_upload.BulkUpdateMode; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingValidationResult; import org.finos.waltz.model.measurable_rating.ImmutableRemoveMeasurableRatingCommand; import org.finos.waltz.model.measurable_rating.MeasurableRating; import org.finos.waltz.model.measurable_rating.MeasurableRatingCategoryView; @@ -34,7 +36,8 @@ import org.finos.waltz.model.measurable_rating.RemoveMeasurableRatingCommand; import org.finos.waltz.model.tally.MeasurableRatingTally; import org.finos.waltz.model.tally.Tally; -import org.finos.waltz.service.measurable_rating.BulkMeasurableItemParser; +import org.finos.waltz.service.measurable_rating.BulkMeasurableItemParser.InputFormat; +import org.finos.waltz.service.measurable_rating.BulkMeasurableRatingService; import org.finos.waltz.service.measurable_rating.MeasurableRatingService; import org.finos.waltz.service.measurable_rating.MeasurableRatingViewService; import org.finos.waltz.service.permission.permission_checker.MeasurableRatingPermissionChecker; @@ -56,8 +59,14 @@ import static org.finos.waltz.common.SetUtilities.asSet; import static org.finos.waltz.model.EntityKind.MEASURABLE_RATING; import static org.finos.waltz.model.EntityReference.mkRef; -import static org.finos.waltz.web.WebUtilities.*; -import static org.finos.waltz.web.WebUtilities.readEnum; +import static org.finos.waltz.web.WebUtilities.getEntityReference; +import static org.finos.waltz.web.WebUtilities.getId; +import static org.finos.waltz.web.WebUtilities.getLong; +import static org.finos.waltz.web.WebUtilities.getUsername; +import static org.finos.waltz.web.WebUtilities.mkPath; +import static org.finos.waltz.web.WebUtilities.readBody; +import static org.finos.waltz.web.WebUtilities.readIdSelectionOptionsFromBody; +import static org.finos.waltz.web.WebUtilities.requireRole; import static org.finos.waltz.web.endpoints.EndpointUtilities.deleteForList; import static org.finos.waltz.web.endpoints.EndpointUtilities.getForDatum; import static org.finos.waltz.web.endpoints.EndpointUtilities.getForList; @@ -71,6 +80,7 @@ public class MeasurableRatingEndpoint implements Endpoint { private final MeasurableRatingService measurableRatingService; + private final BulkMeasurableRatingService bulkMeasurableRatingService; private final MeasurableRatingViewService measurableRatingViewService; private final MeasurableRatingPermissionChecker measurableRatingPermissionChecker; private final UserRoleService userRoleService; @@ -78,6 +88,7 @@ public class MeasurableRatingEndpoint implements Endpoint { @Autowired public MeasurableRatingEndpoint(MeasurableRatingService measurableRatingService, + BulkMeasurableRatingService bulkMeasurableRatingService, MeasurableRatingViewService measurableRatingViewService, MeasurableRatingPermissionChecker measurableRatingPermissionChecker, UserRoleService userRoleService) { @@ -90,6 +101,7 @@ public MeasurableRatingEndpoint(MeasurableRatingService measurableRatingService, this.measurableRatingService = measurableRatingService; this.measurableRatingPermissionChecker = measurableRatingPermissionChecker; this.measurableRatingViewService = measurableRatingViewService; + this.bulkMeasurableRatingService = bulkMeasurableRatingService; this.userRoleService = userRoleService; } @@ -115,7 +127,12 @@ public void register() { String saveRatingDescriptionPath = mkPath(BASE_URL, "entity", ":kind", ":id", "measurable", ":measurableId", "description"); String saveRatingIsPrimaryPath = mkPath(BASE_URL, "entity", ":kind", ":id", "measurable", ":measurableId", "is-primary"); - registerPreviewBulkMeasurableRatingChanges(mkPath(BASE_URL, "bulk", "preview", ":kind", ":id")); + String bulkRatingPreviewPath = mkPath(BASE_URL, "bulk", "preview", "MEASURABLE_CATEGORY", ":id"); + String bulkRatingApplyPath = mkPath(BASE_URL, "bulk", "apply", "MEASURABLE_CATEGORY", ":id"); + + registerPreviewBulkMeasurableRatingChanges(bulkRatingPreviewPath); + registerApplyBulkMeasurableRatingChanges(bulkRatingApplyPath); + DatumRoute getByIdRoute = (request, response) -> measurableRatingService.getById(getId(request)); @@ -254,11 +271,26 @@ private void checkHasPermissionForThisOperation(EntityReference parentRef, private void registerPreviewBulkMeasurableRatingChanges(String path) { postForDatum(path, (req, resp) -> { - EntityReference measurableRatingRef = getEntityReference(req); - BulkMeasurableItemParser.InputFormat format = readEnum(req, "format", BulkMeasurableItemParser.InputFormat.class, s -> BulkMeasurableItemParser.InputFormat.TSV); - BulkUpdateMode mode = readEnum(req, "mode", BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + EntityReference categoryRef = mkRef(EntityKind.MEASURABLE_CATEGORY, getId(req)); + String modeStr = req.queryParams("mode"); + String formatStr = req.queryParams("format"); + BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + InputFormat format = EnumUtilities.readEnum(formatStr, InputFormat.class, s -> InputFormat.TSV); + String body = req.body(); + return bulkMeasurableRatingService.bulkPreview(categoryRef, body, format, mode); + }); + } + + private void registerApplyBulkMeasurableRatingChanges(String path) { + postForDatum(path, (req, resp) -> { + EntityReference categoryRef = mkRef(EntityKind.MEASURABLE_CATEGORY, getId(req)); + String modeStr = req.queryParams("mode"); + String formatStr = req.queryParams("format"); + BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + InputFormat format = EnumUtilities.readEnum(formatStr, InputFormat.class, s -> InputFormat.TSV); String body = req.body(); - return measurableRatingService.bulkPreview(measurableRatingRef, body, format, mode); + BulkMeasurableRatingValidationResult preview = bulkMeasurableRatingService.bulkPreview(categoryRef, body, format, mode); + return bulkMeasurableRatingService.apply(categoryRef, preview, mode, getUsername(req)); }); } }