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..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 @@ -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; @@ -71,5 +75,4 @@ public static Condition mkApplicationConditions(Application appTable, IdSelectio } } - } 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..176ed11e35 --- /dev/null +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/BulkAssessmentRatingServiceTest.java @@ -0,0 +1,198 @@ +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.service.application.ApplicationService; +import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; +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 java.util.List; + +import static org.finos.waltz.common.CollectionUtilities.all; +import static org.finos.waltz.common.CollectionUtilities.isEmpty; +import static org.finos.waltz.common.ListUtilities.asList; +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; +import static org.junit.jupiter.api.Assertions.*; + +public class 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", 0, "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)); + + 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)); + + assertNotNull(result2,"Expected a result"); + assertNoErrors(result2); + assertExternalIdsMatch(result2,asList(actorName)); + + } + + @Test + public void previewAddsForCardinalityChecks() { + 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, "Assessment1")); + + /** + * Zero-One + */ + String[] externalIds = {appExternalId, appExternalId}; + String[] ratingCodes = {"Y", "N"}; + AssessmentRatingValidationResult result = bulkAssessmentRatingService.bulkPreview( + mkRef(def.entityKind(), def.id().get()), + mkTsvWithForCardinalityCheck(externalIds, ratingCodes)); + + 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)); + + 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())), + "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"; + } + + 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 new file mode 100644 index 0000000000..46de0221aa --- /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(); + + String 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/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/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..c8013eeb6d --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_rating/bulk_upload/ValidationError.java @@ -0,0 +1,9 @@ +package org.finos.waltz.model.assessment_rating.bulk_upload; + +public enum ValidationError { + ENTITY_KIND_NOT_FOUND, + RATING_NOT_FOUND, + DUPLICATE, + + RATING_NOT_USER_SELECTABLE +} 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..7401753628 --- /dev/null +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/BulkAssessmentRatingSelector.svelte @@ -0,0 +1,323 @@ + + +
+ 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} + + {#if item.errors.length > 0} + + + + + + {/if} +
+
+ {/if} + + + +{/if} + +{#if mode === Modes.APPLY} + + + + + + + + + + + + + + + + + + + +
Added Records 0}> + {applyData.recordsAdded} +
Updated Records 0}> + {applyData.recordsUpdated} +
Removed Records 0}> + {applyData.recordsRemoved} +
Skipped Records 0}> + {applyData.skippedRows} +
+ + {#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/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..3e62378f33 --- /dev/null +++ b/waltz-ng/client/assessments/components/bulk-assessment-rating-selector/DataCellErrorTooltip.svelte @@ -0,0 +1,17 @@ + + + +{#if errors && errors.length} + {#each errors as error} +
{error}
+ {/each} +{/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..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,22 +58,12 @@ -
-

- Use the text box below to provide assessment ratings for application as comma or tab separated value -
- e.g. asset-code, rating-name (or code), comments(optional) -

- +
- - + +
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..9ec52af07d 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,9 @@ function controller($q, const loadAll = () => { serviceBroker .loadViewData(CORE_API.AssessmentDefinitionStore.getById, [definitionId]) - .then(r => vm.definition = r.data); + .then(r => { + vm.definition = 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..fe549197b4 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, @@ -59,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", "MEASURABLE"]; userService .whoami() diff --git a/waltz-ng/client/svelte-stores/assessment-rating-store.js b/waltz-ng/client/svelte-stores/assessment-rating-store.js index f6d7296808..2a3efc102f 100644 --- a/waltz-ng/client/svelte-stores/assessment-rating-store.js +++ b/waltz-ng/client/svelte-stores/assessment-rating-store.js @@ -80,6 +80,20 @@ export function mkAssessmentRatingStore() { null, {force}); + const bulkPreview = (entityRef, rawText) => + remote + .execute( + "POST", + `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 { findByDefinitionId, findForEntityReference, @@ -92,7 +106,9 @@ export function mkAssessmentRatingStore() { updateComment, updateRating, findSummaryCounts, - hasMultiValuedAssessments + hasMultiValuedAssessments, + bulkPreview, + bulkApply }; } 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..ad235c846c --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/assessment_rating/BulkAssessmentRatingService.java @@ -0,0 +1,361 @@ +package org.finos.waltz.service.assessment_rating; + +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.*; +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.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.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.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.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.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 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) { + + AssessmentRatingParsedResult result = new BulkAssessmentRatingItemParser().parse(inputStr, BulkAssessmentRatingItemParser.InputFormat.TSV); + 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 targetReferences = 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 targetRefsByExternalId = indexBy(targetReferences, k -> k.externalId().get()); + boolean isCardinalityZeroToOne = assessmentDefinition.cardinality().equals(Cardinality.ZERO_ONE); + Set seen = new HashSet<>(); + + List>> validatedEntries = result + .parsedItems() + .stream() + .map(d -> { + EntityReference entityReference = targetRefsByExternalId.get(d.externalId()); + RatingSchemeItem ratingSchemeItem = ratingSchemeItemsByCode.get(String.valueOf(d.ratingCode())); + return tuple(d, entityReference, ratingSchemeItem); + }) + .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()); + + 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())); + + 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(emptySet()) + .build(); + } + + public BulkAssessmentRatingApplyResult apply(EntityReference assessmentRef, + AssessmentRatingValidationResult preview, + String userId) { + + verifyUserHasPermissions(userId); + + if (preview.error() != null) { + throw new IllegalStateException("Cannot apply changes with formatting errors"); + } + Timestamp now = DateTimeUtilities.nowUtcTimestamp(); + + 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 = 0; + int changeLogCount = summarizeResults(tx.batchInsert(auditLogs).execute()); + + LOG.info( + "Batch assessment rating: {} adds, {} updates, {} removes, {} changeLogs", + insertCount, + updateCount, + removalCount, + changeLogCount); + + 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(); + } + } +} \ No newline at end of file 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..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 @@ -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(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..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 @@ -38,8 +38,7 @@ 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.web.WebUtilities.*; @Service public class AssessmentDefinitionEndpoint implements Endpoint { @@ -70,6 +69,7 @@ public void register() { String findByRefPath = WebUtilities.mkPath(BASE_URL, "kind", ":kind", "id", ":id"); String removeByIdPath = WebUtilities.mkPath(BASE_URL, "id", ":id"); + DatumRoute getByIdRoute = (request, response) -> assessmentDefinitionService.getById(WebUtilities.getId(request)); ListRoute findAllRoute = (request, response) -> assessmentDefinitionService.findAll(); ListRoute findByKindRoute = (request, response) -> assessmentDefinitionService.findByEntityKind(WebUtilities.getKind(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..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 @@ -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,14 @@ 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 +81,7 @@ public AssessmentRatingEndpoint(AssessmentRatingService assessmentRatingService, this.assessmentDefinitionService = assessmentDefinitionService; this.assessmentRatingPermissionChecker = assessmentRatingPermissionChecker; this.userRoleService = userRoleService; + this.bulkAssessmentRatingService = bulkAssessmentRatingService; } @@ -93,6 +103,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 +278,22 @@ 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 body = req.body(); + return bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); + }); + } + + private void registerApplyBulkAssessmentRatingChanges(String path) { + postForDatum(path, (req, resp) -> { + EntityReference assessmentDefRef = mkRef(EntityKind.ASSESSMENT_DEFINITION, getId(req)); + String body = req.body(); + AssessmentRatingValidationResult preview = bulkAssessmentRatingService.bulkPreview(assessmentDefRef, body); + return bulkAssessmentRatingService.apply(assessmentDefRef, preview, getUsername(req)); + }); + } + }