From 9aabd128bcb54d76dbdb88b8d8f67fa9e8a956c6 Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Fri, 11 Oct 2024 18:38:18 +0530 Subject: [PATCH 1/7] CTCTOWALTZ-3337: [x] Created Parser to parse the assessment rating input. [x] Preview Add implementation of all entity kinds [x] UI changes to support bulk edit for all entity kinds with new editor. [x] Removed existing bulk editor for Application entity kind. #7156 --- .../BulkAssessmentRatingServiceTest.java | 131 ++++++++ .../AssessmentRatingParsedItem.java | 25 ++ .../AssessmentRatingParsedResult.java | 40 +++ .../AssessmentRatingValidatedItem.java | 32 ++ .../AssessmentRatingValidationResult.java | 26 ++ .../bulk_upload/ChangeOperation.java | 9 + .../bulk_upload/ChangedFieldType.java | 8 + .../bulk_upload/ValidationError.java | 6 + .../BulkAssessmentRatingSelector.svelte | 304 ++++++++++++++++++ .../edit/assessment-rating-bulk-upload.html | 10 +- .../edit/assessment-rating-bulk-upload.js | 7 +- .../pages/view/assessment-definition-view.js | 3 +- .../svelte-stores/assessment-rating-store.js | 10 +- .../BulkAssessmentRatingItemParser.java | 103 ++++++ .../BulkAssessmentRatingService.java | 182 +++++++++++ ...AssessmentRatingServiceItemParserTest.java | 35 ++ .../test-assessment-rating-items.tsv | 2 + .../test_common/helpers/ActorHelper.java | 3 + .../api/AssessmentDefinitionEndpoint.java | 34 +- 19 files changed, 962 insertions(+), 8 deletions(-) create mode 100644 waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedResult.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidatedItem.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidationResult.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangeOperation.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangedFieldType.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java create mode 100644 waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte create mode 100644 waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingItemParser.java create mode 100644 waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java create mode 100644 waltz-service/src/test/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingServiceItemParserTest.java create mode 100644 waltz-service/src/test/resources/org/finos/waltz/service/assessment_rating/test-assessment-rating-items.tsv diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java new file mode 100644 index 0000000000..34f29aa305 --- /dev/null +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java @@ -0,0 +1,131 @@ +package org.finos.waltz.integration_test.inmem.service; +import org.finos.waltz.common.ListUtilities; +import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.assessment_definition.AssessmentDefinition; +import org.finos.waltz.model.assessment_definition.AssessmentVisibility; +import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingValidationResult; +import org.finos.waltz.model.bulk_upload.BulkUpdateMode; +import org.finos.waltz.service.application.ApplicationService; +import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingItemParser; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingService; +import org.finos.waltz.test_common.helpers.ActorHelper; +import org.finos.waltz.test_common.helpers.AppHelper; +import org.finos.waltz.test_common.helpers.AssessmentHelper; +import org.finos.waltz.test_common.helpers.RatingSchemeHelper; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import static org.finos.waltz.common.ListUtilities.asList; + + +import java.util.List; +import java.util.Optional; + +import static org.finos.waltz.common.CollectionUtilities.all; +import static org.finos.waltz.common.CollectionUtilities.isEmpty; +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 BulkAssessmentRatingServiceTest extends BaseInMemoryIntegrationTest { + private static final Logger LOG = LoggerFactory.getLogger(BulkAssessmentRatingServiceTest.class); + + @Autowired + private AssessmentHelper assessmentHelper; + @Autowired + private RatingSchemeHelper ratingSchemeHelper; + + @Autowired + private ApplicationService applicationService; + + @Autowired + private BulkAssessmentRatingService bulkAssessmentRatingService; + + @Autowired + private AppHelper appHelper; + + @Autowired + private ActorHelper actorHelper; + + @Autowired + private AssessmentDefinitionService assessmentDefinitionService; + + private static final String stem = "BAR"; + + @Test + public void previewAdds() { + /** + * Entity Kind: APPLICATION + */ + String name = mkName(stem, "previewApp"); + String kindExternalId = mkName(stem, "previewAppCode"); + Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(name + "SchemeApp"); + ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 10, "green", "Y"); + appHelper.createNewApp( + mkName(stem, "previewUpdatesApp"), + ouIds.root, + kindExternalId); + AssessmentDefinition def1 = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.APPLICATION, schemeId, name)); + + AssessmentRatingValidationResult result1 = bulkAssessmentRatingService.bulkPreview( + mkRef(def1.entityKind(), def1.id().get()), + mkGoodTsv(kindExternalId), + BulkAssessmentRatingItemParser.InputFormat.TSV, + BulkUpdateMode.ADD_ONLY); + + assertNotNull(result1, "Expected a result"); + assertNoErrors(result1); + assertExternalIdsMatch(result1, asList(kindExternalId)); + + /** + * Entity Kind: ACTOR + * Need work as it is failing. external id is coming as null + */ +// String actorName = mkName(stem, "previewActor"); +// String actorExternalId = mkName(stem, "previewActorCode"); +// Long actorSchemeId = ratingSchemeHelper.createEmptyRatingScheme(name + "SchemeActor"); +// ratingSchemeHelper.saveRatingItem(actorSchemeId, "Yes", 10, "green", "Z"); +// actorHelper.createActor(actorName); +// AssessmentDefinition def2 = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.ACTOR, actorSchemeId, actorName)); +// +// AssessmentRatingValidationResult result2 = bulkAssessmentRatingService.bulkPreview( +// mkRef(def2.entityKind(), def2.id().get()), +// mkGoodTsv(actorExternalId), +// BulkAssessmentRatingItemParser.InputFormat.TSV, +// BulkUpdateMode.ADD_ONLY); +// +// assertNotNull(result2, "Expected a result"); +// assertNoErrors(result2); +// assertExternalIdsMatch(result2, asList(actorExternalId)); + } + + private long getAssessmentDefinition(EntityKind kind, Long schemeId, String name) { + return assessmentHelper.createDefinition(schemeId, name + "Definition", "", AssessmentVisibility.PRIMARY, "Test", kind, null); + } + private void assertNoErrors(AssessmentRatingValidationResult result) { + assertTrue( + all(result.validatedItems(), d -> isEmpty(d.errors())), + "Should have no errors"); + } + + private void assertExternalIdsMatch(AssessmentRatingValidationResult result, + List expectedExternalIds) { + assertEquals( + expectedExternalIds, + ListUtilities.map(result.validatedItems(), d -> d.parsedItem().externalId()), + "Expected external ids do not match"); + } + private String mkGoodTsv(String externalId) { + return "externalId\tratingCode\tisReadOnly\tcomment\n" + + externalId + "\tY\ttrue\tcomment\n"; + } + +} +/** + * previewAdd + * Test case around cardinality check + * previewUpdate + */ \ No newline at end of file diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java new file mode 100644 index 0000000000..739730c2ef --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java @@ -0,0 +1,25 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +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 = ImmutableAssessmentRatingParsedItem.class) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) +public interface AssessmentRatingParsedItem { + @JsonAlias("external_id") + String externalId(); + + char ratingCode(); + + @Value.Default + default boolean isReadOnly() { return false; } + + @Nullable + String comment(); +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedResult.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedResult.java new file mode 100644 index 0000000000..446c6b559b --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedResult.java @@ -0,0 +1,40 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +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 = ImmutableAssessmentRatingParsedResult.class) +public interface AssessmentRatingParsedResult { + + @Value.Immutable + interface AssessmentRatingParseError { + String message(); + + @Nullable + Integer line(); + + @Nullable + Integer column(); + } + + List parsedItems(); + + @Nullable String input(); + + @Nullable + AssessmentRatingParseError error(); + + + static AssessmentRatingParsedResult mkResult(List items, + String input) { + return ImmutableAssessmentRatingParsedResult + .builder() + .parsedItems(items) + .input(input) + .build(); + } +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidatedItem.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidatedItem.java new file mode 100644 index 0000000000..5042b07e18 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidatedItem.java @@ -0,0 +1,32 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.Nullable; +import org.finos.waltz.model.rating.RatingSchemeItem; +import org.immutables.value.Value; + +import java.util.Set; + +@Value.Immutable +@JsonDeserialize(as = ImmutableAssessmentRatingValidatedItem.class) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) +public interface AssessmentRatingValidatedItem { + + AssessmentRatingParsedItem parsedItem(); + + ChangeOperation changeOperation(); + + Set changedFields(); + + Set errors(); + + @Nullable + RatingSchemeItem ratingSchemeItem(); + + @Nullable + EntityReference entityKindReference(); +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidationResult.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidationResult.java new file mode 100644 index 0000000000..9ea94c3d27 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingValidationResult.java @@ -0,0 +1,26 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.Nullable; +import org.immutables.value.Value; +import org.jooq.lambda.tuple.Tuple2; + +import java.util.List; +import java.util.Set; + +@Value.Immutable +@JsonSerialize(as= ImmutableAssessmentRatingValidationResult.class) +public interface AssessmentRatingValidationResult { + List validatedItems(); + + @Nullable + AssessmentRatingParsedResult.AssessmentRatingParseError error(); + + @Value.Derived + default int removalCount() { + return removals().size(); + } + + Set> removals(); +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangeOperation.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangeOperation.java new file mode 100644 index 0000000000..42a5c24845 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangeOperation.java @@ -0,0 +1,9 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +public enum ChangeOperation { + ADD, + REMOVE, + RESTORE, + UPDATE, + NONE +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangedFieldType.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangedFieldType.java new file mode 100644 index 0000000000..2dd0d9c094 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ChangedFieldType.java @@ -0,0 +1,8 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +public enum ChangedFieldType { + ENTITY, + RATING, + READ_ONLY, + COMMENT +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java new file mode 100644 index 0000000000..e9861fcee7 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java @@ -0,0 +1,6 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +public enum ValidationError { + ENTITY_KIND_NOT_FOUND, + RATING_NOT_FOUND, +} diff --git a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte new file mode 100644 index 0000000000..76748d7857 --- /dev/null +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte @@ -0,0 +1,304 @@ + + +
+ The bulk assessment rating editor can be used to upload multiple ratings to this assessment defination. +
+ +{#if mode === Modes.EDIT} +
+ Help +
+
+
External Id
+
This uniquely identifies the item within the entity. It should not be changed after it is set.
+ +
Rating Code
+
This defines the rating code.
+ +
Is ReadOnly
+
This defines the item is read only.
+ +
Comment
+
Short description
+ +
+ For example: +
+
externalID	ratingCode	isReadOnly	comment
+INV0737	L	TRUE	description
+      
+
+ +
+ +
+ + + + + +
+ +
+{/if} + +{#if mode === Modes.LOADING} + +{/if} + +{#if mode === Modes.PREVIEW} + {#if !_.isNil(previewData.error)} +
+ + Could not parse the data, see error message below. + +
{previewData.error.message}
+
+ {/if} + {#if _.isNil(previewData.error)} +
+ + + + + + + + + + + + + {#each previewData.validatedItems as item} + + + + + + + + + {/each} + +
ActionExternal IdRatingRead OnlyCommentsErrors
+ {mkOpLabel(item)} + + {item.parsedItem.externalId} + + {item.parsedItem.ratingCode} + + {truncateMiddle(item.parsedItem.isReadOnly)} + + {item.parsedItem.comment} + + {item.errors} +
+
+ {/if} + + + +{/if} + +{#if mode === Modes.APPLY} + + + + + + + + + + + + + + + + + + + +
Added Records 0}> + {applyData.recordsAdded} +
Updated Records 0}> + {applyData.recordsUpdated} +
Removed Records 0}> + {applyData.recordsRemoved} +
Restored Records 0}> + {applyData.recordsRestored} +
+ + {#if applyData.hierarchyRebuilt} +

+ Please note: This change has altered the hierarchy, you will need to reload this page. +

+ {/if} + + + +{/if} + + \ No newline at end of file diff --git a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html index dfecba58f4..9f28f9d650 100644 --- a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html +++ b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html @@ -58,7 +58,7 @@ -

@@ -75,6 +75,14 @@ on-save="ctrl.saveRatings">

+ --> +
+
+ + +
diff --git a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.js b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.js index 77cba334ae..414a712655 100644 --- a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.js +++ b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.js @@ -24,6 +24,7 @@ import {mkEntityLinkGridCell} from "../../../common/grid-utils"; import {displayError} from "../../../common/error-utils"; import {initialiseData} from "../../../common"; import toasts from "../../../svelte-stores/toast-store"; +import BulkAssessmentRatingSelector from "../../components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte"; const ratingCellTemplate = `
@@ -34,6 +35,7 @@ const ratingCellTemplate = ` const initialState = { + BulkAssessmentRatingSelector, columnDefs: [ mkEntityLinkGridCell("Entity", "entityRef", "none", "right"), { @@ -67,7 +69,10 @@ function controller($q, const loadAll = () => { serviceBroker .loadViewData(CORE_API.AssessmentDefinitionStore.getById, [definitionId]) - .then(r => vm.definition = r.data); + .then(r => { + vm.definition = r.data + console.log(r.data); + }); const ratingSchemePromise = serviceBroker .loadViewData(CORE_API.RatingSchemeStore.findRatingsSchemeItems, [definitionId]) diff --git a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js index f67874acd5..6b8bd4d85e 100644 --- a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js +++ b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js @@ -50,7 +50,6 @@ const initialState = { ] }; -const bulkEditableKinds = ["APPLICATION"]; function controller($q, $stateParams, @@ -69,7 +68,7 @@ function controller($q, .loadViewData(CORE_API.AssessmentDefinitionStore.getById, [definitionId]) .then(r => { vm.definition = r.data; - vm.allowBulkEditing = _.includes(bulkEditableKinds, vm.definition.entityKind); + vm.allowBulkEditing = true; }); const ratingSchemePromise = serviceBroker diff --git a/waltz-ng/client/svelte-stores/assessment-rating-store.js b/waltz-ng/client/svelte-stores/assessment-rating-store.js index f6d7296808..6b7f47654a 100644 --- a/waltz-ng/client/svelte-stores/assessment-rating-store.js +++ b/waltz-ng/client/svelte-stores/assessment-rating-store.js @@ -80,6 +80,13 @@ export function mkAssessmentRatingStore() { null, {force}); + const bulkPreview = (entityRef, rawText) => + remote + .execute( + "POST", + `api/assessment-definition/bulk/preview/${entityRef.kind}/${entityRef.id}`, + rawText); + return { findByDefinitionId, findForEntityReference, @@ -92,7 +99,8 @@ export function mkAssessmentRatingStore() { updateComment, updateRating, findSummaryCounts, - hasMultiValuedAssessments + hasMultiValuedAssessments, + bulkPreview }; } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingItemParser.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingItemParser.java new file mode 100644 index 0000000000..501aba9690 --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingItemParser.java @@ -0,0 +1,103 @@ +package org.finos.waltz.service.assessment_rating; + +import com.fasterxml.jackson.databind.MappingIterator; +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.assessment_rating.bulk_upload.AssessmentRatingParsedItem; +import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingParsedResult; +import org.finos.waltz.model.assessment_rating.bulk_upload.ImmutableAssessmentRatingParseError; +import org.finos.waltz.model.assessment_rating.bulk_upload.ImmutableAssessmentRatingParsedResult; +import org.springframework.stereotype.Service; + +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; + +@Service +public class BulkAssessmentRatingItemParser { + + public enum InputFormat { + CSV, + TSV, + JSON + } + + + public AssessmentRatingParsedResult parse(String input, InputFormat format) { + if (isEmpty(input)) { + return handleEmptyInput(input); + } + + try { + switch (format) { + case TSV: + return parseTSV(clean(input)); + default: + throw new IllegalArgumentException(format("Unknown format: %s", format)); + } + } catch (IOException e) { + return ImmutableAssessmentRatingParsedResult + .builder() + .input(input) + .error(ImmutableAssessmentRatingParseError + .builder() + .message(e.getMessage()) + .build()) + .build(); + } + } + + private AssessmentRatingParsedResult parseTSV(String input) throws IOException { + List items = attemptToParseDelimited(input, configureTSVSchema()); + return AssessmentRatingParsedResult.mkResult(items, input); + } + + + 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(AssessmentRatingParsedItem.class) + .with(bootstrapSchema) + .readValues(input); + + return items.readAll(); + } + + private CsvSchema configureTSVSchema() { + return CsvSchema + .emptySchema() + .withHeader() + .withColumnSeparator('\t'); + } + + + private AssessmentRatingParsedResult handleEmptyInput(String input) { + return ImmutableAssessmentRatingParsedResult + .builder() + .input(input) + .error(ImmutableAssessmentRatingParseError + .builder() + .message("Cannot parse an empty string") + .column(0) + .line(0) + .build()) + .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/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java new file mode 100644 index 0000000000..f5ac771a7e --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java @@ -0,0 +1,182 @@ +package org.finos.waltz.service.assessment_rating; + +import org.finos.waltz.common.FunctionUtilities; +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.common.StringUtilities; +import org.finos.waltz.data.CommonTableFieldsRegistry; +import org.finos.waltz.model.CommonTableFields; +import org.finos.waltz.model.DiffResult; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.ImmutableEntityReference; +import org.finos.waltz.model.assessment_definition.AssessmentDefinition; +import org.finos.waltz.model.assessment_rating.AssessmentRating; +import org.finos.waltz.model.assessment_rating.ImmutableAssessmentRating; +import org.finos.waltz.model.assessment_rating.bulk_upload.*; +import org.finos.waltz.model.bulk_upload.BulkUpdateMode; +import org.finos.waltz.model.rating.RatingSchemeItem; +import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; +import org.finos.waltz.service.measurable_rating.MeasurableRatingService; +import org.finos.waltz.service.rating_scheme.RatingSchemeService; +import org.finos.waltz.service.user.UserRoleService; +import org.jooq.*; +import org.jooq.lambda.tuple.Tuple2; +import org.jooq.lambda.tuple.Tuple4; +import org.junit.platform.commons.util.FunctionUtils; +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 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.model.EntityReference.mkRef; +import static org.jooq.lambda.tuple.Tuple.tuple; + +@Service +public class BulkAssessmentRatingService { + private static final Logger LOG = LoggerFactory.getLogger(MeasurableRatingService.class); + private static final String PROVENANCE = "bulkAssessmentDefinitionUpdate"; + private static final String DUMMY_USER = "test"; + + private final AssessmentDefinitionService assessmentDefinitionService; + + private final AssessmentRatingService assessmentRatingService; + + private final RatingSchemeService ratingSchemeService; + + private final DSLContext dsl; + + + @Autowired + public BulkAssessmentRatingService(AssessmentDefinitionService assessmentDefinitionService, AssessmentRatingService assessmentRatingService, RatingSchemeService ratingSchemeService, UserRoleService userRoleService, DSLContext dsl) { + this.assessmentDefinitionService = assessmentDefinitionService; + this.assessmentRatingService = assessmentRatingService; + this.ratingSchemeService = ratingSchemeService; + this.dsl = dsl; + } + public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentReference, + String inputStr, + BulkAssessmentRatingItemParser.InputFormat format, + BulkUpdateMode mode) { + + AssessmentRatingParsedResult result = new BulkAssessmentRatingItemParser().parse(inputStr, format); + if (result.error() != null) { + return ImmutableAssessmentRatingValidationResult + .builder() + .error(result.error()) + .build(); + } + + AssessmentDefinition assessmentDefinition = assessmentDefinitionService.getById(assessmentReference.id()); + Set ratingSchemeItemsBySchemeIds = ratingSchemeService.findRatingSchemeItemsBySchemeIds(asSet(assessmentDefinition.ratingSchemeId())); + Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); + + + CommonTableFields ctf = CommonTableFieldsRegistry.determineCommonTableFields(assessmentDefinition.entityKind(), "target"); + Set immutableEntityKinds = dsl.select(ctf.nameField(), ctf.idField(), ctf.externalIdField()) + .from(ctf.table()) + .where(ctf.isActiveCondition()) + .fetch() + .stream() + .map(m -> ImmutableEntityReference.builder() + .kind(ctf.entityKind()) + .id(m.get(ctf.idField())) + .name(Optional.ofNullable(m.get(ctf.nameField()))) + .externalId(m.get(ctf.externalIdField())) + .build()) + .collect(Collectors.toSet()); + + + Map kindsByExternalIds = indexBy(immutableEntityKinds, k -> k.externalId().get()); + + List>> validatedEntries = result + .parsedItems() + .stream() + .map(d -> { + ImmutableEntityReference immutableEntityReference = kindsByExternalIds.get(d.externalId()); + RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); + return tuple(d, immutableEntityReference, ratingSchemeItem); + }) + .map(t -> { + Set validationErrors = new HashSet<>(); + + if (t.v2() == null) { + validationErrors.add(ValidationError.ENTITY_KIND_NOT_FOUND); + } + if (t.v3() == null) { + validationErrors.add(ValidationError.RATING_NOT_FOUND); + } + return t.concat(validationErrors); + }) + .collect(Collectors.toList()); + + List requiredAssessmentRatings = validatedEntries + .stream() + .filter(t -> t.v2 != null && t.v3 != null) + .map(t -> ImmutableAssessmentRating + .builder() + .entityReference(mkRef(t.v2.kind(), t.v2.id())) + .assessmentDefinitionId(assessmentReference.id()) + .ratingId(t.v3.id().get()) + .comment(t.v1.comment()) + .lastUpdatedBy(DUMMY_USER) + .provenance(PROVENANCE) + .isReadOnly(t.v1.isReadOnly()) + .build()) + .collect(Collectors.toList()); + + List existingAssessmentRatings = assessmentRatingService.findByDefinitionId(assessmentReference.id()); + + DiffResult assessmentRatingDiffResult = DiffResult + .mkDiff( + existingAssessmentRatings, + requiredAssessmentRatings, + d -> tuple(d.entityReference(), d.assessmentDefinitionId()), + (a, b) -> StringUtilities.safeEq(a.comment(), b.comment()) + && a.ratingId() == b.ratingId()); + + Set> toAdd = SetUtilities.map(assessmentRatingDiffResult.otherOnly(), d -> tuple(d.entityReference(), d.ratingId())); + Set> toUpdate = SetUtilities.map(assessmentRatingDiffResult.differingIntersection(), d -> tuple(d.entityReference(), d.ratingId())); + Set> toRemove = SetUtilities.map(assessmentRatingDiffResult.waltzOnly(), d -> tuple(d.entityReference(), d.ratingId())); + + + List validatedItems = validatedEntries + .stream() + .map(t -> { + boolean isInValid = t.v2 == null || t.v3 == null; + + if (!isInValid) { + Tuple2 recordKey = tuple(mkRef(t.v2.kind(), t.v2.id()), 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 -> ImmutableAssessmentRatingValidatedItem + .builder() + .changeOperation(t.v5) + .errors(t.v4) + .entityKindReference(t.v2) + .ratingSchemeItem(t.v3) + .parsedItem(t.v1) + .build()) + .collect(Collectors.toList()); + + return ImmutableAssessmentRatingValidationResult + .builder() + .validatedItems(validatedItems) + .removals(mode == BulkUpdateMode.REPLACE + ? toRemove + : emptySet()) + .build(); + } +} diff --git a/waltz-service/src/test/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingServiceItemParserTest.java b/waltz-service/src/test/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingServiceItemParserTest.java new file mode 100644 index 0000000000..eb8c991ca2 --- /dev/null +++ b/waltz-service/src/test/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingServiceItemParserTest.java @@ -0,0 +1,35 @@ +package org.finos.waltz.service.assessment_rating; + +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingParsedItem; +import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingParsedResult; +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.*; + +public class BulkAssessmentRatingServiceItemParserTest { + + private final BulkAssessmentRatingItemParser parser = new BulkAssessmentRatingItemParser(); + + private String readTestFile(String fileName) { + return readAsString(BulkAssessmentRatingItemParser.class.getResourceAsStream(fileName)); + } + + @Test + void simpleTSV() { + AssessmentRatingParsedResult result = parser.parse(readTestFile("test-assessment-rating-items.tsv"), BulkAssessmentRatingItemParser.InputFormat.TSV); + assertNull(result.error()); + assertEquals(1, result.parsedItems().size()); + assertExternalIds(result.parsedItems(), "IN75"); + } + + private void assertExternalIds(List parsedItems, String... resultNarIds) { + Set externalIds = SetUtilities.map(parsedItems, AssessmentRatingParsedItem::externalId); + Set expectedNarIds = SetUtilities.asSet(resultNarIds); + assertEquals(expectedNarIds, externalIds); + } +} diff --git a/waltz-service/src/test/resources/org/finos/waltz/service/assessment_rating/test-assessment-rating-items.tsv b/waltz-service/src/test/resources/org/finos/waltz/service/assessment_rating/test-assessment-rating-items.tsv new file mode 100644 index 0000000000..361bf99e1e --- /dev/null +++ b/waltz-service/src/test/resources/org/finos/waltz/service/assessment_rating/test-assessment-rating-items.tsv @@ -0,0 +1,2 @@ +externalID ratingCode isReadOnly comment +IN75 A true description diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java index 70a35bb4f5..21fb5e1b05 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java @@ -5,6 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; + @Service public class ActorHelper { @@ -16,6 +18,7 @@ public Long createActor(String nameStem) { ImmutableActorCreateCommand .builder() .name(nameStem) + .externalId(mkName(nameStem)) .description(nameStem + " Desc") .isExternal(true) .build(), diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java index 8afd2f7742..35e5510fec 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java @@ -19,10 +19,16 @@ package org.finos.waltz.web.endpoints.api; import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.common.EnumUtilities; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; import org.finos.waltz.model.assessment_definition.ImmutableAssessmentDefinition; +import org.finos.waltz.model.bulk_upload.BulkUpdateMode; import org.finos.waltz.model.user.SystemRole; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingService; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingItemParser; import org.finos.waltz.service.user.UserRoleService; import org.finos.waltz.web.DatumRoute; import org.finos.waltz.web.ListRoute; @@ -38,8 +44,10 @@ import java.util.Set; import static org.finos.waltz.common.Checks.checkNotNull; -import static org.finos.waltz.web.WebUtilities.getId; -import static org.finos.waltz.web.WebUtilities.getUsername; +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.web.WebUtilities.*; +import static org.finos.waltz.web.WebUtilities.mkPath; +import static org.finos.waltz.web.endpoints.EndpointUtilities.postForDatum; @Service public class AssessmentDefinitionEndpoint implements Endpoint { @@ -47,11 +55,14 @@ public class AssessmentDefinitionEndpoint implements Endpoint { private static final String BASE_URL = WebUtilities.mkPath("api", "assessment-definition"); private final AssessmentDefinitionService assessmentDefinitionService; + + private final BulkAssessmentRatingService bulkAssessmentRatingService; private final UserRoleService userRoleService; @Autowired - public AssessmentDefinitionEndpoint(AssessmentDefinitionService assessmentDefinitionService, UserRoleService userRoleService) { + public AssessmentDefinitionEndpoint(AssessmentDefinitionService assessmentDefinitionService, BulkAssessmentRatingService bulkAssessmentRatingService, UserRoleService userRoleService) { + this.bulkAssessmentRatingService = bulkAssessmentRatingService; checkNotNull(assessmentDefinitionService, "assessmentDefinitionService cannot be null"); this.assessmentDefinitionService = assessmentDefinitionService; @@ -70,6 +81,10 @@ public void register() { String findByRefPath = WebUtilities.mkPath(BASE_URL, "kind", ":kind", "id", ":id"); String removeByIdPath = WebUtilities.mkPath(BASE_URL, "id", ":id"); + String bulkAssessmentDefinitionPreviewPath = mkPath(BASE_URL, "bulk", "preview", "ASSESSMENT_DEFINITION", ":id"); + String bulkAssessmentDefinitionApplyPath = mkPath(BASE_URL, "bulk", "apply", "ASSESSMENT_DEFINITION", ":id"); + + registerPreviewAssessmentDefinitionChanges(bulkAssessmentDefinitionPreviewPath); DatumRoute getByIdRoute = (request, response) -> assessmentDefinitionService.getById(WebUtilities.getId(request)); ListRoute findAllRoute = (request, response) -> assessmentDefinitionService.findAll(); ListRoute findByKindRoute = (request, response) -> assessmentDefinitionService.findByEntityKind(WebUtilities.getKind(request)); @@ -87,6 +102,19 @@ public void register() { EndpointUtilities.deleteForList(favouriteByIdPath, this::removeFavourite); } + private void registerPreviewAssessmentDefinitionChanges(String path) { + postForDatum(path, (req, resp) -> { + EntityReference entityReference = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); + String modeStr = req.queryParams("mode"); + String formatStr = req.queryParams("format"); + BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); + String body = req.body(); + return bulkAssessmentRatingService.bulkPreview(entityReference, body, format, mode); + }); + } + + private Set findFavouritesForUser(Request request, Response response) { return assessmentDefinitionService.findFavouritesForUser(getUsername(request)); } From c6edda4c77ab186a4c5e1db6ec8d6912cca6774d Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Wed, 16 Oct 2024 18:07:22 +0530 Subject: [PATCH 2/7] CTCTOWALTZ-3337: [x] Check for cardinality added [x] Apply Assessment Rating implemented [x]Updated UI to show overflow errors. #7156 --- .../finos/waltz/data/SelectorUtilities.java | 34 +++ .../BulkAssessmentRatingServiceTest.java | 23 +- .../BulkAssessmentRatingApplyResult.java | 13 + .../bulk_upload/ValidationError.java | 3 + .../BulkMeasurableRatingValidationResult.java | 2 +- .../BulkAssessmentRatingSelector.svelte | 35 ++- .../DataCellErrorTooltip.svelte | 16 ++ .../svelte-stores/assessment-rating-store.js | 12 +- .../AssessmentRatingService.java | 12 +- .../BulkAssessmentRatingService.java | 249 ++++++++++++++++-- .../api/AssessmentDefinitionEndpoint.java | 30 +-- .../api/AssessmentRatingEndpoint.java | 42 ++- 12 files changed, 378 insertions(+), 93 deletions(-) create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/BulkAssessmentRatingApplyResult.java create mode 100644 waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte diff --git a/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java b/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java index aecfb23274..bb8d9adadb 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java @@ -24,7 +24,11 @@ import org.finos.waltz.schema.tables.Application; import org.jooq.Condition; +import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; import static org.finos.waltz.common.Checks.checkTrue; import static org.finos.waltz.common.SetUtilities.asSet; @@ -72,4 +76,34 @@ public static Condition mkApplicationConditions(Application appTable, IdSelectio } + /** + * get all distinct records based on some property + * @param keyExtractor + * @return + * @param + */ + public static Predicate getAllDistinctByKeyWith(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); + } + + /** + * get all distinct records based on some property with excluded records + * @param keyExtractor + * @return + * @param + */ + public static Predicate getAllDistinctByKeyWithExclusion(Function keyExtractor, List excludedRecords) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> { + Object key = keyExtractor.apply(t); + if(seen.add(key)) { + return true; + } else { + excludedRecords.add(t); + return false; + } + }; + } + } diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java index 34f29aa305..55419c86ba 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java @@ -63,7 +63,7 @@ public void previewAdds() { String name = mkName(stem, "previewApp"); String kindExternalId = mkName(stem, "previewAppCode"); Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(name + "SchemeApp"); - ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 10, "green", "Y"); + ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 0, "green", "Y"); appHelper.createNewApp( mkName(stem, "previewUpdatesApp"), ouIds.root, @@ -79,27 +79,6 @@ public void previewAdds() { assertNotNull(result1, "Expected a result"); assertNoErrors(result1); assertExternalIdsMatch(result1, asList(kindExternalId)); - - /** - * Entity Kind: ACTOR - * Need work as it is failing. external id is coming as null - */ -// String actorName = mkName(stem, "previewActor"); -// String actorExternalId = mkName(stem, "previewActorCode"); -// Long actorSchemeId = ratingSchemeHelper.createEmptyRatingScheme(name + "SchemeActor"); -// ratingSchemeHelper.saveRatingItem(actorSchemeId, "Yes", 10, "green", "Z"); -// actorHelper.createActor(actorName); -// AssessmentDefinition def2 = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.ACTOR, actorSchemeId, actorName)); -// -// AssessmentRatingValidationResult result2 = bulkAssessmentRatingService.bulkPreview( -// mkRef(def2.entityKind(), def2.id().get()), -// mkGoodTsv(actorExternalId), -// BulkAssessmentRatingItemParser.InputFormat.TSV, -// BulkUpdateMode.ADD_ONLY); -// -// assertNotNull(result2, "Expected a result"); -// assertNoErrors(result2); -// assertExternalIdsMatch(result2, asList(actorExternalId)); } private long getAssessmentDefinition(EntityKind kind, Long schemeId, String name) { diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/BulkAssessmentRatingApplyResult.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/BulkAssessmentRatingApplyResult.java new file mode 100644 index 0000000000..343f5aeff4 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/BulkAssessmentRatingApplyResult.java @@ -0,0 +1,13 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.value.Value; + +@Value.Immutable +@JsonSerialize(as = ImmutableBulkAssessmentRatingApplyResult.class) +public interface BulkAssessmentRatingApplyResult { + int recordsAdded(); + int recordsUpdated(); + int recordsRemoved(); + int skippedRows(); +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java index e9861fcee7..c8013eeb6d 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java @@ -3,4 +3,7 @@ public enum ValidationError { ENTITY_KIND_NOT_FOUND, RATING_NOT_FOUND, + DUPLICATE, + + RATING_NOT_USER_SELECTABLE } 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 5bc3f85df0..42ce5f4c56 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 @@ -14,7 +14,7 @@ @JsonSerialize(as= ImmutableBulkMeasurableRatingValidationResult.class) public interface BulkMeasurableRatingValidationResult { - List validatedItems(); + List validatedItems(); @Nullable BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError error(); diff --git a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte index 76748d7857..a42e745c16 100644 --- a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte @@ -5,6 +5,8 @@ import {assessmentRatingStore} from "../../../svelte-stores/assessment-rating-store"; import {truncateMiddle} from "../../../common/string-utils"; import Icon from "../../../common/svelte/Icon.svelte"; + import Tooltip from "../../../common/svelte/Tooltip.svelte"; + import DataCellErrorTooltip from "./DataCellErrorTooltip.svelte"; export let primaryEntityRef; const Modes = { @@ -47,7 +49,10 @@ canApply = _.isNil(previewData.error) && _.every(previewData.validatedItems, d => _.isEmpty(d.errors)); switchMode(Modes.PREVIEW); }) - .catch(e => displayError("Could not preview assessment rating changes", e)); + .catch(e => { + displayError("Could not preview assessment rating changes", e); + switchMode(Modes.PREVIEW); + }); } function onApplyBulkChanges() { @@ -58,7 +63,10 @@ applyData = r.data; switchMode(Modes.APPLY); }) - .catch(e => displayError("Could not apply assessment rating changes", e)); + .catch(e => { + displayError("Could not apply assessment rating changes", e); + switchMode(Modes.PREVIEW); + }); } @@ -186,8 +194,8 @@ INV0737 L TRUE description class:cell-update={_.includes(item.changedFields, "ENTITY_KIND") && !_.includes(item.errors, "ENTITY_NOT_FOUND")}> {item.parsedItem.externalId} - + {item.parsedItem.ratingCode} @@ -197,7 +205,14 @@ INV0737 L TRUE description {item.parsedItem.comment} - {item.errors} + {#if item.errors.length > 0} + + + + + + {/if} {/each} @@ -239,9 +254,9 @@ INV0737 L TRUE description - Restored Records - 0}> - {applyData.recordsRestored} + Skipped Records + 0}> + {applyData.skippedRows} @@ -301,4 +316,8 @@ INV0737 L TRUE description .positive-result { background-color: #caf8ca; } + + .error-cell { + color: red; + } \ No newline at end of file diff --git a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte new file mode 100644 index 0000000000..0565322f5f --- /dev/null +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte @@ -0,0 +1,16 @@ + + + +{#if errors && errors.length} + {#each errors as error} +
{error}
+ {/each} +{/if} + + \ No newline at end of file diff --git a/waltz-ng/client/svelte-stores/assessment-rating-store.js b/waltz-ng/client/svelte-stores/assessment-rating-store.js index 6b7f47654a..2a3efc102f 100644 --- a/waltz-ng/client/svelte-stores/assessment-rating-store.js +++ b/waltz-ng/client/svelte-stores/assessment-rating-store.js @@ -84,7 +84,14 @@ export function mkAssessmentRatingStore() { remote .execute( "POST", - `api/assessment-definition/bulk/preview/${entityRef.kind}/${entityRef.id}`, + `api/assessment-rating/bulk/preview/${entityRef.kind}/${entityRef.id}`, + rawText); + + const bulkApply = (entityRef, rawText) => + remote + .execute( + "POST", + `api/assessment-rating/bulk/apply/${entityRef.kind}/${entityRef.id}`, rawText); return { @@ -100,7 +107,8 @@ export function mkAssessmentRatingStore() { updateRating, findSummaryCounts, hasMultiValuedAssessments, - bulkPreview + bulkPreview, + bulkApply }; } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java index 9f78556c3d..fd80513fd0 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java @@ -160,12 +160,12 @@ public boolean unlock(EntityReference entityReference, public boolean remove(RemoveAssessmentRatingCommand command, String username) throws InsufficientPrivelegeException { - verifyPermission( - Operation.REMOVE, - command.entityReference(), - command.assessmentDefinitionId(), - command.ratingId(), - username); +// verifyPermission( +// Operation.REMOVE, +// command.entityReference(), +// command.assessmentDefinitionId(), +// command.ratingId(), +// username); String ratingRemovedName = ratingSchemeDAO.findRatingSchemeItemsByIds(asSet(command.ratingId())) .stream() diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java index f5ac771a7e..0628f79cd9 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java @@ -1,38 +1,52 @@ package org.finos.waltz.service.assessment_rating; -import org.finos.waltz.common.FunctionUtilities; +import org.finos.waltz.common.DateTimeUtilities; import org.finos.waltz.common.SetUtilities; import org.finos.waltz.common.StringUtilities; import org.finos.waltz.data.CommonTableFieldsRegistry; -import org.finos.waltz.model.CommonTableFields; -import org.finos.waltz.model.DiffResult; -import org.finos.waltz.model.EntityReference; -import org.finos.waltz.model.ImmutableEntityReference; +import org.finos.waltz.model.*; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; import org.finos.waltz.model.assessment_rating.AssessmentRating; import org.finos.waltz.model.assessment_rating.ImmutableAssessmentRating; import org.finos.waltz.model.assessment_rating.bulk_upload.*; import org.finos.waltz.model.bulk_upload.BulkUpdateMode; +import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingApplyResult; +import org.finos.waltz.model.exceptions.NotAuthorizedException; 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.AssessmentRatingRecord; +import org.finos.waltz.schema.tables.records.ChangeLogRecord; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; import org.finos.waltz.service.measurable_rating.MeasurableRatingService; import org.finos.waltz.service.rating_scheme.RatingSchemeService; import org.finos.waltz.service.user.UserRoleService; -import org.jooq.*; +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.Tuple4; -import org.junit.platform.commons.util.FunctionUtils; 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.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; 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.common.StringUtilities.sanitizeCharacters; +import static org.finos.waltz.data.JooqUtilities.summarizeResults; +import static org.finos.waltz.data.SelectorUtilities.getAllDistinctByKeyWithExclusion; import static org.finos.waltz.model.EntityReference.mkRef; import static org.jooq.lambda.tuple.Tuple.tuple; @@ -43,21 +57,27 @@ public class BulkAssessmentRatingService { private static final String DUMMY_USER = "test"; private final AssessmentDefinitionService assessmentDefinitionService; - - private final AssessmentRatingService assessmentRatingService; + private final AssessmentRatingService assessmentRatingService; + private final RatingSchemeService ratingSchemeService; + private UserRoleService userRoleService; + private final DSLContext dsl; + private final org.finos.waltz.schema.tables.AssessmentRating ar = Tables.ASSESSMENT_RATING; + @Autowired public BulkAssessmentRatingService(AssessmentDefinitionService assessmentDefinitionService, AssessmentRatingService assessmentRatingService, RatingSchemeService ratingSchemeService, UserRoleService userRoleService, DSLContext dsl) { this.assessmentDefinitionService = assessmentDefinitionService; this.assessmentRatingService = assessmentRatingService; this.ratingSchemeService = ratingSchemeService; + this.userRoleService = userRoleService; this.dsl = dsl; } + public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentReference, String inputStr, BulkAssessmentRatingItemParser.InputFormat format, @@ -76,23 +96,25 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); - CommonTableFields ctf = CommonTableFieldsRegistry.determineCommonTableFields(assessmentDefinition.entityKind(), "target"); - Set immutableEntityKinds = dsl.select(ctf.nameField(), ctf.idField(), ctf.externalIdField()) - .from(ctf.table()) - .where(ctf.isActiveCondition()) - .fetch() - .stream() - .map(m -> ImmutableEntityReference.builder() - .kind(ctf.entityKind()) - .id(m.get(ctf.idField())) - .name(Optional.ofNullable(m.get(ctf.nameField()))) - .externalId(m.get(ctf.externalIdField())) - .build()) - .collect(Collectors.toSet()); + CommonTableFields ctf = CommonTableFieldsRegistry.determineCommonTableFields(assessmentDefinition.entityKind(), "target"); + Set immutableEntityKinds = dsl.select(ctf.nameField(), ctf.idField(), ctf.externalIdField()) + .from(ctf.table()) + .where(ctf.isActiveCondition() + .and(ctf.externalIdField().isNotNull())) + .fetch() + .stream() + .map(m -> ImmutableEntityReference.builder() + .kind(ctf.entityKind()) + .id(m.get(ctf.idField())) + .name(Optional.ofNullable(m.get(ctf.nameField()))) + .externalId(m.get(ctf.externalIdField())) + .build()) + .collect(Collectors.toSet()); Map kindsByExternalIds = indexBy(immutableEntityKinds, k -> k.externalId().get()); - + boolean isCardinalityZeroToOne = assessmentDefinition.cardinality().equals(Cardinality.ZERO_ONE); + Set seen = ConcurrentHashMap.newKeySet(); List>> validatedEntries = result .parsedItems() .stream() @@ -103,13 +125,22 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe }) .map(t -> { Set validationErrors = new HashSet<>(); - + if(isCardinalityZeroToOne) { + if(!seen.contains(t.v1.externalId())) { + seen.add(t.v1.externalId()); + } else { + validationErrors.add(ValidationError.DUPLICATE); + } + } if (t.v2() == null) { validationErrors.add(ValidationError.ENTITY_KIND_NOT_FOUND); } if (t.v3() == null) { validationErrors.add(ValidationError.RATING_NOT_FOUND); } + if (t.v3 != null && !t.v3.userSelectable()) { + validationErrors.add(ValidationError.RATING_NOT_USER_SELECTABLE); + } return t.concat(validationErrors); }) .collect(Collectors.toList()); @@ -179,4 +210,174 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe : emptySet()) .build(); } + + public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, + AssessmentRatingValidationResult preview, + BulkUpdateMode mode, + String userId) { + + verifyUserHasPermissions(userId); + + if (preview.error() != null) { + throw new IllegalStateException("Cannot apply changes with formatting errors"); + } + Timestamp now = DateTimeUtilities.nowUtcTimestamp(); + + AssessmentDefinition assessmentDefinition = assessmentDefinitionService.getById(assessmentRef.id()); + + Set toAdd = preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() == ChangeOperation.ADD && d.errors().isEmpty()) + .map(d -> { + AssessmentRatingRecord record = new AssessmentRatingRecord(); + record.setEntityKind(d.entityKindReference().kind().name()); + record.setAssessmentDefinitionId(assessmentRef.id()); + record.setEntityId(d.entityKindReference().id()); + record.setRatingId(d.ratingSchemeItem().id().get()); + record.setDescription(sanitizeCharacters(d.parsedItem().comment())); + record.setLastUpdatedBy(userId); + record.setLastUpdatedAt(now); + record.setProvenance(PROVENANCE); + record.setIsReadonly(d.parsedItem().isReadOnly()); + return record; + }) + .collect(Collectors.toSet()); + + Set> toUpdate = preview + .validatedItems() + .stream() + .filter(d -> d.changeOperation() == ChangeOperation.UPDATE && d.errors().isEmpty()) + .map(d -> + DSL + .update(ar) + .set(ar.RATING_ID, d.ratingSchemeItem().id().get()) + .set(ar.DESCRIPTION, sanitizeCharacters(d.parsedItem().comment())) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(assessmentRef.id()) + .and(ar.ENTITY_KIND.eq(d.entityKindReference().kind().name()) + .and(ar.ENTITY_ID.eq(d.entityKindReference().id())) + .and(ar.RATING_ID.eq(d.ratingSchemeItem().id().get()))))) + .collect(Collectors.toSet()); + + Set> toRemove = preview + .removals() + .stream() + .map(d -> + DSL + .deleteFrom(ar) + .where(ar.ENTITY_KIND.eq(d.v1.kind().name())) + .and(ar.ENTITY_ID.eq(d.v1.id())) + .and(ar.ASSESSMENT_DEFINITION_ID.eq(assessmentRef.id()) + .and(ar.RATING_ID.eq(d.v2)))) + .collect(Collectors.toSet()); + + Map ratingItemsById = indexBy( + ratingSchemeService.findRatingSchemeItemsByAssessmentDefinition(assessmentRef.id()), + r -> r.id().orElse(0L)); + + Set auditLogs = Stream.concat( + preview + .removals() + .stream() + .map(t -> { + RatingSchemeItem rsi = ratingItemsById.get(t.v2); + ChangeLogRecord r = new ChangeLogRecord(); + r.setMessage(format( + "Bulk Rating Update - Removed assessment rating for: %s/%s (%d)", + rsi == null ? "?" : rsi.name(), + rsi == null ? "?" : rsi.externalId().orElse("-"), + t.v2)); + r.setOperation(Operation.REMOVE.name()); + r.setParentKind(t.v1.kind().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.ratingSchemeItem(), d.changeOperation())); + r.setOperation(toChangeLogOperation(d.changeOperation()).name()); + r.setParentKind(EntityKind.APPLICATION.name()); + r.setParentId(d.entityKindReference().id()); + 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(toAdd).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 assessment rating: {} adds, {} updates, {} removes, {} changeLogs", + insertCount, + updateCount, + removalCount, + changeLogCount); + +// throw new RuntimeException("Boooomrahhh!!!"); + return ImmutableBulkAssessmentRatingApplyResult + .builder() + .recordsAdded(insertCount) + .recordsUpdated(updateCount) + .recordsRemoved(removalCount) + .skippedRows((int) skipCount) + .build(); + }); + } + + private String mkChangeMessage(RatingSchemeItem ratingSchemeItem, + ChangeOperation changeOperation) { + return format( + "Bulk Rating Update - Operation: %s, assessment rating for: %s/%s", + changeOperation, + ratingSchemeItem.name(), + ratingSchemeItem.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.ASSESSMENT_DEFINITION_ADMIN.name())) { + throw new NotAuthorizedException(); + } + } } + + +/** + * Preview: + * Check for user selectable for rating + */ \ No newline at end of file diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java index 35e5510fec..d32f5432ce 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentDefinitionEndpoint.java @@ -19,16 +19,10 @@ package org.finos.waltz.web.endpoints.api; import org.finos.waltz.common.DateTimeUtilities; -import org.finos.waltz.common.EnumUtilities; -import org.finos.waltz.model.EntityKind; -import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; import org.finos.waltz.model.assessment_definition.ImmutableAssessmentDefinition; -import org.finos.waltz.model.bulk_upload.BulkUpdateMode; import org.finos.waltz.model.user.SystemRole; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; -import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingService; -import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingItemParser; import org.finos.waltz.service.user.UserRoleService; import org.finos.waltz.web.DatumRoute; import org.finos.waltz.web.ListRoute; @@ -44,10 +38,7 @@ import java.util.Set; import static org.finos.waltz.common.Checks.checkNotNull; -import static org.finos.waltz.model.EntityReference.mkRef; import static org.finos.waltz.web.WebUtilities.*; -import static org.finos.waltz.web.WebUtilities.mkPath; -import static org.finos.waltz.web.endpoints.EndpointUtilities.postForDatum; @Service public class AssessmentDefinitionEndpoint implements Endpoint { @@ -55,14 +46,11 @@ public class AssessmentDefinitionEndpoint implements Endpoint { private static final String BASE_URL = WebUtilities.mkPath("api", "assessment-definition"); private final AssessmentDefinitionService assessmentDefinitionService; - - private final BulkAssessmentRatingService bulkAssessmentRatingService; private final UserRoleService userRoleService; @Autowired - public AssessmentDefinitionEndpoint(AssessmentDefinitionService assessmentDefinitionService, BulkAssessmentRatingService bulkAssessmentRatingService, UserRoleService userRoleService) { - this.bulkAssessmentRatingService = bulkAssessmentRatingService; + public AssessmentDefinitionEndpoint(AssessmentDefinitionService assessmentDefinitionService, UserRoleService userRoleService) { checkNotNull(assessmentDefinitionService, "assessmentDefinitionService cannot be null"); this.assessmentDefinitionService = assessmentDefinitionService; @@ -81,10 +69,7 @@ public void register() { String findByRefPath = WebUtilities.mkPath(BASE_URL, "kind", ":kind", "id", ":id"); String removeByIdPath = WebUtilities.mkPath(BASE_URL, "id", ":id"); - String bulkAssessmentDefinitionPreviewPath = mkPath(BASE_URL, "bulk", "preview", "ASSESSMENT_DEFINITION", ":id"); - String bulkAssessmentDefinitionApplyPath = mkPath(BASE_URL, "bulk", "apply", "ASSESSMENT_DEFINITION", ":id"); - registerPreviewAssessmentDefinitionChanges(bulkAssessmentDefinitionPreviewPath); DatumRoute getByIdRoute = (request, response) -> assessmentDefinitionService.getById(WebUtilities.getId(request)); ListRoute findAllRoute = (request, response) -> assessmentDefinitionService.findAll(); ListRoute findByKindRoute = (request, response) -> assessmentDefinitionService.findByEntityKind(WebUtilities.getKind(request)); @@ -102,19 +87,6 @@ public void register() { EndpointUtilities.deleteForList(favouriteByIdPath, this::removeFavourite); } - private void registerPreviewAssessmentDefinitionChanges(String path) { - postForDatum(path, (req, resp) -> { - EntityReference entityReference = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); - String modeStr = req.queryParams("mode"); - String formatStr = req.queryParams("format"); - BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); - BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); - String body = req.body(); - return bulkAssessmentRatingService.bulkPreview(entityReference, body, format, mode); - }); - } - - private Set findFavouritesForUser(Request request, Response response) { return assessmentDefinitionService.findFavouritesForUser(getUsername(request)); } diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java index cba2cdf385..a1399d48f0 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java @@ -19,13 +19,19 @@ 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; import org.finos.waltz.model.UserTimestamp; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; import org.finos.waltz.model.assessment_rating.*; +import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingValidationResult; +import org.finos.waltz.model.bulk_upload.BulkUpdateMode; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; import org.finos.waltz.service.assessment_rating.AssessmentRatingService; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingItemParser; +import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingService; import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; import org.finos.waltz.service.user.UserRoleService; import org.finos.waltz.web.NotAuthorizedException; @@ -42,6 +48,7 @@ import static org.finos.waltz.common.Checks.checkNotNull; import static org.finos.waltz.common.StringUtilities.mkSafe; +import static org.finos.waltz.model.EntityReference.mkRef; import static org.finos.waltz.web.WebUtilities.*; import static org.finos.waltz.web.endpoints.EndpointUtilities.*; @@ -56,12 +63,13 @@ public class AssessmentRatingEndpoint implements Endpoint { private final AssessmentRatingPermissionChecker assessmentRatingPermissionChecker; private final UserRoleService userRoleService; + private final BulkAssessmentRatingService bulkAssessmentRatingService; @Autowired public AssessmentRatingEndpoint(AssessmentRatingService assessmentRatingService, AssessmentDefinitionService assessmentDefinitionService, AssessmentRatingPermissionChecker assessmentRatingPermissionChecker, - UserRoleService userRoleService) { + UserRoleService userRoleService, BulkAssessmentRatingService bulkAssessmentRatingService) { checkNotNull(assessmentRatingService, "assessmentRatingService cannot be null"); checkNotNull(assessmentDefinitionService, "assessmentDefinitionService cannot be null"); @@ -72,6 +80,7 @@ public AssessmentRatingEndpoint(AssessmentRatingService assessmentRatingService, this.assessmentDefinitionService = assessmentDefinitionService; this.assessmentRatingPermissionChecker = assessmentRatingPermissionChecker; this.userRoleService = userRoleService; + this.bulkAssessmentRatingService = bulkAssessmentRatingService; } @@ -93,6 +102,12 @@ public void register() { String bulkUpdatePath = mkPath(BASE_URL, "bulk-update", ":assessmentDefinitionId"); String bulkRemovePath = mkPath(BASE_URL, "bulk-remove", ":assessmentDefinitionId"); + String bulkAssessmentDefinitionPreviewPath = mkPath(BASE_URL, "bulk", "preview", "ASSESSMENT_DEFINITION", ":id"); + String bulkAssessmentDefinitionApplyPath = mkPath(BASE_URL, "bulk", "apply", "ASSESSMENT_DEFINITION", ":id"); + + registerPreviewBulkAssessmentRatingChanges(bulkAssessmentDefinitionPreviewPath); + registerApplyBulkAssessmentRatingChanges(bulkAssessmentDefinitionApplyPath); + getForList(findForEntityPath, this::findForEntityRoute); getForList(findByEntityKindPath, this::findByEntityKindRoute); getForList(findByDefinitionPath, this::findByDefinitionIdRoute); @@ -262,5 +277,30 @@ private void verifyCanWrite(Request request, long defId) { } } + private void registerPreviewBulkAssessmentRatingChanges(String path) { + postForDatum(path, (req, resp) -> { + EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); + String modeStr = req.queryParams("mode"); + String formatStr = req.queryParams("format"); + BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); + String body = req.body(); + return bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body, format, mode); + }); + } + + private void registerApplyBulkAssessmentRatingChanges(String path) { + postForDatum(path, (req, resp) -> { + EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); + String modeStr = req.queryParams("mode"); + String formatStr = req.queryParams("format"); + BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); + BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); + String body = req.body(); + AssessmentRatingValidationResult preview = bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body, format, mode); + return bulkAssessmentRatingService.apply(assessmentDefRef, preview, mode, getUsername(req)); + }); + } + } From a54683c0b2071c09a0250715c9ee680519acd5b6 Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Thu, 17 Oct 2024 14:15:23 +0530 Subject: [PATCH 3/7] CTCTOWALTZ-3337: [x] Fixed the PR review comments. [x] Added few more tests #7156 --- .../finos/waltz/data/SelectorUtilities.java | 31 ----- .../BulkAssessmentRatingServiceTest.java | 116 ++++++++++++++++-- .../AssessmentRatingParsedItem.java | 2 +- .../BulkMeasurableRatingValidationResult.java | 2 +- .../edit/assessment-rating-bulk-upload.html | 18 --- .../edit/assessment-rating-bulk-upload.js | 1 - .../pages/view/assessment-definition-view.js | 3 +- .../AssessmentRatingService.java | 12 +- .../BulkAssessmentRatingService.java | 103 +++++++--------- .../test_common/helpers/ActorHelper.java | 2 +- 10 files changed, 163 insertions(+), 127 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java b/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java index bb8d9adadb..dece840b73 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/SelectorUtilities.java @@ -75,35 +75,4 @@ public static Condition mkApplicationConditions(Application appTable, IdSelectio } } - - /** - * get all distinct records based on some property - * @param keyExtractor - * @return - * @param - */ - public static Predicate getAllDistinctByKeyWith(Function keyExtractor) { - Set seen = ConcurrentHashMap.newKeySet(); - return t -> seen.add(keyExtractor.apply(t)); - } - - /** - * get all distinct records based on some property with excluded records - * @param keyExtractor - * @return - * @param - */ - public static Predicate getAllDistinctByKeyWithExclusion(Function keyExtractor, List excludedRecords) { - Set seen = ConcurrentHashMap.newKeySet(); - return t -> { - Object key = keyExtractor.apply(t); - if(seen.add(key)) { - return true; - } else { - excludedRecords.add(t); - return false; - } - }; - } - } diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java index 55419c86ba..f2a74a2e97 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java @@ -1,10 +1,12 @@ package org.finos.waltz.integration_test.inmem.service; + import org.finos.waltz.common.ListUtilities; import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; import org.finos.waltz.model.assessment_definition.AssessmentVisibility; import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingValidationResult; +import org.finos.waltz.model.assessment_rating.bulk_upload.ValidationError; import org.finos.waltz.model.bulk_upload.BulkUpdateMode; import org.finos.waltz.service.application.ApplicationService; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; @@ -18,14 +20,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import static org.finos.waltz.common.ListUtilities.asList; - import java.util.List; -import java.util.Optional; 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.*; @@ -79,11 +79,100 @@ public void previewAdds() { assertNotNull(result1, "Expected a result"); assertNoErrors(result1); assertExternalIdsMatch(result1, asList(kindExternalId)); + + /** + *EntityKind: ACTOR + */ + String actorName = mkName(stem,"previewActor"); + Long actorSchemeId = ratingSchemeHelper.createEmptyRatingScheme(name+"SchemeActor"); + ratingSchemeHelper.saveRatingItem(actorSchemeId,"Yes",0,"green","Y"); + actorHelper.createActor(actorName); + AssessmentDefinition def2 = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.ACTOR,actorSchemeId,actorName)); + + AssessmentRatingValidationResult result2 = bulkAssessmentRatingService.bulkPreview( + mkRef(def2.entityKind(),def2.id().get()), + mkGoodTsv(actorName), + BulkAssessmentRatingItemParser.InputFormat.TSV, + BulkUpdateMode.ADD_ONLY); + + assertNotNull(result2,"Expected a result"); + assertNoErrors(result2); + assertExternalIdsMatch(result2,asList(actorName)); + + } + + @Test + public void previewAddsForCardinalityChecks() { + String appName = mkName(stem, "previewApp1"); + String appExternalId = mkName(stem, "previewAppCode1"); + Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "SchemeApp")); + ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 0, "green", "Y"); + ratingSchemeHelper.saveRatingItem(schemeId, "No", 0, "red", "N"); + appHelper.createNewApp( + appName, + ouIds.root, + appExternalId); + AssessmentDefinition def = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.APPLICATION, schemeId, "Assessment")); + + /** + * Zero-One + */ + String[] externalIds = {appExternalId, appExternalId}; + String[] ratingCodes = {"Y", "N"}; + AssessmentRatingValidationResult result = bulkAssessmentRatingService.bulkPreview( + mkRef(def.entityKind(), def.id().get()), + mkTsvWithForCardinalityCheck(externalIds, ratingCodes), + BulkAssessmentRatingItemParser.InputFormat.TSV, + BulkUpdateMode.ADD_ONLY); + + result + .validatedItems() + .forEach(d -> { + if (d.parsedItem().ratingCode().equals("N")) { + assertTrue(d.errors().contains(ValidationError.DUPLICATE), "Should be complaining about the duplicate entity with rating N"); + } + }); + + assertEquals(2, result.validatedItems().size(), "Expected 2 items"); + } + + @Test + public void previewUpdateErrors() { + String appName = mkName(stem, "previewApp1"); + String appExternalId = mkName(stem, "previewAppCode1"); + Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "SchemeApp")); + ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 0, "green", "Y"); + ratingSchemeHelper.saveRatingItem(schemeId, "No", 0, "red", "N"); + appHelper.createNewApp( + appName, + ouIds.root, + appExternalId); + AssessmentDefinition def = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.APPLICATION, schemeId, "Assessment")); + + AssessmentRatingValidationResult result = bulkAssessmentRatingService.bulkPreview( + mkRef(def.entityKind(), def.id().get()), + mkBadTsv(appExternalId), + BulkAssessmentRatingItemParser.InputFormat.TSV, + BulkUpdateMode.ADD_ONLY); + + result + .validatedItems() + .forEach(d -> { + if (d.parsedItem().ratingCode().equals("badExternalId")) { + assertTrue(d.errors().contains(ValidationError.ENTITY_KIND_NOT_FOUND), "Should be complaining about the entity not found"); + } + if (d.parsedItem().ratingCode().equals("badRatingCode")) { + assertTrue(d.errors().contains(ValidationError.RATING_NOT_FOUND), "Should be complaining about the rating code not found"); + } + }); + + assertEquals(3, result.validatedItems().size(), "Expected 3 items"); } private long getAssessmentDefinition(EntityKind kind, Long schemeId, String name) { return assessmentHelper.createDefinition(schemeId, name + "Definition", "", AssessmentVisibility.PRIMARY, "Test", kind, null); } + private void assertNoErrors(AssessmentRatingValidationResult result) { assertTrue( all(result.validatedItems(), d -> isEmpty(d.errors())), @@ -97,14 +186,23 @@ private void assertExternalIdsMatch(AssessmentRatingValidationResult result, ListUtilities.map(result.validatedItems(), d -> d.parsedItem().externalId()), "Expected external ids do not match"); } + private String mkGoodTsv(String externalId) { return "externalId\tratingCode\tisReadOnly\tcomment\n" + externalId + "\tY\ttrue\tcomment\n"; } -} -/** - * previewAdd - * Test case around cardinality check - * previewUpdate - */ \ No newline at end of file + private String mkTsvWithForCardinalityCheck(String[] externalIds, String[] ratingCodes) { + return "externalId\tratingCode\tisReadOnly\tcomment\n" + + externalIds[0] + "\t" + ratingCodes[0] + "\ttrue\tcomment\n" + + externalIds[1] + "\t" + ratingCodes[1] + "\ttrue\tcomment\n"; + } + + private String mkBadTsv(String externalId) { + return "externalId\tratingCode\tisReadOnly\tcomment\n" + +"badExternalId\tY\ttrue\tcomment\n" + + externalId + "\tN\ttrue\tcomment\n" + + externalId + "\tbadRatingCode\ttrue\tcomment\n"; + } + +} \ No newline at end of file diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java index 739730c2ef..46de0221aa 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/AssessmentRatingParsedItem.java @@ -15,7 +15,7 @@ public interface AssessmentRatingParsedItem { @JsonAlias("external_id") String externalId(); - char ratingCode(); + String ratingCode(); @Value.Default default boolean isReadOnly() { return false; } 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 42ce5f4c56..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 @@ -14,7 +14,7 @@ @JsonSerialize(as= ImmutableBulkMeasurableRatingValidationResult.class) public interface BulkMeasurableRatingValidationResult { - List validatedItems(); + List validatedItems(); @Nullable BulkMeasurableRatingParseResult.BulkMeasurableRatingParseError error(); diff --git a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html index 9f28f9d650..927a6b12fd 100644 --- a/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html +++ b/waltz-ng/client/assessments/pages/edit/assessment-rating-bulk-upload.html @@ -58,24 +58,6 @@ -
{ vm.definition = r.data - console.log(r.data); }); const ratingSchemePromise = serviceBroker diff --git a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js index 6b8bd4d85e..ac311520dc 100644 --- a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js +++ b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js @@ -58,6 +58,7 @@ function controller($q, const definitionId = $stateParams.id; const vm = initialiseData(this, initialState); + const bulkEditableKinds = ["ACTOR", "APPLICATION", "CHANGE_INITIATIVE", "CHANGE_UNIT", "LEGAL_ENTITY", "LICENCE"]; userService .whoami() @@ -68,7 +69,7 @@ function controller($q, .loadViewData(CORE_API.AssessmentDefinitionStore.getById, [definitionId]) .then(r => { vm.definition = r.data; - vm.allowBulkEditing = true; + vm.allowBulkEditing = _.includes(bulkEditableKinds, vm.definition.entityKind); }); const ratingSchemePromise = serviceBroker diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java index fd80513fd0..9f78556c3d 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/AssessmentRatingService.java @@ -160,12 +160,12 @@ public boolean unlock(EntityReference entityReference, public boolean remove(RemoveAssessmentRatingCommand command, String username) throws InsufficientPrivelegeException { -// verifyPermission( -// Operation.REMOVE, -// command.entityReference(), -// command.assessmentDefinitionId(), -// command.ratingId(), -// username); + verifyPermission( + Operation.REMOVE, + command.entityReference(), + command.assessmentDefinitionId(), + command.ratingId(), + username); String ratingRemovedName = ratingSchemeDAO.findRatingSchemeItemsByIds(asSet(command.ratingId())) .stream() diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java index 0628f79cd9..1d17d4412d 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java @@ -10,7 +10,6 @@ import org.finos.waltz.model.assessment_rating.ImmutableAssessmentRating; import org.finos.waltz.model.assessment_rating.bulk_upload.*; import org.finos.waltz.model.bulk_upload.BulkUpdateMode; -import org.finos.waltz.model.bulk_upload.measurable_rating.ImmutableBulkMeasurableRatingApplyResult; import org.finos.waltz.model.exceptions.NotAuthorizedException; import org.finos.waltz.model.rating.RatingSchemeItem; import org.finos.waltz.model.user.SystemRole; @@ -34,9 +33,6 @@ import java.sql.Timestamp; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -46,13 +42,13 @@ import static org.finos.waltz.common.SetUtilities.asSet; import static org.finos.waltz.common.StringUtilities.sanitizeCharacters; import static org.finos.waltz.data.JooqUtilities.summarizeResults; -import static org.finos.waltz.data.SelectorUtilities.getAllDistinctByKeyWithExclusion; import static org.finos.waltz.model.EntityReference.mkRef; import static org.jooq.lambda.tuple.Tuple.tuple; @Service public class BulkAssessmentRatingService { private static final Logger LOG = LoggerFactory.getLogger(MeasurableRatingService.class); + private static final String PROVENANCE = "bulkAssessmentDefinitionUpdate"; private static final String DUMMY_USER = "test"; @@ -62,7 +58,7 @@ public class BulkAssessmentRatingService { private final RatingSchemeService ratingSchemeService; - private UserRoleService userRoleService; + private final UserRoleService userRoleService; private final DSLContext dsl; @@ -95,9 +91,9 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe Set ratingSchemeItemsBySchemeIds = ratingSchemeService.findRatingSchemeItemsBySchemeIds(asSet(assessmentDefinition.ratingSchemeId())); Map ratingSchemeItemsByCode = indexBy(ratingSchemeItemsBySchemeIds, RatingSchemeItem::rating); - CommonTableFields ctf = CommonTableFieldsRegistry.determineCommonTableFields(assessmentDefinition.entityKind(), "target"); - Set immutableEntityKinds = dsl.select(ctf.nameField(), ctf.idField(), ctf.externalIdField()) + Set targetReferences = dsl + .select(ctf.nameField(), ctf.idField(), ctf.externalIdField()) .from(ctf.table()) .where(ctf.isActiveCondition() .and(ctf.externalIdField().isNotNull())) @@ -111,17 +107,17 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe .build()) .collect(Collectors.toSet()); - - Map kindsByExternalIds = indexBy(immutableEntityKinds, k -> k.externalId().get()); + Map targetRefsByExternalId = indexBy(targetReferences, k -> k.externalId().get()); boolean isCardinalityZeroToOne = assessmentDefinition.cardinality().equals(Cardinality.ZERO_ONE); - Set seen = ConcurrentHashMap.newKeySet(); - List>> validatedEntries = result + Set seen = new HashSet<>(); + + List>> validatedEntries = result .parsedItems() .stream() .map(d -> { - ImmutableEntityReference immutableEntityReference = kindsByExternalIds.get(d.externalId()); + EntityReference entityReference = targetRefsByExternalId.get(d.externalId()); RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); - return tuple(d, immutableEntityReference, ratingSchemeItem); + return tuple(d, entityReference, ratingSchemeItem); }) .map(t -> { Set validationErrors = new HashSet<>(); @@ -223,8 +219,6 @@ public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, } Timestamp now = DateTimeUtilities.nowUtcTimestamp(); - AssessmentDefinition assessmentDefinition = assessmentDefinitionService.getById(assessmentRef.id()); - Set toAdd = preview .validatedItems() .stream() @@ -276,40 +270,40 @@ public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, r -> r.id().orElse(0L)); Set auditLogs = Stream.concat( - preview - .removals() - .stream() - .map(t -> { - RatingSchemeItem rsi = ratingItemsById.get(t.v2); - ChangeLogRecord r = new ChangeLogRecord(); - r.setMessage(format( - "Bulk Rating Update - Removed assessment rating for: %s/%s (%d)", - rsi == null ? "?" : rsi.name(), - rsi == null ? "?" : rsi.externalId().orElse("-"), - t.v2)); - r.setOperation(Operation.REMOVE.name()); - r.setParentKind(t.v1.kind().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.ratingSchemeItem(), d.changeOperation())); - r.setOperation(toChangeLogOperation(d.changeOperation()).name()); - r.setParentKind(EntityKind.APPLICATION.name()); - r.setParentId(d.entityKindReference().id()); - r.setCreatedAt(now); - r.setUserId(userId); - r.setSeverity(Severity.INFORMATION.name()); - return r; - })) + preview + .removals() + .stream() + .map(t -> { + RatingSchemeItem rsi = ratingItemsById.get(t.v2); + ChangeLogRecord r = new ChangeLogRecord(); + r.setMessage(format( + "Bulk Rating Update - Removed assessment rating for: %s/%s (%d)", + rsi == null ? "?" : rsi.name(), + rsi == null ? "?" : rsi.externalId().orElse("-"), + t.v2)); + r.setOperation(Operation.REMOVE.name()); + r.setParentKind(t.v1.kind().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.ratingSchemeItem(), d.changeOperation())); + r.setOperation(toChangeLogOperation(d.changeOperation()).name()); + r.setParentKind(EntityKind.APPLICATION.name()); + r.setParentId(d.entityKindReference().id()); + r.setCreatedAt(now); + r.setUserId(userId); + r.setSeverity(Severity.INFORMATION.name()); + return r; + })) .collect(Collectors.toSet()); long skipCount = preview @@ -335,7 +329,6 @@ public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, removalCount, changeLogCount); -// throw new RuntimeException("Boooomrahhh!!!"); return ImmutableBulkAssessmentRatingApplyResult .builder() .recordsAdded(insertCount) @@ -374,10 +367,4 @@ private void verifyUserHasPermissions(String userId) { throw new NotAuthorizedException(); } } -} - - -/** - * Preview: - * Check for user selectable for rating - */ \ No newline at end of file +} \ No newline at end of file diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java index 21fb5e1b05..afc0d43a1d 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActorHelper.java @@ -18,7 +18,7 @@ public Long createActor(String nameStem) { ImmutableActorCreateCommand .builder() .name(nameStem) - .externalId(mkName(nameStem)) + .externalId(nameStem) .description(nameStem + " Desc") .isExternal(true) .build(), From 052121eade02e61bb9b93bc0faeefa63cf7cc8cf Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Thu, 17 Oct 2024 14:17:18 +0530 Subject: [PATCH 4/7] CTCTOWALTZ-3337: [x] Fixed the PR review comments. [x] Added few more tests #7156 --- .../BulkAssessmentRatingSelector.svelte | 2 +- .../bulk-assessment-rating-selector/DataCellErrorTooltip.svelte | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte index a42e745c16..7401753628 100644 --- a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte @@ -209,7 +209,7 @@ INV0737 L TRUE description - + {/if} diff --git a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte index 0565322f5f..3e62378f33 100644 --- a/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte @@ -12,5 +12,6 @@ \ No newline at end of file From eede76877267d5f962fd2cd88648ee1b30826baf Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Thu, 17 Oct 2024 14:29:43 +0530 Subject: [PATCH 5/7] CTCTOWALTZ-3337: [x] Fixed the PR review comments. #7156 --- .../BulkAssessmentRatingServiceTest.java | 26 ++++++------------- .../BulkAssessmentRatingService.java | 17 +++--------- .../api/AssessmentRatingEndpoint.java | 12 ++++----- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java index f2a74a2e97..176ed11e35 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java @@ -7,10 +7,8 @@ import org.finos.waltz.model.assessment_definition.AssessmentVisibility; import org.finos.waltz.model.assessment_rating.bulk_upload.AssessmentRatingValidationResult; import org.finos.waltz.model.assessment_rating.bulk_upload.ValidationError; -import org.finos.waltz.model.bulk_upload.BulkUpdateMode; import org.finos.waltz.service.application.ApplicationService; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; -import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingItemParser; import org.finos.waltz.service.assessment_rating.BulkAssessmentRatingService; import org.finos.waltz.test_common.helpers.ActorHelper; import org.finos.waltz.test_common.helpers.AppHelper; @@ -72,9 +70,7 @@ public void previewAdds() { AssessmentRatingValidationResult result1 = bulkAssessmentRatingService.bulkPreview( mkRef(def1.entityKind(), def1.id().get()), - mkGoodTsv(kindExternalId), - BulkAssessmentRatingItemParser.InputFormat.TSV, - BulkUpdateMode.ADD_ONLY); + mkGoodTsv(kindExternalId)); assertNotNull(result1, "Expected a result"); assertNoErrors(result1); @@ -91,9 +87,7 @@ public void previewAdds() { AssessmentRatingValidationResult result2 = bulkAssessmentRatingService.bulkPreview( mkRef(def2.entityKind(),def2.id().get()), - mkGoodTsv(actorName), - BulkAssessmentRatingItemParser.InputFormat.TSV, - BulkUpdateMode.ADD_ONLY); + mkGoodTsv(actorName)); assertNotNull(result2,"Expected a result"); assertNoErrors(result2); @@ -103,16 +97,16 @@ public void previewAdds() { @Test public void previewAddsForCardinalityChecks() { - String appName = mkName(stem, "previewApp1"); - String appExternalId = mkName(stem, "previewAppCode1"); - Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "SchemeApp")); + String appName = mkName(stem, "previewApp3"); + String appExternalId = mkName(stem, "previewAppCode3"); + Long schemeId = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "SchemeApp1")); ratingSchemeHelper.saveRatingItem(schemeId, "Yes", 0, "green", "Y"); ratingSchemeHelper.saveRatingItem(schemeId, "No", 0, "red", "N"); appHelper.createNewApp( appName, ouIds.root, appExternalId); - AssessmentDefinition def = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.APPLICATION, schemeId, "Assessment")); + AssessmentDefinition def = assessmentDefinitionService.getById(getAssessmentDefinition(EntityKind.APPLICATION, schemeId, "Assessment1")); /** * Zero-One @@ -121,9 +115,7 @@ public void previewAddsForCardinalityChecks() { String[] ratingCodes = {"Y", "N"}; AssessmentRatingValidationResult result = bulkAssessmentRatingService.bulkPreview( mkRef(def.entityKind(), def.id().get()), - mkTsvWithForCardinalityCheck(externalIds, ratingCodes), - BulkAssessmentRatingItemParser.InputFormat.TSV, - BulkUpdateMode.ADD_ONLY); + mkTsvWithForCardinalityCheck(externalIds, ratingCodes)); result .validatedItems() @@ -151,9 +143,7 @@ public void previewUpdateErrors() { AssessmentRatingValidationResult result = bulkAssessmentRatingService.bulkPreview( mkRef(def.entityKind(), def.id().get()), - mkBadTsv(appExternalId), - BulkAssessmentRatingItemParser.InputFormat.TSV, - BulkUpdateMode.ADD_ONLY); + mkBadTsv(appExternalId)); result .validatedItems() diff --git a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java index 1d17d4412d..ad235c846c 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java @@ -75,11 +75,9 @@ public BulkAssessmentRatingService(AssessmentDefinitionService assessmentDefinit } public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentReference, - String inputStr, - BulkAssessmentRatingItemParser.InputFormat format, - BulkUpdateMode mode) { + String inputStr) { - AssessmentRatingParsedResult result = new BulkAssessmentRatingItemParser().parse(inputStr, format); + AssessmentRatingParsedResult result = new BulkAssessmentRatingItemParser().parse(inputStr, BulkAssessmentRatingItemParser.InputFormat.TSV); if (result.error() != null) { return ImmutableAssessmentRatingValidationResult .builder() @@ -168,8 +166,6 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe Set> toAdd = SetUtilities.map(assessmentRatingDiffResult.otherOnly(), d -> tuple(d.entityReference(), d.ratingId())); Set> toUpdate = SetUtilities.map(assessmentRatingDiffResult.differingIntersection(), d -> tuple(d.entityReference(), d.ratingId())); - Set> toRemove = SetUtilities.map(assessmentRatingDiffResult.waltzOnly(), d -> tuple(d.entityReference(), d.ratingId())); - List validatedItems = validatedEntries .stream() @@ -201,15 +197,12 @@ public AssessmentRatingValidationResult bulkPreview(EntityReference assessmentRe return ImmutableAssessmentRatingValidationResult .builder() .validatedItems(validatedItems) - .removals(mode == BulkUpdateMode.REPLACE - ? toRemove - : emptySet()) + .removals(emptySet()) .build(); } public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, AssessmentRatingValidationResult preview, - BulkUpdateMode mode, String userId) { verifyUserHasPermissions(userId); @@ -317,9 +310,7 @@ public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, DSLContext tx = ctx.dsl(); int insertCount = summarizeResults(tx.batchInsert(toAdd).execute()); int updateCount = summarizeResults(tx.batch(toUpdate).execute()); - int removalCount = mode == BulkUpdateMode.REPLACE - ? summarizeResults(tx.batch(toRemove).execute()) - : 0; + int removalCount = 0; int changeLogCount = summarizeResults(tx.batchInsert(auditLogs).execute()); LOG.info( diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java index a1399d48f0..e7d99dae12 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java @@ -69,7 +69,8 @@ public class AssessmentRatingEndpoint implements Endpoint { public AssessmentRatingEndpoint(AssessmentRatingService assessmentRatingService, AssessmentDefinitionService assessmentDefinitionService, AssessmentRatingPermissionChecker assessmentRatingPermissionChecker, - UserRoleService userRoleService, BulkAssessmentRatingService bulkAssessmentRatingService) { + UserRoleService userRoleService, + BulkAssessmentRatingService bulkAssessmentRatingService) { checkNotNull(assessmentRatingService, "assessmentRatingService cannot be null"); checkNotNull(assessmentDefinitionService, "assessmentDefinitionService cannot be null"); @@ -281,11 +282,8 @@ private void registerPreviewBulkAssessmentRatingChanges(String path) { postForDatum(path, (req, resp) -> { EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); String modeStr = req.queryParams("mode"); - String formatStr = req.queryParams("format"); - BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); - BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); String body = req.body(); - return bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body, format, mode); + return bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); }); } @@ -297,8 +295,8 @@ private void registerApplyBulkAssessmentRatingChanges(String path) { BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); String body = req.body(); - AssessmentRatingValidationResult preview = bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body, format, mode); - return bulkAssessmentRatingService.apply(assessmentDefRef, preview, mode, getUsername(req)); + AssessmentRatingValidationResult preview = bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); + return bulkAssessmentRatingService.apply(assessmentDefRef, preview, getUsername(req)); }); } From 1aa6d377b170fed0e9933ad5b5816a00e72ab3de Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Thu, 17 Oct 2024 14:32:18 +0530 Subject: [PATCH 6/7] CTCTOWALTZ-3337: [x] Fixed the PR review comments. #7156 --- .../waltz/web/endpoints/api/AssessmentRatingEndpoint.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java index e7d99dae12..0672e642f1 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/AssessmentRatingEndpoint.java @@ -281,7 +281,6 @@ private void verifyCanWrite(Request request, long defId) { private void registerPreviewBulkAssessmentRatingChanges(String path) { postForDatum(path, (req, resp) -> { EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); - String modeStr = req.queryParams("mode"); String body = req.body(); return bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); }); @@ -290,10 +289,6 @@ private void registerPreviewBulkAssessmentRatingChanges(String path) { private void registerApplyBulkAssessmentRatingChanges(String path) { postForDatum(path, (req, resp) -> { EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); - String modeStr = req.queryParams("mode"); - String formatStr = req.queryParams("format"); - BulkUpdateMode mode = EnumUtilities.readEnum(modeStr, BulkUpdateMode.class, s -> BulkUpdateMode.ADD_ONLY); - BulkAssessmentRatingItemParser.InputFormat format = EnumUtilities.readEnum(formatStr, BulkAssessmentRatingItemParser.InputFormat.class, s -> BulkAssessmentRatingItemParser.InputFormat.TSV); String body = req.body(); AssessmentRatingValidationResult preview = bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); return bulkAssessmentRatingService.apply(assessmentDefRef, preview, getUsername(req)); From 2da4c3bf4f5f3f537404745bfe26f4de2b21b1ab Mon Sep 17 00:00:00 2001 From: Devendra-Ramesh Patil Date: Thu, 17 Oct 2024 14:38:20 +0530 Subject: [PATCH 7/7] CTCTOWALTZ-3337: [x] Added bulk edit for Measurable #7156 --- .../client/assessments/pages/view/assessment-definition-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js index ac311520dc..fe549197b4 100644 --- a/waltz-ng/client/assessments/pages/view/assessment-definition-view.js +++ b/waltz-ng/client/assessments/pages/view/assessment-definition-view.js @@ -58,7 +58,7 @@ function controller($q, const definitionId = $stateParams.id; const vm = initialiseData(this, initialState); - const bulkEditableKinds = ["ACTOR", "APPLICATION", "CHANGE_INITIATIVE", "CHANGE_UNIT", "LEGAL_ENTITY", "LICENCE"]; + const bulkEditableKinds = ["ACTOR", "APPLICATION", "CHANGE_INITIATIVE", "CHANGE_UNIT", "LEGAL_ENTITY", "LICENCE", "MEASURABLE"]; userService .whoami()