From c4a6365b0f1d7f538fc300c4a9ea5233a21f8fb3 Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Mon, 16 Sep 2024 14:30:27 +0530 Subject: [PATCH 01/10] CTCTOWALTZ-3335: Added parser for measurable rating. Integration test for parser. Added bulk preview method to validate and preview the bulk upload for measurable rating. Integration test for bulk preview. --- .../data/application/ApplicationDao.java | 8 +- .../BulkMeasurableRatingServiceTest.java | 72 ++++++++ .../BulkMeasurableRatingItem.java | 29 ++++ .../BulkMeasurableRatingParseResult.java | 41 +++++ .../BulkMeasurableRatingValidatedItem.java | 16 ++ .../BulkMeasurableRatingValidationResult.java | 17 ++ .../measurable_rating/ChangedFieldType.java | 7 + .../measurable_rating/ValidationError.java | 9 + .../BulkMeasurableItemParser.java | 122 +++++++++++++ .../MeasurableRatingService.java | 160 ++++++++++++++++-- .../rating_scheme/RatingSchemeService.java | 5 + .../BulkMeasurableRatingItemParserTest.java | 36 ++++ .../test-measurable-rating-items.tsv | 4 + .../waltz/test_common/helpers/AppHelper.java | 7 +- .../api/MeasurableRatingEndpoint.java | 22 ++- 15 files changed, 527 insertions(+), 28 deletions(-) create mode 100644 waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingItem.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingParseResult.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangedFieldType.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ValidationError.java create mode 100644 waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java create mode 100644 waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv diff --git a/waltz-data/src/main/java/org/finos/waltz/data/application/ApplicationDao.java b/waltz-data/src/main/java/org/finos/waltz/data/application/ApplicationDao.java index db34c2aa94..ab83a5d626 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/application/ApplicationDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/application/ApplicationDao.java @@ -101,7 +101,8 @@ public ApplicationDao(DSLContext dsl) { public Application getById(long id) { - return dsl.select() + return dsl + .select(APPLICATION.fields()) .from(APPLICATION) .where(APPLICATION.ID.eq(id)) .fetchOne(TO_DOMAIN_MAPPER); @@ -110,14 +111,15 @@ public Application getById(long id) { public List findAll() { return dsl - .select() + .select(APPLICATION.fields()) .from(APPLICATION) .fetch(TO_DOMAIN_MAPPER); } public List findByIds(Collection ids) { - return dsl.select() + return dsl + .select(APPLICATION.fields()) .from(APPLICATION) .where(APPLICATION.ID.in(ids)) .fetch(TO_DOMAIN_MAPPER); 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 new file mode 100644 index 0000000000..f822641401 --- /dev/null +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkMeasurableRatingServiceTest.java @@ -0,0 +1,72 @@ +package org.finos.waltz.integration_test.inmem.service; + + +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.measurable_rating.BulkMeasurableRatingValidationResult; +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.test_common.helpers.AppHelper; +import org.finos.waltz.test_common.helpers.MeasurableHelper; +import org.finos.waltz.test_common.helpers.RatingSchemeHelper; +import org.finos.waltz.test_common.helpers.UserHelper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; + +public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest { + @Autowired + private UserHelper userHelper; + + @Autowired + private AppHelper appHelper; + @Autowired + private MeasurableHelper measurableHelper; + + @Autowired + private MeasurableCategoryDao measurableCategoryDao; + + @Autowired + private RatingSchemeHelper ratingSchemeHelper; + + @Autowired + private MeasurableService measurableService; + + @Autowired + private MeasurableRatingService measurableRatingService; + + @Test + public void previewUpdates() { + MeasurableCategory category = measurableCategoryDao.getById(setupCategory()); + measurableHelper.createMeasurable("CT-001", "M1", category.id().get()); + String app1AssetCode = mkName("assetCode1", "previewUpdates"); + EntityReference app1Ref = appHelper.createNewApp( + mkName("app1", "previewUpdates"), + ouIds.root, + app1AssetCode); + + ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), "Rating1", 0, "#111", "R"); + BulkMeasurableRatingValidationResult result = measurableRatingService.bulkPreview( + category.entityReference(), + mkSimpleTsv(app1AssetCode), + BulkMeasurableItemParser.InputFormat.CSV, + BulkUpdateMode.ADD_ONLY); + } + + private long setupCategory() { + String categoryName = mkName("MeasurableRatingChangeServiceTest", "category"); + return measurableHelper.createMeasurableCategory(categoryName); + } + + private String mkSimpleTsv(String assetCode) { + return "assetCode, taxonomyExternalId, ratingCode, isPrimary, comment\n" + +assetCode+", CT-001, R, true, comment\n"; + } +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingItem.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingItem.java new file mode 100644 index 0000000000..0c538f93c8 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingItem.java @@ -0,0 +1,29 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.finos.waltz.model.Nullable; +import org.immutables.value.Value; + +@Value.Immutable +@JsonDeserialize(as = ImmutableBulkMeasurableRatingItem.class) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) +public interface BulkMeasurableRatingItem { + + @JsonAlias({"nar_id", "application_id", "asset_code"}) + String assetCode(); + + @JsonAlias({"taxonomy_external_id", "external_id", "ext_id", "extId"}) + String taxonomyExternalId(); + + char ratingCode(); + + @Value.Default + default boolean isPrimary() { return false; } + + @Nullable + String comment(); +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingParseResult.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingParseResult.java new file mode 100644 index 0000000000..3107285102 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingParseResult.java @@ -0,0 +1,41 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.Nullable; +import org.immutables.value.Value; + +import java.util.List; + +@Value.Immutable +@JsonSerialize(as = ImmutableBulkMeasurableRatingParseResult.class) +public interface BulkMeasurableRatingParseResult { + + @Value.Immutable + interface BulkMeasurableRatingParseError { + String message(); + + @Nullable + Integer line(); + + @Nullable + Integer column(); + } + + List parsedItems(); + + @Nullable String input(); + + @Nullable + BulkMeasurableRatingParseError error(); + + + static BulkMeasurableRatingParseResult mkResult(List items, + String input) { + return ImmutableBulkMeasurableRatingParseResult + .builder() + .parsedItems(items) + .input(input) + .build(); + } + +} 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 new file mode 100644 index 0000000000..752d47e136 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidatedItem.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; + +import java.util.Set; + +@Value.Immutable +@JsonSerialize(as= ImmutableBulkMeasurableRatingValidatedItem.class) +public interface BulkMeasurableRatingValidatedItem { + + BulkMeasurableRatingItem parsedItem(); + ChangeOperation changeOperation(); + Set changedFields(); + Set errors(); +} \ 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 new file mode 100644 index 0000000000..fd03ff02cc --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingValidationResult.java @@ -0,0 +1,17 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.measurable.Measurable; +import org.immutables.value.Value; + +import java.util.List; +import java.util.Set; + +@Value.Immutable +@JsonSerialize(as= ImmutableBulkMeasurableRatingValidationResult.class) +public interface BulkMeasurableRatingValidationResult { + + List validatedItems(); + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangedFieldType.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangedFieldType.java new file mode 100644 index 0000000000..e06b527155 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangedFieldType.java @@ -0,0 +1,7 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + +public enum ChangedFieldType { + RATING, + PRIMARY, + COMMENT +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ValidationError.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ValidationError.java new file mode 100644 index 0000000000..588694b48c --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ValidationError.java @@ -0,0 +1,9 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + +public enum ValidationError { + MEASURABLE_NOT_FOUND, + APPLICATION_NOT_FOUND, + RATING_NOT_FOUND, + MEASURABLE_NOT_CONCRETE, + RATING_NOT_USER_SELECTABLE +} diff --git a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java new file mode 100644 index 0000000000..3cabfee7f5 --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java @@ -0,0 +1,122 @@ +package org.finos.waltz.service.measurable_rating; + +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvParser; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +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.ImmutableBulkMeasurableRatingParseError; +import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingParseResult; + +import java.io.IOException; +import java.util.List; + +import static java.lang.String.format; +import static org.finos.waltz.common.StringUtilities.isEmpty; + +public class BulkMeasurableItemParser { + + public enum InputFormat { + CSV, + TSV, + JSON + } + + + public BulkMeasurableRatingParseResult parse(String input, InputFormat format) { + if (isEmpty(input)) { + return handleEmptyInput(input); + } + + try { + switch (format) { + case CSV: + return parseCSV(input); + case TSV: + return parseTSV(input); + case JSON: + return parseJSON(input); + default: + throw new IllegalArgumentException(format("Unknown format: %s", format)); + } + } catch (IOException e) { + return ImmutableBulkMeasurableRatingParseResult + .builder() + .input(input) + .error(ImmutableBulkMeasurableRatingParseError + .builder() + .message(e.getMessage()) + .build()) + .build(); + } + } + + + private BulkMeasurableRatingParseResult parseJSON(String input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + MappingIterator items = mapper + .readerFor(BulkMeasurableRatingItem.class) + .readValues(input); + + return BulkMeasurableRatingParseResult.mkResult( + items.readAll(), + input); + } + + + private BulkMeasurableRatingParseResult parseCSV(String input) throws IOException { + List items = attemptToParseDelimited(input, configureCSVSchema()); + return BulkMeasurableRatingParseResult.mkResult(items, input); + } + + + private BulkMeasurableRatingParseResult parseTSV(String input) throws IOException { + List items = attemptToParseDelimited(input, configureTSVSchema()); + return BulkMeasurableRatingParseResult.mkResult(items, input); + } + + + private List attemptToParseDelimited(String input, + CsvSchema bootstrapSchema) throws IOException { + CsvMapper mapper = new CsvMapper(); + mapper.enable(CsvParser.Feature.TRIM_SPACES); + MappingIterator items = mapper + .readerFor(BulkMeasurableRatingItem.class) + .with(bootstrapSchema) + .readValues(input); + + return items.readAll(); + } + + + private CsvSchema configureCSVSchema() { + return CsvSchema + .emptySchema() + .withHeader(); + } + + + private CsvSchema configureTSVSchema() { + return CsvSchema + .emptySchema() + .withHeader() + .withColumnSeparator('\t'); + } + + + private BulkMeasurableRatingParseResult handleEmptyInput(String input) { + return ImmutableBulkMeasurableRatingParseResult + .builder() + .input(input) + .error(ImmutableBulkMeasurableRatingParseError + .builder() + .message("Cannot parse an empty string") + .column(0) + .line(0) + .build()) + .build(); + } + +} 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 003a782b5d..0b2095bb7f 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,56 +19,70 @@ 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; import org.finos.waltz.data.application.ApplicationIdSelectorFactory; import org.finos.waltz.data.measurable.MeasurableDao; import org.finos.waltz.data.measurable.MeasurableIdSelectorFactory; 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.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.*; +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.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.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.measurable_rating.*; +import org.finos.waltz.model.rating.RatingScheme; import org.finos.waltz.model.rating.RatingSchemeItem; import org.finos.waltz.model.tally.MeasurableRatingTally; import org.finos.waltz.model.tally.Tally; +import org.finos.waltz.service.application.ApplicationService; import org.finos.waltz.service.changelog.ChangeLogService; +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.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.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; 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; @Service public class MeasurableRatingService { + private static final Logger LOG = LoggerFactory.getLogger(MeasurableRatingService.class); private final MeasurableRatingDao measurableRatingDao; private final MeasurableDao measurableDao; private final MeasurableCategoryDao measurableCategoryDao; private final ChangeLogService changeLogService; private final RatingSchemeService ratingSchemeService; private final EntityReferenceNameResolver entityReferenceNameResolver; + private final MeasurableService measurableService; + + private final MeasurableCategoryService measurableCategoryService; + private final ApplicationDao applicationDao; private static final MeasurableIdSelectorFactory MEASURABLE_ID_SELECTOR_FACTORY = new MeasurableIdSelectorFactory(); private static final ApplicationIdSelectorFactory APPLICATION_ID_SELECTOR_FACTORY = new ApplicationIdSelectorFactory(); @@ -81,12 +95,17 @@ public MeasurableRatingService(MeasurableRatingDao measurableRatingDao, MeasurableCategoryDao measurableCategoryDao, ChangeLogService changeLogService, RatingSchemeService ratingSchemeService, - EntityReferenceNameResolver entityReferenceNameResolver) { + EntityReferenceNameResolver entityReferenceNameResolver, + MeasurableService measurableService, ApplicationService applicationService, MeasurableCategoryService measurableCategoryService, ApplicationDao applicationDao) { + + checkNotNull(measurableRatingDao, "measurableRatingDao cannot be null"); checkNotNull(measurableDao, "measurableDao cannot be null"); checkNotNull(measurableCategoryDao, "measurableCategoryDao cannot be null"); checkNotNull(changeLogService, "changeLogService cannot be null"); checkNotNull(ratingSchemeService, "ratingSchemeService cannot be null"); + checkNotNull(measurableService, "MeasurableService cannot be null"); + checkNotNull(applicationDao, "ApplicationDao cannot be null"); this.measurableRatingDao = measurableRatingDao; this.measurableDao = measurableDao; @@ -94,6 +113,9 @@ public MeasurableRatingService(MeasurableRatingDao measurableRatingDao, this.changeLogService = changeLogService; this.ratingSchemeService = ratingSchemeService; this.entityReferenceNameResolver = entityReferenceNameResolver; + this.measurableService = measurableService; + this.applicationDao = applicationDao; + this.measurableCategoryService = measurableCategoryService; } // -- READ @@ -448,4 +470,110 @@ public Set findPrimaryRatingsForGenericSelector(GenericSelecto public Set findPrimaryRatingsForMeasurableIdSelector(Select> ratingIdSelector) { return measurableRatingDao.findPrimaryRatingsForMeasurableIdSelector(ratingIdSelector); } + + public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurableRatingRef, + String inputStr, + BulkMeasurableItemParser.InputFormat format, + BulkUpdateMode mode) { + + MeasurableCategory category = measurableCategoryService.getById(measurableRatingRef.id()); + List 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); + + BulkMeasurableRatingParseResult result = new BulkMeasurableItemParser().parse(inputStr, format); + List> resolvedEntries = result + .parsedItems() + .stream() + .map(d -> { + Application application = allApplicationsByAssetCode.get(d.assetCode().toLowerCase()); + Measurable measurable = existingByExtId.get(d.taxonomyExternalId()); + RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(d.ratingCode()); + return tuple(d, application, measurable, ratingSchemeItem); + }) + .collect(Collectors.toList()); + + + Collection existingRatings = measurableRatingDao.findByCategory(category.id().get()); + + List requiredRatings = resolvedEntries + .stream() + .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()) + .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 = resolvedEntries + .stream() + .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); + }) + .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-service/src/main/java/org/finos/waltz/service/rating_scheme/RatingSchemeService.java b/waltz-service/src/main/java/org/finos/waltz/service/rating_scheme/RatingSchemeService.java index b972db6335..92dbfa7226 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/rating_scheme/RatingSchemeService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/rating_scheme/RatingSchemeService.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.Set; @Service @@ -88,4 +89,8 @@ public List calcRatingUsageStats() { public Boolean removeRatingScheme(long id) { return ratingSchemeDAO.removeRatingScheme(id); } + + public Set findRatingSchemeItemsBySchemeIds(Set schemeIds) { + return ratingSchemeDAO.findRatingSchemeItemsForSchemeIds(schemeIds); + } } diff --git a/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java new file mode 100644 index 0000000000..5d138b1d84 --- /dev/null +++ b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java @@ -0,0 +1,36 @@ +package org.finos.waltz.service.measurable_rating; + +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingItem; +import org.finos.waltz.model.bulk_upload.measurable_rating.BulkMeasurableRatingParseResult; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.finos.waltz.common.IOUtilities.readAsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class BulkMeasurableRatingItemParserTest { + + private final BulkMeasurableItemParser parser = new BulkMeasurableItemParser(); + + @Test + void simpleTSV() { + BulkMeasurableRatingParseResult result = parser.parse(readTestFile("test-measurable-rating-items.tsv"), BulkMeasurableItemParser.InputFormat.TSV); + assertNull(result.error()); + assertEquals(3, result.parsedItems().size()); + assertNarIds(result.parsedItems(), "109235-1", "109235-1", "324234-1"); + } + + private void assertNarIds(List parsedItems, String... resultNarIds) { + Set narIds = SetUtilities.map(parsedItems, BulkMeasurableRatingItem::assetCode); + Set expectedNarIds = SetUtilities.asSet(resultNarIds); + assertEquals(expectedNarIds, narIds); + } + + private String readTestFile(String fileName) { + return readAsString(BulkMeasurableRatingItemParserTest.class.getResourceAsStream(fileName)); + } +} diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv new file mode 100644 index 0000000000..ef58412b66 --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv @@ -0,0 +1,4 @@ +narId taxonomyExternalId ratingCode isPrimary comment +109235-1 CT-001 A TRUE comment +109235-1 CT-001 A FALSE comment +324234-1 CT-003 A FALSE comment diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AppHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AppHelper.java index 5b7c5da2c2..c6cdcded62 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AppHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AppHelper.java @@ -20,11 +20,16 @@ public class AppHelper { public EntityReference createNewApp(String name, Long ouId) { + return createNewApp(name, ouId, name); + } + + + public EntityReference createNewApp(String name, Long ouId, String assetCode) { AppRegistrationResponse resp = applicationSvc .registerApp( ImmutableAppRegistrationRequest.builder() .name(name) - .assetCode(name) + .assetCode(assetCode) .organisationalUnitId(ouId != null ? ouId : 1L) .applicationKind(ApplicationKind.IN_HOUSE) .businessCriticality(Criticality.MEDIUM) 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 0958080d2d..1c6ce6910b 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 @@ -25,6 +25,7 @@ import org.finos.waltz.model.Operation; 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.measurable_rating.ImmutableRemoveMeasurableRatingCommand; import org.finos.waltz.model.measurable_rating.MeasurableRating; import org.finos.waltz.model.measurable_rating.MeasurableRatingCategoryView; @@ -33,6 +34,7 @@ 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.MeasurableRatingService; import org.finos.waltz.service.measurable_rating.MeasurableRatingViewService; import org.finos.waltz.service.permission.permission_checker.MeasurableRatingPermissionChecker; @@ -54,14 +56,8 @@ 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.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.WebUtilities.*; +import static org.finos.waltz.web.WebUtilities.readEnum; 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; @@ -119,6 +115,7 @@ 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")); DatumRoute getByIdRoute = (request, response) -> measurableRatingService.getById(getId(request)); @@ -255,4 +252,13 @@ private void checkHasPermissionForThisOperation(EntityReference parentRef, measurableRatingPermissionChecker.verifyAnyPerms(operations, perms, MEASURABLE_RATING, username); } + 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); + String body = req.body(); + return measurableRatingService.bulkPreview(measurableRatingRef, body, format, mode); + }); + } } From 2a31afdf573dad35e0aaec3f77ac0cc5dd5ff105 Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Mon, 16 Sep 2024 14:35:27 +0530 Subject: [PATCH 02/10] CTCTOWALTZ-3335: Added parser for measurable rating. Integration test for parser. Added bulk preview method to validate and preview the bulk upload for measurable rating. Integration test for bulk preview. --- pom.xml | 5 +++ .../finos/waltz/common/StringUtilities.java | 35 +++++++++++++++++-- .../measurable_rating/ChangeOperation.java | 9 +++++ waltz-service/pom.xml | 4 +++ .../test_common/helpers/MeasurableHelper.java | 35 ++++++++++++++----- 5 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangeOperation.java diff --git a/pom.xml b/pom.xml index fd485e53bd..cf6b79deff 100644 --- a/pom.xml +++ b/pom.xml @@ -178,6 +178,11 @@ jackson-dataformat-yaml ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + ${jackson.version} + com.fasterxml.jackson.datatype jackson-datatype-jdk8 diff --git a/waltz-common/src/main/java/org/finos/waltz/common/StringUtilities.java b/waltz-common/src/main/java/org/finos/waltz/common/StringUtilities.java index 72be7336aa..c68d5d0b27 100644 --- a/waltz-common/src/main/java/org/finos/waltz/common/StringUtilities.java +++ b/waltz-common/src/main/java/org/finos/waltz/common/StringUtilities.java @@ -256,7 +256,7 @@ public static String sanitizeCharacters(String str) { .replaceAll("“", "\"") .replaceAll("”", "\"") .replaceAll("’", "'") - .replaceAll(" ", " "); + .replaceAll(" ", " "); } } @@ -327,5 +327,36 @@ public static Optional firstNonNull(String... xs) { .findFirst(); } -} + public static boolean safeEq(String expected, String actual) { + if(actual == null && expected == null) { + return true; + } else if(actual == null || expected == null) { + return false; + } else { + return actual.equals(expected); + } + } + + + public static boolean safeEqIgnoreCase(String expected, String actual) { + if(actual == null && expected == null) { + return true; + } else if(actual == null || expected == null) { + return false; + } else { + return actual.equalsIgnoreCase(expected); + } + } + + + public static String mkExternalId(String str) { + return sanitizeCharacters(mkSafe(str)) + .toUpperCase() + .replaceAll("&", " AND ") + .replaceAll("[\\s\\-(){}/\\\\,.*;]", "_") + .replaceAll("_+", "_"); + } + + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangeOperation.java b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangeOperation.java new file mode 100644 index 0000000000..8fcecb2284 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/ChangeOperation.java @@ -0,0 +1,9 @@ +package org.finos.waltz.model.bulk_upload.measurable_rating; + +public enum ChangeOperation { + ADD, + REMOVE, + RESTORE, + UPDATE, + NONE +} diff --git a/waltz-service/pom.xml b/waltz-service/pom.xml index 3a4d79c72a..844187b794 100644 --- a/waltz-service/pom.xml +++ b/waltz-service/pom.xml @@ -107,6 +107,10 @@ junit-jupiter-engine test + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + 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 ce024901b6..c3a599b336 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 @@ -1,6 +1,7 @@ package org.finos.waltz.test_common.helpers; import org.finos.waltz.common.CollectionUtilities; +import org.finos.waltz.model.EntityLifecycleStatus; import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.measurable_category.MeasurableCategory; import org.finos.waltz.schema.tables.records.MeasurableCategoryRecord; @@ -16,6 +17,7 @@ import static org.finos.waltz.common.DateTimeUtilities.nowUtcTimestamp; import static org.finos.waltz.common.DateTimeUtilities.toSqlDate; +import static org.finos.waltz.common.StringUtilities.mkExternalId; import static org.finos.waltz.schema.Tables.MEASURABLE; import static org.finos.waltz.schema.Tables.MEASURABLE_CATEGORY; import static org.finos.waltz.schema.Tables.MEASURABLE_RATING; @@ -75,6 +77,14 @@ public void updateCategoryNotEditable(long categoryId) { public long createMeasurable(String name, long categoryId) { + return createMeasurable( + mkExternalId(name), + name, + categoryId); + } + + + public long createMeasurable(String externalId, String name, long categoryId) { return dsl .select(MEASURABLE.ID) .from(MEASURABLE) @@ -87,7 +97,7 @@ public long createMeasurable(String name, long categoryId) { record.setName(name); record.setDescription(name); record.setConcrete(true); - record.setExternalId(name); + record.setExternalId(externalId); record.setProvenance(PROVENANCE); record.setLastUpdatedBy(LAST_UPDATE_USER); record.setLastUpdatedAt(nowUtcTimestamp()); @@ -114,13 +124,13 @@ public long createRating(EntityReference ref, long measurableId) { // I know this is daft, however it doesn't work if we just return ratingRecord.getId() - instead it returns lastUpdatedAt in millis ?! return dsl - .select(MEASURABLE_RATING.ID) - .from(MEASURABLE_RATING) - .where(MEASURABLE_RATING.ENTITY_ID.eq(ref.id()) - .and(MEASURABLE_RATING.ENTITY_KIND.eq(ref.kind().name())) - .and(MEASURABLE_RATING.MEASURABLE_ID.eq(measurableId))) - .fetchOne() - .get(MEASURABLE_RATING.ID); + .select(MEASURABLE_RATING.ID) + .from(MEASURABLE_RATING) + .where(MEASURABLE_RATING.ENTITY_ID.eq(ref.id()) + .and(MEASURABLE_RATING.ENTITY_KIND.eq(ref.kind().name())) + .and(MEASURABLE_RATING.MEASURABLE_ID.eq(measurableId))) + .fetchOne() + .get(MEASURABLE_RATING.ID); } @@ -152,4 +162,11 @@ public void updateMeasurableReadOnly(EntityReference ref, long measurableId) { .execute(); } -} + public int updateMeasurableLifecycleStatus(long measurableId, EntityLifecycleStatus newStatus) { + return dsl + .update(MEASURABLE) + .set(MEASURABLE.ENTITY_LIFECYCLE_STATUS, newStatus.name()) + .where(MEASURABLE.ID.eq(measurableId)) + .execute(); + } +} \ No newline at end of file From e3983aad98e94d05d52ef02b3447882703d67ad0 Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Tue, 17 Sep 2024 13:54:39 +0530 Subject: [PATCH 03/10] CTCTOWALTZ-3335: Modified Validation Result entity to support error. --- .../BulkMeasurableRatingValidationResult.java | 4 ++ .../MeasurableRatingService.java | 58 ++++++++++--------- .../test-measurable-rating-items.tsv | 2 +- 3 files changed, 37 insertions(+), 27 deletions(-) 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 fd03ff02cc..005f6000e8 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,6 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.Nullable; import org.finos.waltz.model.measurable.Measurable; import org.immutables.value.Value; @@ -14,4 +15,7 @@ public interface BulkMeasurableRatingValidationResult { List validatedItems(); + @Nullable + BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError error(); + } 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 0b2095bb7f..1ad2511552 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 @@ -476,6 +476,13 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab 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(measurableRatingRef.id()); List existingMeasurables = measurableService.findByCategoryId(measurableRatingRef.id()); Map existingByExtId = indexBy(existingMeasurables, m -> m.externalId().orElse(null)); @@ -489,8 +496,8 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab Set ratingSchemeItemsBySchemeIds = ratingSchemeService.findRatingSchemeItemsBySchemeIds(asSet(category.ratingSchemeId())); Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); - BulkMeasurableRatingParseResult result = new BulkMeasurableItemParser().parse(inputStr, format); - List> resolvedEntries = result + + List>> validatedEntries = result .parsedItems() .stream() .map(d -> { @@ -499,13 +506,33 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(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 = resolvedEntries + List requiredRatings = validatedEntries .stream() + .filter(t -> t.v1 != null && t.v2 != null && t.v3 != null) .map(t -> ImmutableMeasurableRating .builder() .entityReference(t.v2.entityReference()) @@ -530,29 +557,8 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab 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 = resolvedEntries + List validatedItems = validatedEntries .stream() - .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); - }) .map(t -> { Tuple2 recordKey = tuple(t.v2.entityReference(), t.v3.id().get()); if (toAdd.contains(recordKey)) { diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv index ef58412b66..d33e9f435f 100644 --- a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.tsv @@ -1,4 +1,4 @@ -narId taxonomyExternalId ratingCode isPrimary comment +assetCode taxonomyExternalId ratingCode isPrimary comment 109235-1 CT-001 A TRUE comment 109235-1 CT-001 A FALSE comment 324234-1 CT-003 A FALSE comment From ddbcd061abdf26e0525120f4f5033aeab8a631ac Mon Sep 17 00:00:00 2001 From: saramee Date: Wed, 18 Sep 2024 23:39:08 +0530 Subject: [PATCH 04/10] tests --- .../BulkMeasurableItemParser.java | 18 ++++++-- .../BulkMeasurableRatingItemParserTest.java | 46 ++++++++++++++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java index 3cabfee7f5..13c9095f34 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableItemParser.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvParser; import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import org.finos.waltz.common.StreamUtilities; +import org.finos.waltz.common.StringUtilities; 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.ImmutableBulkMeasurableRatingParseError; @@ -12,6 +14,7 @@ import java.io.IOException; import java.util.List; +import java.util.stream.Collectors; import static java.lang.String.format; import static org.finos.waltz.common.StringUtilities.isEmpty; @@ -33,11 +36,11 @@ public BulkMeasurableRatingParseResult parse(String input, InputFormat format) { try { switch (format) { case CSV: - return parseCSV(input); + return parseCSV(clean(input)); case TSV: - return parseTSV(input); + return parseTSV(clean(input)); case JSON: - return parseJSON(input); + return parseJSON(clean(input)); default: throw new IllegalArgumentException(format("Unknown format: %s", format)); } @@ -82,6 +85,7 @@ private List attemptToParseDelimited(String input, CsvSchema bootstrapSchema) throws IOException { CsvMapper mapper = new CsvMapper(); mapper.enable(CsvParser.Feature.TRIM_SPACES); + mapper.enable(CsvParser.Feature.SKIP_EMPTY_LINES); MappingIterator items = mapper .readerFor(BulkMeasurableRatingItem.class) .with(bootstrapSchema) @@ -119,4 +123,12 @@ private BulkMeasurableRatingParseResult handleEmptyInput(String input) { .build(); } + private String clean(String input) { + return StreamUtilities + .lines(input) + .filter(StringUtilities::isDefined) + .filter(line -> ! line.startsWith("#")) + .collect(Collectors.joining("\n")); + } + } diff --git a/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java index 5d138b1d84..5ea3fd99fe 100644 --- a/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java +++ b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java @@ -3,14 +3,15 @@ import org.finos.waltz.common.SetUtilities; 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.taxonomy.BulkTaxonomyParseResult; +import org.finos.waltz.service.taxonomy_management.BulkTaxonomyItemParser; import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; import static org.finos.waltz.common.IOUtilities.readAsString; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.*; public class BulkMeasurableRatingItemParserTest { @@ -33,4 +34,45 @@ private void assertNarIds(List parsedItems, String... private String readTestFile(String fileName) { return readAsString(BulkMeasurableRatingItemParserTest.class.getResourceAsStream(fileName)); } + + @Test + void parseFailsIfGivenEmpty() { + BulkMeasurableRatingParseResult result = parser.parse("", BulkMeasurableItemParser.InputFormat.TSV); + expectEmptyError(result); + } + + @Test + void parseFailsIfGivenNull() { + BulkMeasurableRatingParseResult result = parser.parse(null, BulkMeasurableItemParser.InputFormat.TSV); + expectEmptyError(result); + } + + @Test + void parseFailsIfGivenBlanks() { + BulkMeasurableRatingParseResult result = parser.parse(" \t\n ", BulkMeasurableItemParser.InputFormat.TSV); + expectEmptyError(result); + } + + @Test + void parseIgnoresEmptyLines() { + BulkMeasurableRatingParseResult result = parser.parse(readTestFile("test-measurable-rating-items-with-blanks.tsv"), BulkMeasurableItemParser.InputFormat.TSV); + assertEquals(3, result.parsedItems().size()); + assertNarIds(result.parsedItems(), "109235-1", "109235-1", "324234-1"); + } + + @Test + void parseIgnoresCommentedLines() { + BulkMeasurableRatingParseResult result = parser.parse(readTestFile("test-measurable-rating-items-with-commented-lines.csv"), BulkMeasurableItemParser.InputFormat.CSV); + assertEquals(2, result.parsedItems().size()); + assertNarIds(result.parsedItems(), "109235-1", "324234-1"); + } + + private void expectEmptyError(BulkMeasurableRatingParseResult result) { + BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError err = result.error(); + assertNotNull(err); + assertTrue(err.message().contains("empty")); + assertEquals(0, err.column()); + assertEquals(0, err.line()); + } + } From a1cff78ee8291d55a559e68e902f07120414eff3 Mon Sep 17 00:00:00 2001 From: saramee Date: Wed, 18 Sep 2024 23:44:44 +0530 Subject: [PATCH 05/10] test files --- .../test-measurable-rating-items-with-blanks.tsv | 6 ++++++ .../test-measurable-rating-items-with-commented-lines.csv | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-blanks.tsv create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-commented-lines.csv diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-blanks.tsv b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-blanks.tsv new file mode 100644 index 0000000000..92fdc3194f --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-blanks.tsv @@ -0,0 +1,6 @@ +assetCode taxonomy_external_id ratingCode isPrimary comment +109235-1 CT-001 A TRUE comment + + +109235-1 CT-001 A FALSE comment +324234-1 CT-003 A FALSE comment \ No newline at end of file diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-commented-lines.csv b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-commented-lines.csv new file mode 100644 index 0000000000..beafd991a9 --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items-with-commented-lines.csv @@ -0,0 +1,6 @@ +assetCode, taxonomy_external_id, ratingCode, isPrimary, comment +#109235-1 CT-001 A TRUE comment + + +109235-1, CT-001, A, FALSE, comment +324234-1, CT-003, A, FALSE, comment \ No newline at end of file From 9361cd783464bae1ca81e06aaba28df3406d087e Mon Sep 17 00:00:00 2001 From: saramee Date: Thu, 19 Sep 2024 00:04:41 +0530 Subject: [PATCH 06/10] additional tests --- .../BulkMeasurableRatingItemParserTest.java | 16 +++++++++++++ .../test-measurable-rating-items.csv | 4 ++++ .../test-measurable-rating-items.json | 23 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.csv create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.json diff --git a/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java index 5ea3fd99fe..27b8ef21d3 100644 --- a/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java +++ b/waltz-service/src/test/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingItemParserTest.java @@ -25,6 +25,22 @@ void simpleTSV() { assertNarIds(result.parsedItems(), "109235-1", "109235-1", "324234-1"); } + @Test + void simpleCSV() { + BulkMeasurableRatingParseResult result = parser.parse(readTestFile("test-measurable-rating-items.csv"), BulkMeasurableItemParser.InputFormat.CSV); + assertNull(result.error()); + assertEquals(3, result.parsedItems().size()); + assertNarIds(result.parsedItems(), "109235-1", "109235-1", "324234-1"); + } + + @Test + void simpleJSON() { + BulkMeasurableRatingParseResult result = parser.parse(readTestFile("test-measurable-rating-items.json"), BulkMeasurableItemParser.InputFormat.JSON); + assertNull(result.error()); + assertEquals(3, result.parsedItems().size()); + assertNarIds(result.parsedItems(), "109235-1", "109235-1", "324234-1"); + } + private void assertNarIds(List parsedItems, String... resultNarIds) { Set narIds = SetUtilities.map(parsedItems, BulkMeasurableRatingItem::assetCode); Set expectedNarIds = SetUtilities.asSet(resultNarIds); diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.csv b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.csv new file mode 100644 index 0000000000..fc4204cf76 --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.csv @@ -0,0 +1,4 @@ +assetCode, taxonomyExternalId, ratingCode, isPrimary, comment +109235-1, CT-001, A, TRUE, comment +109235-1, CT-001, A, FALSE, comment +324234-1, CT-003, A, FALSE, comment diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.json b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.json new file mode 100644 index 0000000000..f85ea6844a --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/measurable_rating/test-measurable-rating-items.json @@ -0,0 +1,23 @@ +[ + { + "assetCode": "109235-1", + "taxonomyExternalId": "CT-001", + "ratingCode": "A", + "isPrimary": "TRUE", + "comment": "comment" + }, + { + "assetCode": "109235-1", + "taxonomyExternalId": "CT-001", + "ratingCode": "A", + "isPrimary": "FALSE", + "comment": "comment" + }, + { + "assetCode": "324234-1", + "taxonomyExternalId": "CT-003", + "ratingCode": "A", + "isPrimary": "FALSE", + "comment": "comment" + } +] \ No newline at end of file From f65a29906fef3f891526a52f72bf0c3cf1275301 Mon Sep 17 00:00:00 2001 From: saramee Date: Fri, 20 Sep 2024 15:09:11 +0530 Subject: [PATCH 07/10] integration tests changes --- .../BulkMeasurableRatingServiceTest.java | 57 ++++++++++++++++++- .../MeasurableRatingService.java | 11 +++- 2 files changed, 64 insertions(+), 4 deletions(-) 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 f822641401..3b805f0056 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,12 +1,16 @@ package org.finos.waltz.integration_test.inmem.service; +import jdk.jfr.internal.Logger; +import org.finos.waltz.common.ListUtilities; 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.BulkMeasurableRatingValidationResult; +import org.finos.waltz.model.bulk_upload.taxonomy.BulkTaxonomyValidationResult; import org.finos.waltz.model.measurable_category.MeasurableCategory; import org.finos.waltz.service.measurable.MeasurableService; import org.finos.waltz.service.measurable_rating.BulkMeasurableItemParser; @@ -18,8 +22,14 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + +import static org.finos.waltz.common.CollectionUtilities.all; +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.test_common.helpers.NameHelper.mkName; +import static org.junit.jupiter.api.Assertions.*; public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest { @Autowired @@ -43,7 +53,7 @@ public class BulkMeasurableRatingServiceTest extends BaseInMemoryIntegrationTest private MeasurableRatingService measurableRatingService; @Test - public void previewUpdates() { + public void previewUpdatesSuccess() { MeasurableCategory category = measurableCategoryDao.getById(setupCategory()); measurableHelper.createMeasurable("CT-001", "M1", category.id().get()); String app1AssetCode = mkName("assetCode1", "previewUpdates"); @@ -58,6 +68,32 @@ public void previewUpdates() { mkSimpleTsv(app1AssetCode), BulkMeasurableItemParser.InputFormat.CSV, BulkUpdateMode.ADD_ONLY); + + assertNotNull(result, "Expected a result"); + assertNoErrors(result); + assertTaxonomyExternalIdMatch(result, asList("CT-001")); + + } + + @Test + public void previewUpdatesError() { + MeasurableCategory category = measurableCategoryDao.getById(setupCategory()); + measurableHelper.createMeasurable("CT-001", "M1", category.id().get()); + String app1AssetCode = mkName("assetCode1", "previewUpdates"); + EntityReference app1Ref = appHelper.createNewApp( + mkName("app1", "previewUpdates"), + ouIds.root, + app1AssetCode); + ratingSchemeHelper.saveRatingItem(category.ratingSchemeId(), "Rating1", 0, "#111", "R"); + BulkMeasurableRatingValidationResult result = measurableRatingService.bulkPreview( + category.entityReference(), + mkSimpleTsv(), + BulkMeasurableItemParser.InputFormat.CSV, + BulkUpdateMode.ADD_ONLY); + + assertNotNull(result, "Expected a result"); + assertNoErrors(result); + assertTaxonomyExternalIdMatch(result, asList("CT-001")); } private long setupCategory() { @@ -69,4 +105,23 @@ private String mkSimpleTsv(String assetCode) { return "assetCode, taxonomyExternalId, ratingCode, isPrimary, comment\n" +assetCode+", CT-001, R, true, comment\n"; } + + private void assertTaxonomyExternalIdMatch(BulkMeasurableRatingValidationResult result, + List taxonomyExternalId) { + assertEquals( + taxonomyExternalId, + ListUtilities.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" ; + } } 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 1ad2511552..1ef309467d 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 @@ -63,6 +63,7 @@ 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; @@ -503,7 +504,7 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab .map(d -> { Application application = allApplicationsByAssetCode.get(d.assetCode().toLowerCase()); Measurable measurable = existingByExtId.get(d.taxonomyExternalId()); - RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(d.ratingCode()); + RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); return tuple(d, application, measurable, ratingSchemeItem); }) .map(t -> { @@ -529,17 +530,19 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab .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.v3 != null) + .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()); @@ -557,8 +560,10 @@ public BulkMeasurableRatingValidationResult bulkPreview(EntityReference measurab 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)) { From 0b11adc3537b37bd34713f20cd9b92438164348d Mon Sep 17 00:00:00 2001 From: David Watkins Date: Fri, 20 Sep 2024 18:15:21 +0100 Subject: [PATCH 08/10] Bulk Measurable Ratings - preview shows removals etc - apply does changes and creates changelog entries #CTCTOWALTZ-3335 #7145 --- .../MeasurableRatingDao.java | 10 +- ...er.java => MeasurableRatingUtilities.java} | 2 +- .../BulkMeasurableRatingServiceTest.java | 181 ++++++-- .../BulkMeasurableRatingApplyResult.java | 16 + .../BulkMeasurableRatingValidatedItem.java | 16 + .../BulkMeasurableRatingValidationResult.java | 10 +- .../BulkMeasurableRatingService.java | 392 ++++++++++++++++++ .../MeasurableRatingService.java | 154 +------ .../test_common/helpers/MeasurableHelper.java | 3 +- .../helpers/MeasurableRatingHelper.java | 27 ++ .../api/MeasurableRatingEndpoint.java | 48 ++- 11 files changed, 677 insertions(+), 182 deletions(-) rename waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/{MeasurableRatingHelper.java => MeasurableRatingUtilities.java} (99%) create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/bulk_upload/measurable_rating/BulkMeasurableRatingApplyResult.java create mode 100644 waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/BulkMeasurableRatingService.java create mode 100644 waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/MeasurableRatingHelper.java 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)); }); } } From 85ecde6cb27bd8d400e5524bd2c27861b872f851 Mon Sep 17 00:00:00 2001 From: David Watkins Date: Tue, 24 Sep 2024 09:27:07 +0100 Subject: [PATCH 09/10] Bulk Measurable Rating import #CTCTOWALTZ-3335 #7145 --- .../BulkTaxonomyEditor.svelte | 11 +- .../pages/edit/measurable-category-edit.html | 14 +- .../pages/edit/measurable-category-edit.js | 4 +- .../BulkRatingEditor.svelte | 239 ++++++++++++++++++ .../svelte-stores/measurable-rating-store.js | 16 +- 5 files changed, 266 insertions(+), 18 deletions(-) create mode 100644 waltz-ng/client/measurable-rating/components/bulk-rating-editor/BulkRatingEditor.svelte diff --git a/waltz-ng/client/measurable-category/components/bulk-taxonomy-editor/BulkTaxonomyEditor.svelte b/waltz-ng/client/measurable-category/components/bulk-taxonomy-editor/BulkTaxonomyEditor.svelte index 84e8fb4c28..b1830a4d82 100644 --- a/waltz-ng/client/measurable-category/components/bulk-taxonomy-editor/BulkTaxonomyEditor.svelte +++ b/waltz-ng/client/measurable-category/components/bulk-taxonomy-editor/BulkTaxonomyEditor.svelte @@ -134,12 +134,11 @@ a1.2\t a1\t A1_2\t Second child\t true` For example: -
-            externalId	 parentExternalId	 name	 description	 concrete
-            a1		 A1	 Root node	 false
-            a1.1	 a1	 A1_1	 First child	 true
-            a1.2	 a1	 A1_2	 Second child	 true
-        
+
+externalId	 parentExternalId	 name	 description	 concrete
+a1		 A1	 Root node	 false
+a1.1	 a1	 A1_1	 First child	 true
+a1.2	 a1	 A1_2	 Second child	 true
Note, removal of items should be done via the Interactive Taxonomy Editor panel. diff --git a/waltz-ng/client/measurable-category/pages/edit/measurable-category-edit.html b/waltz-ng/client/measurable-category/pages/edit/measurable-category-edit.html index 7b297125cb..cd05f0bbe1 100644 --- a/waltz-ng/client/measurable-category/pages/edit/measurable-category-edit.html +++ b/waltz-ng/client/measurable-category/pages/edit/measurable-category-edit.html @@ -74,7 +74,7 @@