From f229f3a6da45d4e6494bf4a6a8ee53622e0ff6ff Mon Sep 17 00:00:00 2001 From: David Watkins Date: Fri, 20 Sep 2024 10:49:03 +0100 Subject: [PATCH 1/7] Assessment Rippler #CTCTOWALTZ-3352 #7146 --- .../AssessmentRatingRippler.java | 362 ++++++++++++++++++ .../inmem/service/AssessmentRipplerTest.java | 56 +++ .../org/finos/waltz/model/PairDiffResult.java | 71 ++++ 3 files changed, 489 insertions(+) create mode 100644 waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java create mode 100644 waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/PairDiffResult.java diff --git a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java new file mode 100644 index 0000000000..89a118a831 --- /dev/null +++ b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java @@ -0,0 +1,362 @@ +package org.finos.waltz.data.assessment_rating; + +import org.finos.waltz.common.Checks; +import org.finos.waltz.common.MapUtilities; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityLifecycleStatus; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.PairDiffResult; +import org.finos.waltz.schema.Tables; +import org.finos.waltz.schema.tables.Actor; +import org.finos.waltz.schema.tables.Application; +import org.finos.waltz.schema.tables.AssessmentDefinition; +import org.finos.waltz.schema.tables.AssessmentRating; +import org.finos.waltz.schema.tables.LogicalFlow; +import org.finos.waltz.schema.tables.Measurable; +import org.finos.waltz.schema.tables.MeasurableRating; +import org.finos.waltz.schema.tables.PhysicalFlow; +import org.finos.waltz.schema.tables.PhysicalSpecDataType; +import org.finos.waltz.schema.tables.PhysicalSpecification; +import org.finos.waltz.schema.tables.RatingScheme; +import org.finos.waltz.schema.tables.RatingSchemeItem; +import org.finos.waltz.schema.tables.records.AssessmentDefinitionRecord; +import org.finos.waltz.schema.tables.records.AssessmentRatingRecord; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.Record4; +import org.jooq.Result; +import org.jooq.Select; +import org.jooq.impl.DSL; +import org.jooq.lambda.tuple.Tuple2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Timestamp; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; +import static org.finos.waltz.common.Checks.checkTrue; +import static org.finos.waltz.common.DateTimeUtilities.nowUtcTimestamp; +import static org.finos.waltz.common.StringUtilities.safeEq; +import static org.finos.waltz.data.JooqUtilities.summarizeResults; +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.model.PairDiffResult.mkPairDiff; +import static org.jooq.lambda.tuple.Tuple.tuple; + +public class AssessmentRatingRippler { + + private static final Logger LOG = LoggerFactory.getLogger(AssessmentRatingRippler.class); + + private static final PhysicalFlow pf = Tables.PHYSICAL_FLOW; + private static final PhysicalSpecification ps = Tables.PHYSICAL_SPECIFICATION; + private static final PhysicalSpecDataType psdt = Tables.PHYSICAL_SPEC_DATA_TYPE; + private static final AssessmentDefinition ad = Tables.ASSESSMENT_DEFINITION; + private static final AssessmentRating ar = Tables.ASSESSMENT_RATING; + private static final Application app = Tables.APPLICATION; + private static final Actor act = Tables.ACTOR; + private static final LogicalFlow lf = Tables.LOGICAL_FLOW; + private static final RatingSchemeItem rsi = Tables.RATING_SCHEME_ITEM; + private static final RatingScheme rs = Tables.RATING_SCHEME; + private static final MeasurableRating mr = Tables.MEASURABLE_RATING; + private static final Measurable m = Tables.MEASURABLE; + + + @SafeVarargs + public static void rippleAssessments(DSLContext tx, + String userId, + String provenance, + Tuple2... listOfSrcAndTargetDefExtIds) { + for (Tuple2 fromTo: listOfSrcAndTargetDefExtIds) { + rippleAssessments(tx, userId, provenance, fromTo.v1, fromTo.v2); + } + } + + + private static void rippleAssessments(DSLContext tx, + String userId, + String provenance, + String from, + String to) { + + Map defs = tx + .selectFrom(ad) + .where(ad.EXTERNAL_ID.in(from, to)) + .fetchMap(r -> r.get(ad.EXTERNAL_ID)); + + AssessmentDefinitionRecord fromDef = defs.get(from); + Checks.checkNotNull(fromDef, format("Cannot ripple assessment as definition: %s not found", from)); + AssessmentDefinitionRecord toDef = defs.get(to); + Checks.checkNotNull(toDef, format("Cannot ripple assessment as definition: %s not found", toDef)); + rippleAssessments(tx, userId, provenance, fromDef, toDef); + } + + + private static void rippleAssessments(DSLContext tx, + String userId, + String provenance, + AssessmentDefinitionRecord from, + AssessmentDefinitionRecord to) { + checkTrue( + from.getRatingSchemeId().equals(to.getRatingSchemeId()), + "Assessments must share a rating scheme when rippling (%s -> %s)", + from.getName(), + to.getName()); + + Tuple2 kinds = tuple( + EntityKind.valueOf(from.getEntityKind()), + EntityKind.valueOf(to.getEntityKind())); + + if (kinds.equals(tuple(EntityKind.PHYSICAL_SPECIFICATION, EntityKind.PHYSICAL_FLOW))) { + // PHYSICAL_SPEC -> PHYSICAL_FLOW + rippleAssessment( + tx, + userId, + provenance, + from, + to, + tx.select(pf.ID, ar.RATING_ID, ps.ID, ps.NAME) + .from(ar) + .innerJoin(ps) + .on(ps.ID.eq(ar.ENTITY_ID)) + .innerJoin(pf) + .on(pf.SPECIFICATION_ID.eq(ps.ID)) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) + .and(ps.IS_REMOVED.isFalse()))); + } else if (kinds.equals(tuple(EntityKind.PHYSICAL_FLOW, EntityKind.LOGICAL_DATA_FLOW))) { + // PHYSICAL_FLOW -> LOGICAL + rippleAssessment( + tx, + userId, + provenance, + from, + to, + tx.select(lf.ID, ar.RATING_ID, pf.ID, pf.NAME) + .from(ar) + .innerJoin(pf).on(pf.ID.eq(ar.ENTITY_ID)) + .innerJoin(lf).on(lf.ID.eq(pf.LOGICAL_FLOW_ID)) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) + .and(pf.IS_REMOVED.isFalse()) + .and(pf.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())))); + } else if (kinds.v1 == EntityKind.LOGICAL_DATA_FLOW && (kinds.v2 == EntityKind.APPLICATION || kinds.v2 == EntityKind.ACTOR)) { + // LOGICAL -> APP | ACTOR + Condition lfIsActiveCondition = lf.IS_REMOVED.isFalse() + .and(lf.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())); + Actor sourceActor = act.as("source_actor"); + Actor targetActor = act.as("target_actor"); + Application sourceApp = app.as("source_app"); + Application targetApp = app.as("target_app"); + Condition sourceActorJoinCondition = lf.SOURCE_ENTITY_KIND.eq(EntityKind.ACTOR.name()).and(sourceActor.ID.eq(lf.SOURCE_ENTITY_ID)); + Condition targetActorJoinCondition = lf.TARGET_ENTITY_KIND.eq(EntityKind.ACTOR.name()).and(targetActor.ID.eq(lf.TARGET_ENTITY_ID)); + Condition sourceAppJoinCondition = lf.SOURCE_ENTITY_KIND.eq(EntityKind.APPLICATION.name()).and(sourceApp.ID.eq(lf.SOURCE_ENTITY_ID)); + Condition targetAppJoinCondition = lf.TARGET_ENTITY_KIND.eq(EntityKind.APPLICATION.name()).and(targetApp.ID.eq(lf.TARGET_ENTITY_ID)); + Field sourceName = DSL.coalesce(sourceApp.NAME, sourceActor.NAME, DSL.value("??")); + Field targetName = DSL.coalesce(targetApp.NAME, targetActor.NAME, DSL.value("??")); + Field flowDesc = DSL.concat( + DSL.value("Flow: "), + sourceName, + DSL.value(" -> "), + targetName); + rippleAssessment( + tx, + userId, + provenance, + from, + to, + tx.select(lf.SOURCE_ENTITY_ID, ar.RATING_ID, lf.ID, flowDesc) + .from(ar) + .innerJoin(lf).on(lf.ID.eq(ar.ENTITY_ID)) + .leftJoin(sourceApp).on(sourceAppJoinCondition) + .leftJoin(targetApp).on(targetAppJoinCondition) + .leftJoin(sourceActor).on(sourceActorJoinCondition) + .leftJoin(targetActor).on(targetActorJoinCondition) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) + .and(lf.SOURCE_ENTITY_KIND.eq(kinds.v2.name())) + .and(lfIsActiveCondition)) + .union(DSL + .select(lf.TARGET_ENTITY_ID, ar.RATING_ID, lf.ID, flowDesc) + .from(ar) + .innerJoin(lf).on(lf.ID.eq(ar.ENTITY_ID)) + .leftJoin(sourceApp).on(sourceAppJoinCondition) + .leftJoin(targetApp).on(targetAppJoinCondition) + .leftJoin(sourceActor).on(sourceActorJoinCondition) + .leftJoin(targetActor).on(targetActorJoinCondition) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) + .and(lf.TARGET_ENTITY_KIND.eq(kinds.v2.name())) + .and(lfIsActiveCondition)))); + } else if (kinds.v1 == EntityKind.MEASURABLE && kinds.v2 == EntityKind.APPLICATION) { + // MEASURABLE -> APPLICTION + rippleAssessment( + tx, + userId, + provenance, + from, + to, + tx.select(mr.ENTITY_ID, ar.RATING_ID, m.ID, m.NAME) + .from(ar) + .innerJoin(mr).on(mr.MEASURABLE_ID.eq(ar.ENTITY_ID)) + .innerJoin(m).on(m.ID.eq(mr.MEASURABLE_ID)) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId())) + .and(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()))); + } else if (kinds.v1 == EntityKind.MEASURABLE && kinds.v2 == EntityKind.MEASURABLE_RATING) { + // MEASURABLE -> MEASURABLE_RATING + rippleAssessment( + tx, + userId, + provenance, + from, + to, + tx.select(mr.ID, ar.RATING_ID, m.ID, m.NAME) + .from(ar) + .innerJoin(mr).on(mr.MEASURABLE_ID.eq(ar.ENTITY_ID)) + .innerJoin(m).on(m.ID.eq(mr.MEASURABLE_ID)) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId())) + .and(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()))); + } else { + throw new UnsupportedOperationException(format( + "Cannot ripple assessment from kind: %s to kind: %s", + kinds.v1, + kinds.v2)); + } + } + + + private static void rippleAssessment(DSLContext tx, + String userId, + String provenance, + AssessmentDefinitionRecord from, + AssessmentDefinitionRecord to, + Select> targetAndRatingProvider) { + Timestamp now = nowUtcTimestamp(); + Set required = MapUtilities + .groupAndThen( + targetAndRatingProvider.fetch(), + r -> tuple(r.get(0, Long.class), r.get(1, Long.class)), + xs -> xs.stream() + .map(x -> mkRef( + EntityKind.valueOf(from.getEntityKind()), + x.get(2, Long.class), + x.get(3, String.class))) + .sorted(Comparator.comparing(d -> d.name().orElse("??"))) + .collect(Collectors.toList())) + .entrySet() + .stream() + .map(kv -> { + String desc = calcDescription(from, kv.getValue()); + + AssessmentRatingRecord record = tx.newRecord(ar); + record.setAssessmentDefinitionId(to.getId()); + record.setEntityId(kv.getKey().v1); + record.setEntityKind(to.getEntityKind()); + record.setRatingId(kv.getKey().v2); + record.setLastUpdatedAt(now); + record.setIsReadonly(true); + record.setLastUpdatedBy(userId); + record.setProvenance(provenance); + record.setDescription(desc); + return record; + }) + .collect(toSet()); + + Result existing = tx + .selectFrom(ar) + .where(ar.ASSESSMENT_DEFINITION_ID.eq(to.getId())) + .fetch(); + + PairDiffResult diff = mkPairDiff( + existing, + required, + a -> tuple(a.getEntityId(), a.getRatingId()), + b -> tuple(b.getEntityId(), b.getRatingId()), + (a, b) -> safeEq(a.getDescription(), b.getDescription())); + + int insertCount = summarizeResults(tx.batchInsert(diff.otherOnly()).execute()); + int rmCount = summarizeResults(tx.batchDelete(diff.waltzOnly()).execute()); + int updCount = summarizeResults(diff.differingIntersection() + .stream() + .map(t -> tx.update(ar).set(ar.DESCRIPTION, t.v2.getDescription()).where(ar.ID.eq(t.v1.getId()))) + .collect(Collectors.collectingAndThen(toSet(), tx::batch)) + .execute()); + + LOG.info(format( + "Assessment Rippler: %s -> %s, created: %d ratings, removed: %d ratings, updated: %d ratings", + from.getExternalId(), + to.getExternalId(), + insertCount, + rmCount, + updCount)); + } + + + private static String calcDescription(AssessmentDefinitionRecord from, + List sourceRefs) { + String fullDesc = format( + "Rating derived from %s assessment/s on:\n\n%s", + from.getName(), + sourceRefs + .stream() + .map(ref -> format( + "- %s", + toLink(ref))) + .collect(joining("\n"))); + + String cutoffText = "\n ..."; + int maxDescLength = ar.DESCRIPTION.getDataType().length() - cutoffText.length(); + + if (fullDesc.length() > maxDescLength) { + String forcedCutoff = fullDesc.substring(0, maxDescLength); + return forcedCutoff + .substring(0, forcedCutoff.lastIndexOf("\n")) + .concat(cutoffText); + } else { + return fullDesc; + } + } + + + private static String toLink(EntityReference ref) { + String path = toPathSegment(ref.kind()); + return format( + "[%s](%s/%d)", + ref.name().orElse("??"), + path, + ref.id()); + } + + + private static String toPathSegment(EntityKind kind) { + switch (kind) { + case ACTOR: + return "actor"; + case APPLICATION: + return "application"; + case CHANGE_INITIATIVE: + return "change-initiative"; + case DATA_TYPE: + return "data-types"; + case FLOW_CLASSIFICATION_RULE: + return "flow-classification-rule"; + case LICENCE: + return "licence"; + case LOGICAL_DATA_FLOW: + return "logical-flow"; + case MEASURABLE: + return "measurable"; + case MEASURABLE_RATING: + return "measurable-rating"; + case PHYSICAL_FLOW: + return "physical-flow"; + case PHYSICAL_SPECIFICATION: + return "physical-specification"; + default: + throw new IllegalArgumentException(format("Cannot convert kind: %s to a path segment", kind)); + } + } + +} diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java new file mode 100644 index 0000000000..dc98a91c60 --- /dev/null +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java @@ -0,0 +1,56 @@ +package org.finos.waltz.integration_test.inmem.service; + +import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; +import org.finos.waltz.model.assessment_definition.AssessmentVisibility; +import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; +import org.finos.waltz.test_common.helpers.AppHelper; +import org.finos.waltz.test_common.helpers.AssessmentHelper; +import org.finos.waltz.test_common.helpers.InvolvementHelper; +import org.finos.waltz.test_common.helpers.PermissionGroupHelper; +import org.finos.waltz.test_common.helpers.PersonHelper; +import org.finos.waltz.test_common.helpers.RatingSchemeHelper; +import org.finos.waltz.test_common.helpers.UserHelper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; + +public class AssessmentRipplerTest extends BaseInMemoryIntegrationTest { + + @Autowired + private AppHelper appHelper; + + @Autowired + private AssessmentRatingPermissionChecker assessmentRatingPermissionChecker; + + @Autowired + private PersonHelper personHelper; + + @Autowired + private AssessmentHelper assessmentHelper; + + @Autowired + private RatingSchemeHelper ratingSchemeHelper; + + @Autowired + private InvolvementHelper involvementHelper; + + @Autowired + private PermissionGroupHelper permissionHelper; + + @Autowired + private UserHelper userHelper; + + private final String stem = "ripple"; + + @Test + public void cannotRippleBetweenAssessmentsWithDifferingRatingSchemes() { + long schemeA = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_a")); + long schemeB = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_b")); + + assessmentHelper.createDefinition(schemeA, mkName(stem, "ripple assmt A"), null, AssessmentVisibility.SECONDARY, stem); + assessmentHelper.createDefinition(schemeB, mkName(stem, "ripple assmt B"), null, AssessmentVisibility.SECONDARY, stem); + } + + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/PairDiffResult.java b/waltz-model/src/main/java/org/finos/waltz/model/PairDiffResult.java new file mode 100644 index 0000000000..0794a5e963 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/PairDiffResult.java @@ -0,0 +1,71 @@ +package org.finos.waltz.model; + +import org.immutables.value.Value; +import org.jooq.lambda.tuple.Tuple2; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.finos.waltz.common.MapUtilities.indexBy; +import static org.jooq.lambda.tuple.Tuple.tuple; + +@Value.Immutable +public abstract class PairDiffResult { + + public abstract Collection> allIntersection(); + public abstract Collection otherOnly(); + public abstract Collection waltzOnly(); + public abstract Collection> differingIntersection(); + + + public static PairDiffResult mkPairDiff(Collection waltzRecords, + Collection otherRecords, + Function aKeyFn, + Function bKeyFn, + BiPredicate equalityPredicate) { + Map waltzByKey = indexBy(waltzRecords, aKeyFn); + Map othersByKey = indexBy(otherRecords, bKeyFn); + + Set otherOnly = otherRecords.stream() + .filter(f -> !waltzByKey.containsKey(bKeyFn.apply(f))) + .collect(Collectors.toSet()); + + Set waltzOnly = waltzRecords.stream() + .filter(f -> !othersByKey.containsKey(aKeyFn.apply(f))) + .collect(Collectors.toSet()); + + Set> intersect = otherRecords.stream() + .map(other -> tuple( + waltzByKey.get(bKeyFn.apply(other)), + other)) + .filter(t -> t.v1 != null) + .collect(Collectors.toSet()); + + Set> differingIntersection = intersect.stream() + .filter(t -> ! equalityPredicate.test(t.v1, t.v2)) + .collect(Collectors.toSet()); + + return ImmutablePairDiffResult + .builder() + .otherOnly(otherOnly) + .waltzOnly(waltzOnly) + .allIntersection(intersect) + .differingIntersection(differingIntersection) + .build(); + } + + @Override + public String toString() { + return String.format( + "%s - [Intersection: %s records, Other: %s records, Waltz: %s records, Differing Intersection: %s records]", + getClass().getName(), + allIntersection().size(), + otherOnly().size(), + waltzOnly().size(), + differingIntersection().size()); + } +} From 62d576bd3e658736f4b4ac9dfdebc2d225056d9d Mon Sep 17 00:00:00 2001 From: David Watkins Date: Mon, 23 Sep 2024 17:31:02 +0100 Subject: [PATCH 2/7] Assessment Rippler - TODO: admin UI #CTCTOWALTZ-3352 #7146 --- .../AssessmentRatingRippler.java | 79 +++++++------ .../inmem/BaseInMemoryIntegrationTest.java | 2 +- .../inmem/service/AssessmentRipplerTest.java | 106 ++++++++++++++---- .../waltz/model/scheduled_job/JobKey.java | 1 + .../AssessmentRatingService.java | 27 ++++- .../scheduled_job/ScheduledJobService.java | 10 +- .../test_common/helpers/AssessmentHelper.java | 11 +- 7 files changed, 179 insertions(+), 57 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java index 89a118a831..22358a9fde 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java @@ -2,6 +2,7 @@ import org.finos.waltz.common.Checks; import org.finos.waltz.common.MapUtilities; +import org.finos.waltz.data.settings.SettingsDao; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityLifecycleStatus; import org.finos.waltz.model.EntityReference; @@ -31,6 +32,8 @@ import org.jooq.lambda.tuple.Tuple2; 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.Comparator; @@ -50,6 +53,7 @@ import static org.finos.waltz.model.PairDiffResult.mkPairDiff; import static org.jooq.lambda.tuple.Tuple.tuple; +@Service public class AssessmentRatingRippler { private static final Logger LOG = LoggerFactory.getLogger(AssessmentRatingRippler.class); @@ -67,23 +71,34 @@ public class AssessmentRatingRippler { private static final MeasurableRating mr = Tables.MEASURABLE_RATING; private static final Measurable m = Tables.MEASURABLE; + private final DSLContext dsl; + private final SettingsDao settingsDao; - @SafeVarargs - public static void rippleAssessments(DSLContext tx, - String userId, - String provenance, - Tuple2... listOfSrcAndTargetDefExtIds) { - for (Tuple2 fromTo: listOfSrcAndTargetDefExtIds) { - rippleAssessments(tx, userId, provenance, fromTo.v1, fromTo.v2); - } + @Autowired + public AssessmentRatingRippler(DSLContext dsl, + SettingsDao settingsDao) { + this.dsl = dsl; + this.settingsDao = settingsDao; } - private static void rippleAssessments(DSLContext tx, - String userId, - String provenance, - String from, - String to) { + public final void rippleAssessments() { + Map requiredRipples = settingsDao.indexByPrefix("job.RIPPLE_ASSESSMENTS."); + requiredRipples.forEach((key, toExtId) -> { + + String fromExtId = key.replaceAll("^job.RIPPLE_ASSESSMENTS.", ""); + + LOG.debug("Start ripple from: {} , to: {}", fromExtId, toExtId); + rippleAssessment(dsl, "waltz", "waltz-assessment-rippler", fromExtId, toExtId); + }); + } + + + public static void rippleAssessment(DSLContext tx, + String userId, + String provenance, + String from, + String to) { Map defs = tx .selectFrom(ad) @@ -91,18 +106,18 @@ private static void rippleAssessments(DSLContext tx, .fetchMap(r -> r.get(ad.EXTERNAL_ID)); AssessmentDefinitionRecord fromDef = defs.get(from); - Checks.checkNotNull(fromDef, format("Cannot ripple assessment as definition: %s not found", from)); + Checks.checkNotNull(fromDef, "Cannot ripple assessment as definition: %s not found", from); AssessmentDefinitionRecord toDef = defs.get(to); - Checks.checkNotNull(toDef, format("Cannot ripple assessment as definition: %s not found", toDef)); - rippleAssessments(tx, userId, provenance, fromDef, toDef); + Checks.checkNotNull(toDef, "Cannot ripple assessment as definition: %s not found", toDef); + rippleAssessment(tx, userId, provenance, fromDef, toDef); } - private static void rippleAssessments(DSLContext tx, - String userId, - String provenance, - AssessmentDefinitionRecord from, - AssessmentDefinitionRecord to) { + private static void rippleAssessment(DSLContext tx, + String userId, + String provenance, + AssessmentDefinitionRecord from, + AssessmentDefinitionRecord to) { checkTrue( from.getRatingSchemeId().equals(to.getRatingSchemeId()), "Assessments must share a rating scheme when rippling (%s -> %s)", @@ -115,7 +130,7 @@ private static void rippleAssessments(DSLContext tx, if (kinds.equals(tuple(EntityKind.PHYSICAL_SPECIFICATION, EntityKind.PHYSICAL_FLOW))) { // PHYSICAL_SPEC -> PHYSICAL_FLOW - rippleAssessment( + rippleAssessments( tx, userId, provenance, @@ -131,7 +146,7 @@ private static void rippleAssessments(DSLContext tx, .and(ps.IS_REMOVED.isFalse()))); } else if (kinds.equals(tuple(EntityKind.PHYSICAL_FLOW, EntityKind.LOGICAL_DATA_FLOW))) { // PHYSICAL_FLOW -> LOGICAL - rippleAssessment( + rippleAssessments( tx, userId, provenance, @@ -163,7 +178,7 @@ private static void rippleAssessments(DSLContext tx, sourceName, DSL.value(" -> "), targetName); - rippleAssessment( + rippleAssessments( tx, userId, provenance, @@ -192,7 +207,7 @@ private static void rippleAssessments(DSLContext tx, .and(lfIsActiveCondition)))); } else if (kinds.v1 == EntityKind.MEASURABLE && kinds.v2 == EntityKind.APPLICATION) { // MEASURABLE -> APPLICTION - rippleAssessment( + rippleAssessments( tx, userId, provenance, @@ -206,7 +221,7 @@ private static void rippleAssessments(DSLContext tx, .and(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()))); } else if (kinds.v1 == EntityKind.MEASURABLE && kinds.v2 == EntityKind.MEASURABLE_RATING) { // MEASURABLE -> MEASURABLE_RATING - rippleAssessment( + rippleAssessments( tx, userId, provenance, @@ -227,12 +242,12 @@ private static void rippleAssessments(DSLContext tx, } - private static void rippleAssessment(DSLContext tx, - String userId, - String provenance, - AssessmentDefinitionRecord from, - AssessmentDefinitionRecord to, - Select> targetAndRatingProvider) { + private static void rippleAssessments(DSLContext tx, + String userId, + String provenance, + AssessmentDefinitionRecord from, + AssessmentDefinitionRecord to, + Select> targetAndRatingProvider) { Timestamp now = nowUtcTimestamp(); Set required = MapUtilities .groupAndThen( diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java index acc4d144ca..dfbea3f9bc 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/BaseInMemoryIntegrationTest.java @@ -79,7 +79,7 @@ public void baseSetup() { } - private DSLContext getDsl() { + protected DSLContext getDsl() { return ctx.getBean(DSLContext.class); } diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java index dc98a91c60..2883befd69 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java @@ -1,30 +1,34 @@ package org.finos.waltz.integration_test.inmem.service; +import org.finos.waltz.data.assessment_rating.AssessmentRatingRippler; import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.assessment_definition.AssessmentVisibility; -import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; +import org.finos.waltz.schema.tables.AssessmentRating; import org.finos.waltz.test_common.helpers.AppHelper; import org.finos.waltz.test_common.helpers.AssessmentHelper; -import org.finos.waltz.test_common.helpers.InvolvementHelper; -import org.finos.waltz.test_common.helpers.PermissionGroupHelper; -import org.finos.waltz.test_common.helpers.PersonHelper; +import org.finos.waltz.test_common.helpers.MeasurableHelper; import org.finos.waltz.test_common.helpers.RatingSchemeHelper; -import org.finos.waltz.test_common.helpers.UserHelper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.schema.Tables.ASSESSMENT_RATING; import static org.finos.waltz.test_common.helpers.NameHelper.mkName; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; public class AssessmentRipplerTest extends BaseInMemoryIntegrationTest { - @Autowired - private AppHelper appHelper; + private static final AssessmentRating ar = ASSESSMENT_RATING; @Autowired - private AssessmentRatingPermissionChecker assessmentRatingPermissionChecker; + private AppHelper appHelper; @Autowired - private PersonHelper personHelper; + private MeasurableHelper measurableHelper; @Autowired private AssessmentHelper assessmentHelper; @@ -32,14 +36,6 @@ public class AssessmentRipplerTest extends BaseInMemoryIntegrationTest { @Autowired private RatingSchemeHelper ratingSchemeHelper; - @Autowired - private InvolvementHelper involvementHelper; - - @Autowired - private PermissionGroupHelper permissionHelper; - - @Autowired - private UserHelper userHelper; private final String stem = "ripple"; @@ -48,8 +44,80 @@ public void cannotRippleBetweenAssessmentsWithDifferingRatingSchemes() { long schemeA = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_a")); long schemeB = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_b")); - assessmentHelper.createDefinition(schemeA, mkName(stem, "ripple assmt A"), null, AssessmentVisibility.SECONDARY, stem); - assessmentHelper.createDefinition(schemeB, mkName(stem, "ripple assmt B"), null, AssessmentVisibility.SECONDARY, stem); + long assmtA = assessmentHelper.createDefinition(schemeA, mkName(stem, "ripple assmt A"), null, AssessmentVisibility.SECONDARY, stem); + long assmtB = assessmentHelper.createDefinition(schemeB, mkName(stem, "ripple assmt B"), null, AssessmentVisibility.SECONDARY, stem); + String assmtA_extId = mkName(stem, "ASSMT_A"); + String assmtB_extId = mkName(stem, "ASSMT_B"); + assessmentHelper.setDefExtId(assmtA, assmtA_extId); + assessmentHelper.setDefExtId(assmtB, assmtB_extId); + + assertThrows( + IllegalArgumentException.class, + () -> AssessmentRatingRippler.rippleAssessment( + getDsl(), + "admin", + "rippler_test", + assmtA_extId, + assmtB_extId)); + + } + + + @Test + public void canRippleBetweenAssessmentsWithSameRatingSchemes() { + // setup app, measurable, and rating + EntityReference appRef = appHelper.createNewApp(mkName(stem, "ripple_app"), ouIds.root); + EntityReference unrelatedRef = appHelper.createNewApp(mkName(stem, "ripple_unrelated_app"), ouIds.root); + long categoryId = measurableHelper.createMeasurableCategory(mkName(stem, "ripple_mc")); + long measurableId = measurableHelper.createMeasurable(mkName(stem, "ripple_m"), categoryId); + + // link app to measurable + measurableHelper.createRating(appRef, measurableId); + + // create schemes, rating items and assessments + long scheme = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "good_ripple_scheme")); + Long rsiId = ratingSchemeHelper.saveRatingItem(scheme, mkName(stem, "ripple_rsi"), 0, "pink", "P"); + long assmtA = assessmentHelper.createDefinition(scheme, mkName(stem, "ripple assmt A"), null, AssessmentVisibility.SECONDARY, stem, EntityKind.MEASURABLE, mkRef(EntityKind.MEASURABLE_CATEGORY, categoryId)); + long assmtB = assessmentHelper.createDefinition(scheme, mkName(stem, "ripple assmt B"), null, AssessmentVisibility.SECONDARY, stem, EntityKind.APPLICATION, null); + String assmtA_extId = mkName(stem, "ASSMT_A"); + String assmtB_extId = mkName(stem, "ASSMT_B"); + assessmentHelper.setDefExtId(assmtA, assmtA_extId); + assessmentHelper.setDefExtId(assmtB, assmtB_extId); + + // link assessment rating to measurable + assessmentHelper.createAssessment(assmtA, mkRef(EntityKind.MEASURABLE, measurableId), rsiId); + + // ripple + AssessmentRatingRippler.rippleAssessment( + getDsl(), + "admin", + "rippler_test", + assmtA_extId, + assmtB_extId); + + // verify + assertEquals( + rsiId, + fetchAssessmentRatingItemId(appRef, assmtB), + "Rating will have rippled from measurable to application"); + + assertNull( + fetchAssessmentRatingItemId(unrelatedRef, assmtB), + "Rating won't have rippled to an unrelated app"); + } + + + // --- helpers ------------- + + private Long fetchAssessmentRatingItemId(EntityReference ref, + long assessmentDefinitionId) { + return getDsl() + .select(ar.RATING_ID) + .from(ar) + .where(ar.ENTITY_KIND.eq(ref.kind().name())) + .and(ar.ENTITY_ID.eq(ref.id())) + .and(ar.ASSESSMENT_DEFINITION_ID.eq(assessmentDefinitionId)) + .fetchOne(ar.RATING_ID); } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/scheduled_job/JobKey.java b/waltz-model/src/main/java/org/finos/waltz/model/scheduled_job/JobKey.java index 4fe47b44af..3dd77ce19c 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/scheduled_job/JobKey.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/scheduled_job/JobKey.java @@ -39,5 +39,6 @@ public enum JobKey { REPORT_GRID_RECALCULATE_APP_GROUPS_FROM_FILTERS, ALLOCATED_COSTS_POPULATOR, + RIPPLE_ASSESSMENTS, COMPLEXITY_REBUILD_MEASURABLE } 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..191bb0f0bc 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 @@ -24,22 +24,42 @@ import org.finos.waltz.data.GenericSelectorFactory; import org.finos.waltz.data.assessment_definition.AssessmentDefinitionDao; import org.finos.waltz.data.assessment_rating.AssessmentRatingDao; +import org.finos.waltz.data.assessment_rating.AssessmentRatingRippler; import org.finos.waltz.data.rating_scheme.RatingSchemeDAO; -import org.finos.waltz.model.*; +import org.finos.waltz.model.Cardinality; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.IdSelectionOptions; +import org.finos.waltz.model.NameProvider; +import org.finos.waltz.model.Operation; +import org.finos.waltz.model.Severity; +import org.finos.waltz.model.UserTimestamp; import org.finos.waltz.model.application.AssessmentsView; import org.finos.waltz.model.application.ImmutableAssessmentsView; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; -import org.finos.waltz.model.assessment_rating.*; +import org.finos.waltz.model.assessment_rating.AssessmentDefinitionRatingOperations; +import org.finos.waltz.model.assessment_rating.AssessmentRating; +import org.finos.waltz.model.assessment_rating.AssessmentRatingSummaryCounts; +import org.finos.waltz.model.assessment_rating.BulkAssessmentRatingCommand; +import org.finos.waltz.model.assessment_rating.ImmutableAssessmentRating; +import org.finos.waltz.model.assessment_rating.RemoveAssessmentRatingCommand; +import org.finos.waltz.model.assessment_rating.SaveAssessmentRatingCommand; +import org.finos.waltz.model.assessment_rating.UpdateRatingCommand; import org.finos.waltz.model.changelog.ChangeLog; import org.finos.waltz.model.changelog.ImmutableChangeLog; import org.finos.waltz.model.rating.RatingScheme; import org.finos.waltz.model.rating.RatingSchemeItem; import org.finos.waltz.service.changelog.ChangeLogService; import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; +import org.jooq.lambda.tuple.Tuple2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import static java.lang.String.format; @@ -477,4 +497,5 @@ public AssessmentsView getPrimaryAssessmentsViewForKindAndSelector(EntityKind en .ratingSchemeItems(assessmentRatingSchemeItems) .build(); } + } \ No newline at end of file diff --git a/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java b/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java index 6caa2f571a..1ee8c09bc1 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java @@ -20,6 +20,7 @@ import org.finos.waltz.common.ExcludeFromIntegrationTesting; +import org.finos.waltz.data.assessment_rating.AssessmentRatingRippler; import org.finos.waltz.data.scheduled_job.ScheduledJobDao; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.scheduled_job.JobKey; @@ -67,6 +68,7 @@ public class ScheduledJobService { private final CostService costService; private final SurveyInstanceActionQueueService surveyInstanceActionQueueService; private final ComplexityService complexityService; + private final AssessmentRatingRippler assessmentRatingRippler; @Autowired @@ -81,7 +83,8 @@ public ScheduledJobService(AttestationRunService attestationRunService, ReportGridFilterViewService reportGridFilterViewService, ScheduledJobDao scheduledJobDao, SurveyInstanceActionQueueService surveyInstanceActionQueueService, - SurveyInstanceService surveyInstanceService) { + SurveyInstanceService surveyInstanceService, + AssessmentRatingRippler assessmentRatingRippler) { checkNotNull(attestationRunService, "attestationRunService cannot be null"); @@ -95,6 +98,7 @@ public ScheduledJobService(AttestationRunService attestationRunService, checkNotNull(scheduledJobDao, "scheduledJobDao cannot be null"); checkNotNull(surveyInstanceActionQueueService, "surveyInstanceActionQueueService cannot be null"); checkNotNull(surveyInstanceService, "surveyInstanceService cannot be null"); + checkNotNull(assessmentRatingRippler, "assessmentRatingRippler cannot be null"); this.attestationRunService = attestationRunService; this.complexityService = complexityService; @@ -108,6 +112,7 @@ public ScheduledJobService(AttestationRunService attestationRunService, this.scheduledJobDao = scheduledJobDao; this.surveyInstanceActionQueueService = surveyInstanceActionQueueService; this.surveyInstanceService = surveyInstanceService; + this.assessmentRatingRippler = assessmentRatingRippler; } @@ -164,6 +169,9 @@ public void run() { runIfNeeded(JobKey.COMPLEXITY_REBUILD_MEASURABLE, (jk) -> complexityService.populateMeasurableComplexities()); + runIfNeeded(JobKey.RIPPLE_ASSESSMENTS, + (jk) -> assessmentRatingRippler.rippleAssessments()); + surveyInstanceActionQueueService.performActions(); } diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AssessmentHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AssessmentHelper.java index 9cd8858773..f5a41435de 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AssessmentHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/AssessmentHelper.java @@ -87,7 +87,6 @@ public void createAssessment(Long defId, EntityReference ref, Long ratingId, Str public void createAssessment(Long defId, EntityReference ref, Long ratingId) { - AssessmentRatingRecord record = dsl.newRecord(ASSESSMENT_RATING); record.setAssessmentDefinitionId(defId); record.setEntityId(ref.id()); @@ -106,6 +105,7 @@ public void createAssessment(Long defId, EntityReference ref, Long ratingId) { .execute(); } + public void updateDefinitionReadOnly(long defnId) { dsl .update(ASSESSMENT_DEFINITION) @@ -114,6 +114,7 @@ public void updateDefinitionReadOnly(long defnId) { .execute(); } + public void updateRatingReadOnly(EntityReference ref, long defnId) { dsl .update(ASSESSMENT_RATING) @@ -124,4 +125,12 @@ public void updateRatingReadOnly(EntityReference ref, long defnId) { .execute(); } + + public void setDefExtId(long assessmentDefinitionId, + String externalId) { + dsl.update(ASSESSMENT_DEFINITION) + .set(ASSESSMENT_DEFINITION.EXTERNAL_ID, externalId) + .where(ASSESSMENT_DEFINITION.ID.eq(assessmentDefinitionId)) + .execute(); + } } From 1a1b2952fddec1ebb7fe4464aaa998922f7c5838 Mon Sep 17 00:00:00 2001 From: David Watkins Date: Tue, 24 Sep 2024 15:18:59 +0100 Subject: [PATCH 3/7] Bulk Measurable Rating import - configuration via the settings table - integration tests TODO: - [ ] UI - [ ] Documentation - [ ] Test with realistic data - [ ] Add more paths (from kind, to kind) ? #CTCTOWALTZ-3335 #7145 --- .../AssessmentRatingRippler.java | 55 +++++++++++++++-- .../AssessmentRatingRipplerTest.java | 27 ++++++++ .../inmem/service/AssessmentRipplerTest.java | 61 ++++++++++++++++++- .../AssessmentRipplerJobConfiguration.java | 18 ++++++ .../AssessmentRipplerJobStep.java | 19 ++++++ 5 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 waltz-data/src/test/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRipplerTest.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobConfiguration.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobStep.java diff --git a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java index 22358a9fde..c30952b936 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java @@ -1,5 +1,7 @@ package org.finos.waltz.data.assessment_rating; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.finos.waltz.common.Checks; import org.finos.waltz.common.MapUtilities; import org.finos.waltz.data.settings.SettingsDao; @@ -7,6 +9,9 @@ import org.finos.waltz.model.EntityLifecycleStatus; import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.PairDiffResult; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobConfiguration; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobStep; +import org.finos.waltz.model.assessment_definition.ImmutableAssessmentRipplerJobConfiguration; import org.finos.waltz.schema.Tables; import org.finos.waltz.schema.tables.Actor; import org.finos.waltz.schema.tables.Application; @@ -41,12 +46,15 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.lang.String.format; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toSet; import static org.finos.waltz.common.Checks.checkTrue; import static org.finos.waltz.common.DateTimeUtilities.nowUtcTimestamp; +import static org.finos.waltz.common.JacksonUtilities.getJsonMapper; +import static org.finos.waltz.common.ListUtilities.asList; import static org.finos.waltz.common.StringUtilities.safeEq; import static org.finos.waltz.data.JooqUtilities.summarizeResults; import static org.finos.waltz.model.EntityReference.mkRef; @@ -82,14 +90,49 @@ public AssessmentRatingRippler(DSLContext dsl, } - public final void rippleAssessments() { - Map requiredRipples = settingsDao.indexByPrefix("job.RIPPLE_ASSESSMENTS."); - requiredRipples.forEach((key, toExtId) -> { + public static AssessmentRipplerJobConfiguration parseConfig(String name, + String value) throws JsonProcessingException { + ObjectMapper jsonMapper = getJsonMapper(); + AssessmentRipplerJobStep[] steps = jsonMapper.readValue(value, AssessmentRipplerJobStep[].class); + + return ImmutableAssessmentRipplerJobConfiguration + .builder() + .name(name) + .steps(asList(steps)) + .build(); + } - String fromExtId = key.replaceAll("^job.RIPPLE_ASSESSMENTS.", ""); - LOG.debug("Start ripple from: {} , to: {}", fromExtId, toExtId); - rippleAssessment(dsl, "waltz", "waltz-assessment-rippler", fromExtId, toExtId); + public final void rippleAssessments() { + Map configEntries = settingsDao + .indexByPrefix("job.RIPPLE_ASSESSMENTS."); + + dsl.transaction(ctx -> { + DSLContext tx = ctx.dsl(); + configEntries + .entrySet() + .stream() + .flatMap(kv -> { + String key = kv.getKey(); + String value = kv.getValue(); + + String rippleName = key.replaceAll("^job.RIPPLE_ASSESSMENTS.", ""); + LOG.debug("Parsing config ripple : {} , json: {}", rippleName, value); + + try { + AssessmentRipplerJobConfiguration config = parseConfig(rippleName, value); + return config.steps().stream(); + } catch (JsonProcessingException e) { + LOG.error("Could not process assessment rippler job: " + rippleName, e); + return Stream.empty(); + } + }) + .forEach(step -> rippleAssessment( + tx, + "waltz", + "waltz-assessment-rippler", + step.fromDef(), + step.toDef())); }); } diff --git a/waltz-data/src/test/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRipplerTest.java b/waltz-data/src/test/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRipplerTest.java new file mode 100644 index 0000000000..6e41c64463 --- /dev/null +++ b/waltz-data/src/test/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRipplerTest.java @@ -0,0 +1,27 @@ +package org.finos.waltz.data.assessment_rating; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobConfiguration; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobStep; +import org.junit.jupiter.api.Test; + +import static org.finos.waltz.common.CollectionUtilities.first; +import static org.junit.jupiter.api.Assertions.*; + +class AssessmentRatingRipplerTest { + + + @Test + public void testSettingsParse() throws JsonProcessingException { + String value = "[ { \"from\" : \"FROM_DEF\", \"to\" : \"TO_DEF\" } ]"; + AssessmentRipplerJobConfiguration config = AssessmentRatingRippler.parseConfig("demoRippler", value); + + assertEquals("demoRippler", config.name()); + assertEquals(1, config.steps().size()); + + AssessmentRipplerJobStep step = first(config.steps()); + assertEquals("FROM_DEF", step.fromDef()); + assertEquals("TO_DEF", step.toDef()); + } + +} \ No newline at end of file diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java index 2883befd69..99d599d829 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/AssessmentRipplerTest.java @@ -1,10 +1,16 @@ package org.finos.waltz.integration_test.inmem.service; +import com.fasterxml.jackson.core.JsonProcessingException; import org.finos.waltz.data.assessment_rating.AssessmentRatingRippler; +import org.finos.waltz.data.settings.SettingsDao; import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobStep; import org.finos.waltz.model.assessment_definition.AssessmentVisibility; +import org.finos.waltz.model.assessment_definition.ImmutableAssessmentRipplerJobStep; +import org.finos.waltz.model.settings.ImmutableSetting; +import org.finos.waltz.model.settings.Setting; import org.finos.waltz.schema.tables.AssessmentRating; import org.finos.waltz.test_common.helpers.AppHelper; import org.finos.waltz.test_common.helpers.AssessmentHelper; @@ -13,6 +19,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import static org.finos.waltz.common.JacksonUtilities.getJsonMapper; +import static org.finos.waltz.common.ListUtilities.asList; import static org.finos.waltz.model.EntityReference.mkRef; import static org.finos.waltz.schema.Tables.ASSESSMENT_RATING; import static org.finos.waltz.test_common.helpers.NameHelper.mkName; @@ -36,9 +44,15 @@ public class AssessmentRipplerTest extends BaseInMemoryIntegrationTest { @Autowired private RatingSchemeHelper ratingSchemeHelper; + @Autowired + private SettingsDao settingsDao; + + @Autowired + private AssessmentRatingRippler assessmentRatingRippler; private final String stem = "ripple"; + @Test public void cannotRippleBetweenAssessmentsWithDifferingRatingSchemes() { long schemeA = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_a")); @@ -59,12 +73,57 @@ public void cannotRippleBetweenAssessmentsWithDifferingRatingSchemes() { "rippler_test", assmtA_extId, assmtB_extId)); + } + + + @Test + public void canRippleBetweenAssessmentsWithSameRatingSchemes() throws JsonProcessingException { + // setup app, measurable, and rating + EntityReference appRef = appHelper.createNewApp(mkName(stem, "ripple_app"), ouIds.root); + long categoryId = measurableHelper.createMeasurableCategory(mkName(stem, "ripple_mc")); + long measurableId = measurableHelper.createMeasurable(mkName(stem, "ripple_m"), categoryId); + + // link app to measurable + measurableHelper.createRating(appRef, measurableId); + + // create schemes, rating items and assessments + long scheme = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "good_ripple_scheme")); + Long rsiId = ratingSchemeHelper.saveRatingItem(scheme, mkName(stem, "ripple_rsi"), 0, "pink", "P"); + long assmtA = assessmentHelper.createDefinition(scheme, mkName(stem, "ripple assmt A"), null, AssessmentVisibility.SECONDARY, stem, EntityKind.MEASURABLE, mkRef(EntityKind.MEASURABLE_CATEGORY, categoryId)); + long assmtB = assessmentHelper.createDefinition(scheme, mkName(stem, "ripple assmt B"), null, AssessmentVisibility.SECONDARY, stem, EntityKind.APPLICATION, null); + String assmtA_extId = mkName(stem, "ASSMT_A"); + String assmtB_extId = mkName(stem, "ASSMT_B"); + assessmentHelper.setDefExtId(assmtA, assmtA_extId); + assessmentHelper.setDefExtId(assmtB, assmtB_extId); + + // link assessment rating to measurable + assessmentHelper.createAssessment(assmtA, mkRef(EntityKind.MEASURABLE, measurableId), rsiId); + + AssessmentRipplerJobStep rippleStep = ImmutableAssessmentRipplerJobStep + .builder() + .fromDef(assmtA_extId) + .toDef(assmtB_extId) + .build(); + Setting rippleSetting = ImmutableSetting + .builder() + .name("job.RIPPLE_ASSESSMENTS."+mkName(stem, "rippleConfig")) + .value(getJsonMapper().writeValueAsString(asList(rippleStep))) + .build(); + settingsDao.create(rippleSetting); + // ripple + assessmentRatingRippler.rippleAssessments(); + + // verify + assertEquals( + rsiId, + fetchAssessmentRatingItemId(appRef, assmtB), + "Rating will have rippled from measurable to application"); } @Test - public void canRippleBetweenAssessmentsWithSameRatingSchemes() { + public void canRippleUsingTheSettingsTableDefinitions() { // setup app, measurable, and rating EntityReference appRef = appHelper.createNewApp(mkName(stem, "ripple_app"), ouIds.root); EntityReference unrelatedRef = appHelper.createNewApp(mkName(stem, "ripple_unrelated_app"), ouIds.root); diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobConfiguration.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobConfiguration.java new file mode 100644 index 0000000000..7d9d177e4d --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobConfiguration.java @@ -0,0 +1,18 @@ +package org.finos.waltz.model.assessment_definition; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.value.Value; + +import java.util.List; + +@Value.Immutable +@JsonDeserialize(as = ImmutableAssessmentRipplerJobConfiguration.class) +@JsonSerialize(as = ImmutableAssessmentRipplerJobConfiguration.class) +public interface AssessmentRipplerJobConfiguration { + + String name(); + + List steps(); + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobStep.java b/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobStep.java new file mode 100644 index 0000000000..dd85923198 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/assessment_definition/AssessmentRipplerJobStep.java @@ -0,0 +1,19 @@ +package org.finos.waltz.model.assessment_definition; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.value.Value; + +@Value.Immutable +@JsonDeserialize(as = ImmutableAssessmentRipplerJobStep.class) +@JsonSerialize(as = ImmutableAssessmentRipplerJobStep.class) +public interface AssessmentRipplerJobStep { + + @JsonAlias({"from", "from_def"}) + String fromDef(); + + @JsonAlias({"to", "to_def"}) + String toDef(); + +} From be339e68921272975aa98676dbeb85f6fb7d7252 Mon Sep 17 00:00:00 2001 From: David Watkins Date: Wed, 25 Sep 2024 16:36:43 +0100 Subject: [PATCH 4/7] Bulk Measurable Rating import - UI shows how to configure/current config - rippler supports more entity kind combos - LF to EUDA - CI to App - mentioned on the recalc admin page link description - seperated the config calc from the ripple action - so we can re-use to show the config - Added support for more paths (from kind > to kind) - CI -> APP - LF -> EUDA #CTCTOWALTZ-3335 #7145 --- .../AssessmentRatingRippler.java | 110 ++++++++++---- .../services/assessment-rating-store.js | 26 +++- waltz-ng/client/system/recalculate-view.html | 138 +++++++++++++++++- waltz-ng/client/system/recalculate-view.js | 40 ++++- waltz-ng/client/system/system-admin-list.js | 2 +- .../AssessmentRatingService.java | 15 +- .../api/AssessmentRatingEndpoint.java | 17 +++ 7 files changed, 305 insertions(+), 43 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java index c30952b936..e51e201740 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/assessment_rating/AssessmentRatingRippler.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.finos.waltz.common.Checks; import org.finos.waltz.common.MapUtilities; +import org.finos.waltz.common.SetUtilities; import org.finos.waltz.data.settings.SettingsDao; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityLifecycleStatus; @@ -17,6 +18,9 @@ import org.finos.waltz.schema.tables.Application; import org.finos.waltz.schema.tables.AssessmentDefinition; import org.finos.waltz.schema.tables.AssessmentRating; +import org.finos.waltz.schema.tables.ChangeInitiative; +import org.finos.waltz.schema.tables.EndUserApplication; +import org.finos.waltz.schema.tables.EntityRelationship; import org.finos.waltz.schema.tables.LogicalFlow; import org.finos.waltz.schema.tables.Measurable; import org.finos.waltz.schema.tables.MeasurableRating; @@ -44,9 +48,9 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.lang.String.format; import static java.util.stream.Collectors.joining; @@ -78,6 +82,10 @@ public class AssessmentRatingRippler { private static final RatingScheme rs = Tables.RATING_SCHEME; private static final MeasurableRating mr = Tables.MEASURABLE_RATING; private static final Measurable m = Tables.MEASURABLE; + private static final EntityRelationship er = Tables.ENTITY_RELATIONSHIP; + private static final ChangeInitiative ci = Tables.CHANGE_INITIATIVE; + private static final EndUserApplication euda = Tables.END_USER_APPLICATION; + private static final Set flowNodeEntities = SetUtilities.asSet(EntityKind.APPLICATION, EntityKind.ACTOR, EntityKind.END_USER_APPLICATION); private final DSLContext dsl; private final SettingsDao settingsDao; @@ -103,36 +111,29 @@ public static AssessmentRipplerJobConfiguration parseConfig(String name, } - public final void rippleAssessments() { - Map configEntries = settingsDao - .indexByPrefix("job.RIPPLE_ASSESSMENTS."); + /** + * Ripple all assessments configured in the settings table + * + * @return the number of steps taken, where a step is a source assessment def and a target assessment def + */ + public final Long rippleAssessments() { + Set rippleConfig = findRippleConfig(); - dsl.transaction(ctx -> { + return dsl.transactionResult(ctx -> { DSLContext tx = ctx.dsl(); - configEntries - .entrySet() + return rippleConfig .stream() - .flatMap(kv -> { - String key = kv.getKey(); - String value = kv.getValue(); - - String rippleName = key.replaceAll("^job.RIPPLE_ASSESSMENTS.", ""); - LOG.debug("Parsing config ripple : {} , json: {}", rippleName, value); - - try { - AssessmentRipplerJobConfiguration config = parseConfig(rippleName, value); - return config.steps().stream(); - } catch (JsonProcessingException e) { - LOG.error("Could not process assessment rippler job: " + rippleName, e); - return Stream.empty(); - } + .flatMap(config -> config.steps().stream()) + .map(step -> { + rippleAssessment( + tx, + "waltz", + "waltz-assessment-rippler", + step.fromDef(), + step.toDef()); + return 1; }) - .forEach(step -> rippleAssessment( - tx, - "waltz", - "waltz-assessment-rippler", - step.fromDef(), - step.toDef())); + .count(); }); } @@ -202,20 +203,24 @@ private static void rippleAssessment(DSLContext tx, .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) .and(pf.IS_REMOVED.isFalse()) .and(pf.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())))); - } else if (kinds.v1 == EntityKind.LOGICAL_DATA_FLOW && (kinds.v2 == EntityKind.APPLICATION || kinds.v2 == EntityKind.ACTOR)) { - // LOGICAL -> APP | ACTOR + } else if (kinds.v1 == EntityKind.LOGICAL_DATA_FLOW && (flowNodeEntities.contains(kinds.v2))) { + // LOGICAL -> APP | ACTOR | END_USER_APP Condition lfIsActiveCondition = lf.IS_REMOVED.isFalse() .and(lf.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())); Actor sourceActor = act.as("source_actor"); Actor targetActor = act.as("target_actor"); + EndUserApplication sourceEuda = euda.as("source_euda"); + EndUserApplication targetEuda = euda.as("target_euda"); Application sourceApp = app.as("source_app"); Application targetApp = app.as("target_app"); Condition sourceActorJoinCondition = lf.SOURCE_ENTITY_KIND.eq(EntityKind.ACTOR.name()).and(sourceActor.ID.eq(lf.SOURCE_ENTITY_ID)); Condition targetActorJoinCondition = lf.TARGET_ENTITY_KIND.eq(EntityKind.ACTOR.name()).and(targetActor.ID.eq(lf.TARGET_ENTITY_ID)); Condition sourceAppJoinCondition = lf.SOURCE_ENTITY_KIND.eq(EntityKind.APPLICATION.name()).and(sourceApp.ID.eq(lf.SOURCE_ENTITY_ID)); Condition targetAppJoinCondition = lf.TARGET_ENTITY_KIND.eq(EntityKind.APPLICATION.name()).and(targetApp.ID.eq(lf.TARGET_ENTITY_ID)); - Field sourceName = DSL.coalesce(sourceApp.NAME, sourceActor.NAME, DSL.value("??")); - Field targetName = DSL.coalesce(targetApp.NAME, targetActor.NAME, DSL.value("??")); + Condition sourceEudaJoinCondition = lf.SOURCE_ENTITY_KIND.eq(EntityKind.END_USER_APPLICATION.name()).and(sourceApp.ID.eq(lf.SOURCE_ENTITY_ID)); + Condition targetEudaJoinCondition = lf.TARGET_ENTITY_KIND.eq(EntityKind.END_USER_APPLICATION.name()).and(targetApp.ID.eq(lf.TARGET_ENTITY_ID)); + Field sourceName = DSL.coalesce(sourceApp.NAME, sourceActor.NAME, sourceEuda.NAME, DSL.value("??")); + Field targetName = DSL.coalesce(targetApp.NAME, targetActor.NAME, targetEuda.NAME, DSL.value("??")); Field flowDesc = DSL.concat( DSL.value("Flow: "), sourceName, @@ -234,6 +239,8 @@ private static void rippleAssessment(DSLContext tx, .leftJoin(targetApp).on(targetAppJoinCondition) .leftJoin(sourceActor).on(sourceActorJoinCondition) .leftJoin(targetActor).on(targetActorJoinCondition) + .leftJoin(sourceEuda).on(sourceEudaJoinCondition) + .leftJoin(targetEuda).on(targetEudaJoinCondition) .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) .and(lf.SOURCE_ENTITY_KIND.eq(kinds.v2.name())) .and(lfIsActiveCondition)) @@ -245,11 +252,13 @@ private static void rippleAssessment(DSLContext tx, .leftJoin(targetApp).on(targetAppJoinCondition) .leftJoin(sourceActor).on(sourceActorJoinCondition) .leftJoin(targetActor).on(targetActorJoinCondition) + .leftJoin(sourceEuda).on(sourceEudaJoinCondition) + .leftJoin(targetEuda).on(targetEudaJoinCondition) .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId()) .and(lf.TARGET_ENTITY_KIND.eq(kinds.v2.name())) .and(lfIsActiveCondition)))); } else if (kinds.v1 == EntityKind.MEASURABLE && kinds.v2 == EntityKind.APPLICATION) { - // MEASURABLE -> APPLICTION + // MEASURABLE -> APPLICATION rippleAssessments( tx, userId, @@ -276,6 +285,20 @@ private static void rippleAssessment(DSLContext tx, .innerJoin(m).on(m.ID.eq(mr.MEASURABLE_ID)) .where(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId())) .and(mr.ENTITY_KIND.eq(EntityKind.APPLICATION.name()))); + } else if (kinds.v1 == EntityKind.CHANGE_INITIATIVE && kinds.v2 == EntityKind.APPLICATION) { + // CHANGE_INITIATIVE -> APPLICATION + rippleAssessments( + tx, + userId, + provenance, + from, + to, + tx.select(er.ID_B, ar.RATING_ID, ci.ID, ci.NAME) + .from(er) + .innerJoin(ci).on(ci.ID.eq(er.ID_A).and(er.KIND_A.eq(EntityKind.CHANGE_INITIATIVE.name()))) + .innerJoin(ar).on(ar.ENTITY_ID.eq(ci.ID) + .and(ar.ENTITY_KIND.eq(EntityKind.CHANGE_INITIATIVE.name())) + .and(ar.ASSESSMENT_DEFINITION_ID.eq(from.getId())))); } else { throw new UnsupportedOperationException(format( "Cannot ripple assessment from kind: %s to kind: %s", @@ -417,4 +440,27 @@ private static String toPathSegment(EntityKind kind) { } } + + public Set findRippleConfig() { + Map configEntries = settingsDao + .indexByPrefix("job.RIPPLE_ASSESSMENTS."); + + return configEntries + .entrySet() + .stream() + .map(kv -> { + String key = kv.getKey(); + String value = kv.getValue(); + String rippleName = key.replaceAll("^job.RIPPLE_ASSESSMENTS.", ""); + LOG.debug("Parsing config ripple : {} , json: {}", rippleName, value); + + try { + return parseConfig(rippleName, value); + } catch (JsonProcessingException e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(toSet()); + } } diff --git a/waltz-ng/client/assessments/services/assessment-rating-store.js b/waltz-ng/client/assessments/services/assessment-rating-store.js index adb967c2df..859bdc9b3a 100644 --- a/waltz-ng/client/assessments/services/assessment-rating-store.js +++ b/waltz-ng/client/assessments/services/assessment-rating-store.js @@ -95,6 +95,18 @@ export function store($http, BaseApiUrl) { .then(d => d.data); }; + const ripple = () => { + return $http + .post(`${BASE}/ripple/all`) + .then(d => d.data); + }; + + const findRippleConfig = () => { + return $http + .get(`${BASE}/ripple/config`) + .then(d => d.data); + }; + return { getRatingPermissions, findForEntityReference, @@ -106,7 +118,9 @@ export function store($http, BaseApiUrl) { unlock, bulkStore, bulkRemove, - remove + remove, + ripple, + findRippleConfig }; } @@ -175,6 +189,16 @@ export const AssessmentRatingStore_API = { serviceName, serviceFnName: "remove", description: "remove a rating" + }, + ripple: { + serviceName, + serviceFnName: "ripple", + description: "ripple assessments based on the settings table config" + }, + findRippleConfig: { + serviceName, + serviceFnName: "findRippleConfig", + description: "ripple assessments based on the settings table config" } }; diff --git a/waltz-ng/client/system/recalculate-view.html b/waltz-ng/client/system/recalculate-view.html index 2a6c2df969..52eb6b3f01 100644 --- a/waltz-ng/client/system/recalculate-view.html +++ b/waltz-ng/client/system/recalculate-view.html @@ -42,7 +42,7 @@

-

Rated Flows

+

Rated Flows

Logical flows are decorated with data types which may in turn be rated against Authoritative Sources. Use the link @@ -50,14 +50,15 @@

Rated Flows

+

-

Data Type Usages

+

Data Type Usages

Applications track what data types they use and how they get used. Use the link below to recalculate all @@ -65,11 +66,140 @@

Data Type Usages

+ +
+
+

Assessment Rippler

+

+ The assessment rippler can copy assessment ratings between different definitions + providing they share the same rating scheme. For example, you may have a critical process + assessment on a measurable and want an equivalent criticality flag to be reflected on + all applications which are aligned to that measurable. +

+
+ Configuration + +

Active Configuration

+
+ +

+ Each row in the table below represents a step in the flow, rippling assessments + between a source (From) and a target (To). + If there are multiple steps they are applied in the sequence shown below. +

+ + + + + + + + + + + + + + + + + + + +
From AssessmentFrom Entity KindTo AssessmentTo Entity Kind
+ + () + + + + » + + + () + + +
+
+ +
+ +

Setup

+

+ To configure the assessment rippler you need to create an entry in the + settings + table for each 'ripple-flow'. For example: +

+ + + + + + + + + + + + + + + + + + + + +
NameDescriptionExample
Setting key + The key must be of the form: + job.RIPPLE_ASSESSMENTS.??? + , the ??? + should be a unique name to identify the ripple flow + + job.RIPPLE_ASSESSMENTS.nice_name +
Setting value + A list of json objects, each object represents the 'from' and 'to' + external id's of the assessments to be rippled + + [{"from": "def1_ext_id", "to": "def2_ext_id"}] +
+

+ To trigger this action (i.e. from a scheduled job), you can add/udpate an + entry in the settings table: + +

+ + + + + + + + + + + + + +
Setting KeySetting Value
+ RIPPLE_ASSESSMENTS + + RUNNABLE +
+ +
+ + +
+ diff --git a/waltz-ng/client/system/recalculate-view.js b/waltz-ng/client/system/recalculate-view.js index cdf74774d3..b2ad4970ae 100644 --- a/waltz-ng/client/system/recalculate-view.js +++ b/waltz-ng/client/system/recalculate-view.js @@ -19,10 +19,37 @@ import template from "./recalculate-view.html"; import {CORE_API} from "../common/services/core-api-utils"; import toasts from "../svelte-stores/toast-store"; +import {initialiseData} from "../common"; +const initialState = { + assessmentRippleConfig: [] +}; + + +function controller($q, serviceBroker) { + const vm = initialiseData(this, initialState); + + vm.$onInit = () => { + const configPromise = serviceBroker + .loadViewData(CORE_API.AssessmentRatingStore.findRippleConfig); + + const defsPromise = serviceBroker + .loadViewData(CORE_API.AssessmentDefinitionStore.findAll); -function controller(serviceBroker) { - const vm = this; + $q.all([configPromise, defsPromise]) + .then(([cr, dr]) => { + const defsByExtId = _.keyBy(dr.data, d => d.externalId); + vm.assessmentRippleConfig = _ + .chain(cr.data) + .map(d => { + const steps = _.map(d.steps, s => ({from: defsByExtId[s.fromDef], to: defsByExtId[s.toDef]})); + return Object.assign({}, d, {steps}); + }) + .value(); + + console.log({assessmentRippleConfig: vm.assessmentRippleConfig}) + }); + }; vm.recalcFlowRatings = () => { toasts.info("Flow Ratings recalculation requested"); @@ -38,10 +65,17 @@ function controller(serviceBroker) { .then(() => toasts.success("Data Type Usage recalculated")); }; + vm.rippleAssessments = () => { + toasts.info("Assessment Ripple requested"); + serviceBroker + .execute(CORE_API.AssessmentRatingStore.ripple) + .then(r => toasts.success(`Assessment Ripple finished. Completed ${r.data} step/s`)); + }; } controller.$inject = [ + "$q", "ServiceBroker" ]; @@ -49,7 +83,7 @@ controller.$inject = [ const page = { template, controller, - controllerAs: "ctrl" + controllerAs: "$ctrl" }; diff --git a/waltz-ng/client/system/system-admin-list.js b/waltz-ng/client/system/system-admin-list.js index ad726839a1..8ffd4dd710 100644 --- a/waltz-ng/client/system/system-admin-list.js +++ b/waltz-ng/client/system/system-admin-list.js @@ -191,7 +191,7 @@ const maintenanceOptions= [ }, { name: "Recalculate Derived Fields", role: "ADMIN", - description: "Recompute: Data Type Usages, Flow Authoritative Source Ratings", + description: "Recompute: Data Type Usages, Flow Authoritative Source Ratings, Ripple Assessments", state: "main.system.recalculate", icon: "calculator" }, { 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 191bb0f0bc..6a48b1bd4c 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 @@ -37,6 +37,7 @@ import org.finos.waltz.model.application.AssessmentsView; import org.finos.waltz.model.application.ImmutableAssessmentsView; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobConfiguration; import org.finos.waltz.model.assessment_rating.AssessmentDefinitionRatingOperations; import org.finos.waltz.model.assessment_rating.AssessmentRating; import org.finos.waltz.model.assessment_rating.AssessmentRatingSummaryCounts; @@ -51,7 +52,6 @@ import org.finos.waltz.model.rating.RatingSchemeItem; import org.finos.waltz.service.changelog.ChangeLogService; import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; -import org.jooq.lambda.tuple.Tuple2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -79,6 +79,7 @@ public class AssessmentRatingService { private final ChangeLogService changeLogService; private final AssessmentRatingPermissionChecker assessmentRatingPermissionChecker; private final GenericSelectorFactory genericSelectorFactory = new GenericSelectorFactory(); + private final AssessmentRatingRippler rippler; @Autowired @@ -87,19 +88,22 @@ public AssessmentRatingService( AssessmentDefinitionDao assessmentDefinitionDao, RatingSchemeDAO ratingSchemeDAO, ChangeLogService changeLogService, - AssessmentRatingPermissionChecker assessmentRatingPermissionChecker) { + AssessmentRatingPermissionChecker assessmentRatingPermissionChecker, + AssessmentRatingRippler rippler) { checkNotNull(assessmentRatingDao, "assessmentRatingDao cannot be null"); checkNotNull(assessmentDefinitionDao, "assessmentDefinitionDao cannot be null"); checkNotNull(ratingSchemeDAO, "ratingSchemeDao cannot be null"); checkNotNull(assessmentRatingPermissionChecker, "ratingPermissionChecker cannot be null"); checkNotNull(changeLogService, "changeLogService cannot be null"); + checkNotNull(rippler, "rippler cannot be null"); this.assessmentRatingPermissionChecker = assessmentRatingPermissionChecker; this.assessmentRatingDao = assessmentRatingDao; this.ratingSchemeDAO = ratingSchemeDAO; this.assessmentDefinitionDao = assessmentDefinitionDao; this.changeLogService = changeLogService; + this.rippler = rippler; } @@ -498,4 +502,11 @@ public AssessmentsView getPrimaryAssessmentsViewForKindAndSelector(EntityKind en .build(); } + public Long rippleAll() { + return rippler.rippleAssessments(); + } + + public Set findRippleConfig() { + return rippler.findRippleConfig(); + } } \ No newline at end of file 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..e69f1c1ca8 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 @@ -23,7 +23,9 @@ import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.UserTimestamp; import org.finos.waltz.model.assessment_definition.AssessmentDefinition; +import org.finos.waltz.model.assessment_definition.AssessmentRipplerJobConfiguration; import org.finos.waltz.model.assessment_rating.*; +import org.finos.waltz.model.user.SystemRole; import org.finos.waltz.service.assessment_definition.AssessmentDefinitionService; import org.finos.waltz.service.assessment_rating.AssessmentRatingService; import org.finos.waltz.service.permission.permission_checker.AssessmentRatingPermissionChecker; @@ -92,6 +94,8 @@ public void register() { String findRatingPermissionsPath = mkPath(BASE_URL, "entity", ":kind", ":id", ":assessmentDefinitionId", "permissions"); String bulkUpdatePath = mkPath(BASE_URL, "bulk-update", ":assessmentDefinitionId"); String bulkRemovePath = mkPath(BASE_URL, "bulk-remove", ":assessmentDefinitionId"); + String rippleAllPath = mkPath(BASE_URL, "ripple", "all"); + String rippleConfigPath = mkPath(BASE_URL, "ripple", "config"); getForList(findForEntityPath, this::findForEntityRoute); getForList(findByEntityKindPath, this::findByEntityKindRoute); @@ -108,6 +112,8 @@ public void register() { putForDatum(lockPath, this::lockRoute); putForDatum(unlockPath, this::unlockRoute); deleteForDatum(removePath, this::removeRoute); + postForDatum(rippleAllPath, this::rippleRoute); + getForList(rippleConfigPath, this::rippleConfigRoute); } @@ -215,6 +221,17 @@ private boolean bulkRemoveRoute(Request request, Response z) throws IOException } + private Long rippleRoute(Request request, Response z) { + requireRole(userRoleService, request, SystemRole.ADMIN); + return assessmentRatingService.rippleAll(); + } + + + private Set rippleConfigRoute(Request request, Response z) { + return assessmentRatingService.findRippleConfig(); + } + + private boolean removeRoute(Request request, Response z) throws InsufficientPrivelegeException { String username = getUsername(request); UserTimestamp lastUpdate = UserTimestamp.mkForUser(username); From 59cddf7bdf8d8b79bd648ac5b5434b70e65a3181 Mon Sep 17 00:00:00 2001 From: David Watkins Date: Wed, 25 Sep 2024 16:48:41 +0100 Subject: [PATCH 5/7] Bulk Measurable Rating import - UI shows how to configure/current config - rippler supports more entity kind combos - LF to EUDA - CI to App - mentioned on the recalc admin page link description - seperated the config calc from the ripple action - so we can re-use to show the config - Added support for more paths (from kind > to kind) - CI -> APP - LF -> EUDA #CTCTOWALTZ-3335 #7145 --- waltz-ng/client/system/recalculate-view.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/waltz-ng/client/system/recalculate-view.js b/waltz-ng/client/system/recalculate-view.js index b2ad4970ae..d19f975a14 100644 --- a/waltz-ng/client/system/recalculate-view.js +++ b/waltz-ng/client/system/recalculate-view.js @@ -20,6 +20,7 @@ import template from "./recalculate-view.html"; import {CORE_API} from "../common/services/core-api-utils"; import toasts from "../svelte-stores/toast-store"; import {initialiseData} from "../common"; +import _ from "lodash"; const initialState = { assessmentRippleConfig: [] @@ -37,17 +38,20 @@ function controller($q, serviceBroker) { .loadViewData(CORE_API.AssessmentDefinitionStore.findAll); $q.all([configPromise, defsPromise]) - .then(([cr, dr]) => { - const defsByExtId = _.keyBy(dr.data, d => d.externalId); + .then(([configResponse, defsResponse]) => { + const defsByExtId = _.keyBy( + defsResponse.data, + d => d.externalId); + vm.assessmentRippleConfig = _ - .chain(cr.data) + .chain(configResponse.data) .map(d => { - const steps = _.map(d.steps, s => ({from: defsByExtId[s.fromDef], to: defsByExtId[s.toDef]})); + const steps = _.map( + d.steps, + s => ({from: defsByExtId[s.fromDef], to: defsByExtId[s.toDef]})); return Object.assign({}, d, {steps}); }) .value(); - - console.log({assessmentRippleConfig: vm.assessmentRippleConfig}) }); }; From d02bc68c7bf5c14cee3a72ee538b17222116017c Mon Sep 17 00:00:00 2001 From: okoyzar Date: Thu, 10 Oct 2024 13:30:39 +0100 Subject: [PATCH 6/7] Static Diagrams Admin Page - added getById to SvgDiagramEndpoint - Created page in system admin to view and edit static diagrams - Route to static diagram page via ID - capability to add and delete new static diagram - filled in help blocks (add help for where to find magic group names) #7155 #CTCTOWALTZ-3382 --- waltz-ng/client/common/svelte/ViewLink.svelte | 12 ++ .../client/svelte-stores/svg-diagram-store.js | 8 + waltz-ng/client/system/routes.js | 13 ++ .../client/system/static-diagram-view.html | 21 ++ waltz-ng/client/system/static-diagram-view.js | 42 ++++ .../client/system/static-diagrams-view.html | 19 ++ .../client/system/static-diagrams-view.js | 39 ++++ .../static-diagrams/StaticDiagram.svelte | 196 ++++++++++++++++++ .../StaticDiagramsAdminView.svelte | 96 +++++++++ waltz-ng/client/system/system-admin-list.js | 6 + .../waltz/service/svg/SvgDiagramService.java | 1 + .../web/endpoints/api/SvgDiagramEndpoint.java | 10 +- 12 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 waltz-ng/client/system/static-diagram-view.html create mode 100644 waltz-ng/client/system/static-diagram-view.js create mode 100644 waltz-ng/client/system/static-diagrams-view.html create mode 100644 waltz-ng/client/system/static-diagrams-view.js create mode 100644 waltz-ng/client/system/svelte/static-diagrams/StaticDiagram.svelte create mode 100644 waltz-ng/client/system/svelte/static-diagrams/StaticDiagramsAdminView.svelte diff --git a/waltz-ng/client/common/svelte/ViewLink.svelte b/waltz-ng/client/common/svelte/ViewLink.svelte index 024eab1b45..fb809d375e 100644 --- a/waltz-ng/client/common/svelte/ViewLink.svelte +++ b/waltz-ng/client/common/svelte/ViewLink.svelte @@ -125,6 +125,18 @@ path: ctx => `server/${ctx.id}`, title: "Server View" }, + "main.system.static-diagrams": { + path: () => `system/static-diagrams`, + title: "Static Diagrams" + }, + "main.system.static-diagram": { + path: () => `system/static-diagrams/${ctx.id}`, + title: "" + }, + "main.system.static-diagram.create": { + path: ctx => `system/static-diagram/create`, + title: "Static Diagram Create" + }, "main.survey.instance.view": { path: ctx => `survey/instance/${ctx.id}/response/view`, title: "Survey View" diff --git a/waltz-ng/client/svelte-stores/svg-diagram-store.js b/waltz-ng/client/svelte-stores/svg-diagram-store.js index 9fb3ece9b0..2a2230b976 100644 --- a/waltz-ng/client/svelte-stores/svg-diagram-store.js +++ b/waltz-ng/client/svelte-stores/svg-diagram-store.js @@ -10,6 +10,13 @@ export function mkSvgDiagramStore() { [], {force}); + const getById = (diagramId, force) => remote + .fetchAppList( + "GET", + `api/svg-diagram/${diagramId}`, + [], + {force}); + const save = (diagram) => remote .execute( "POST", @@ -23,6 +30,7 @@ export function mkSvgDiagramStore() { return { findAll, + getById, save, remove }; diff --git a/waltz-ng/client/system/routes.js b/waltz-ng/client/system/routes.js index a9bcfe8dd6..7e8655c9ea 100644 --- a/waltz-ng/client/system/routes.js +++ b/waltz-ng/client/system/routes.js @@ -27,6 +27,8 @@ import RecalculateView from "./recalculate-view"; import ActorsView from "./actors-view"; import EntityNamedNoteTypesView from "./entity-named-node-types-view"; import StaticPanelsView from "./static-panels-view"; +import StaticDiagramsView from "./static-diagrams-view"; +import StaticDiagramView from "./static-diagram-view"; import AssessmentDefinitionsView from "./assessment-defintions-view"; import RatingSchemesView from "./rating-schemes-view"; import EudaListView from "./euda-list-view"; @@ -102,6 +104,15 @@ const staticPanelsState = { views: {"content@": StaticPanelsView} }; +const staticDiagramsState = { + url: "/static-diagrams", + views: {"content@": StaticDiagramsView} +} + +const staticDiagramState = { + url: "/static-diagrams/{id:int}", + views: {"content@": StaticDiagramView} +} const assessmentDefintionsState = { url: "/assessment-definitions", @@ -214,6 +225,8 @@ function setupRoutes($stateProvider) { .state("main.system.recalculate", recalculateState) .state("main.system.relationship-kinds", relationshipKindsState) .state("main.system.settings", settingsState) + .state("main.system.static-diagrams", staticDiagramsState) + .state("main.system.static-diagram", staticDiagramState) .state("main.system.static-panels", staticPanelsState) .state("main.system.version-info", versionInfoState); } diff --git a/waltz-ng/client/system/static-diagram-view.html b/waltz-ng/client/system/static-diagram-view.html new file mode 100644 index 0000000000..638c2a5daf --- /dev/null +++ b/waltz-ng/client/system/static-diagram-view.html @@ -0,0 +1,21 @@ + + + + + diff --git a/waltz-ng/client/system/static-diagram-view.js b/waltz-ng/client/system/static-diagram-view.js new file mode 100644 index 0000000000..bbbc8ef2a7 --- /dev/null +++ b/waltz-ng/client/system/static-diagram-view.js @@ -0,0 +1,42 @@ +/* + * Waltz - Enterprise Architecture + * Copyright (C) 2016, 2017, 2018, 2019 Waltz open source project + * See README.md for more information + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific + * + */ +import template from './static-diagram-view.html'; + +import StaticDiagram from "./svelte/static-diagrams/StaticDiagram.svelte" +import {initialiseData} from '../common'; + +const initialState = { + StaticDiagram +}; + +function controller($stateParams) { + const vm = initialiseData(this, initialState); + + vm.diagramId = $stateParams.id; +} + +controller.$inject = ["$stateParams"]; + +const page = { + controller, + template, + controllerAs: '$ctrl' +}; + + +export default page; \ No newline at end of file diff --git a/waltz-ng/client/system/static-diagrams-view.html b/waltz-ng/client/system/static-diagrams-view.html new file mode 100644 index 0000000000..939b8c72c9 --- /dev/null +++ b/waltz-ng/client/system/static-diagrams-view.html @@ -0,0 +1,19 @@ + + + diff --git a/waltz-ng/client/system/static-diagrams-view.js b/waltz-ng/client/system/static-diagrams-view.js new file mode 100644 index 0000000000..d9ab28d661 --- /dev/null +++ b/waltz-ng/client/system/static-diagrams-view.js @@ -0,0 +1,39 @@ +/* + * Waltz - Enterprise Architecture + * Copyright (C) 2016, 2017, 2018, 2019 Waltz open source project + * See README.md for more information + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific + * + */ +import template from './static-diagrams-view.html'; + +import StaticDiagramsAdminView from "./svelte/static-diagrams/StaticDiagramsAdminView.svelte" +import {initialiseData} from '../common'; + + +const initialState = { + StaticDiagramsAdminView +}; + + +function controller() { + initialiseData(this, initialState); +} + +const page = { + controller, + template, + controllerAs: '$ctrl' +}; + +export default page; \ No newline at end of file diff --git a/waltz-ng/client/system/svelte/static-diagrams/StaticDiagram.svelte b/waltz-ng/client/system/svelte/static-diagrams/StaticDiagram.svelte new file mode 100644 index 0000000000..f4b1fb58f5 --- /dev/null +++ b/waltz-ng/client/system/svelte/static-diagrams/StaticDiagram.svelte @@ -0,0 +1,196 @@ + + + + +
+
    +
  1. Home
  2. +
  3. System Admin
  4. +
  5. Static Diagrams
  6. +
  7. {diagram ? diagram.name : ""}
  8. +
+
+
+ +
+
+
+ + {#if diagram} +
+
+
+
+ +
+ +
+ Group determines what screen the diagram will appear. Some example naming conventions include +
    +
  • DATA_TYPE to appear on the Data screen
  • +
  • ORG_TREE to appear on the People screen
  • +
  • ORG_UNIT to appear on the Org Units screen
  • +
  • NAVAID.MEASURABLE.id to appear on one of the measurable screens, where id denotes the category (which can be found in the url of the category)
  • +
+
+
+
+
+
+ +
+ +
Description of this diagram
+
+
+
+
+
+
+ +
+ +
Short name that describes this diagram
+
+
+
+
+
+
+ +
+ +
If multiple diagrams belong to the same group, priority will be used to order them (ascending, where 1 is the highest priority)
+
+
+
+
+
+
+ +
+ +
HTML or SVG code, any paths will be resolved against the context root of this waltz installation, e.g., /waltz
+
+
+
+ + + +
+ {:else} +

not found

+ {/if} +
+
+
+ + diff --git a/waltz-ng/client/system/svelte/static-diagrams/StaticDiagramsAdminView.svelte b/waltz-ng/client/system/svelte/static-diagrams/StaticDiagramsAdminView.svelte new file mode 100644 index 0000000000..a0aea178fc --- /dev/null +++ b/waltz-ng/client/system/svelte/static-diagrams/StaticDiagramsAdminView.svelte @@ -0,0 +1,96 @@ + + + + +
+
    +
  1. Home
  2. +
  3. System Admin
  4. +
  5. Static Diagrams
  6. +
+
+
+ + +
+
+
+ + +
+ + + + + + + + + {#each visibleDiagrams as diagram} + + + + + + {/each} + + + + + + +
GroupDiagram Name
+ + {diagram.group} + + + {diagram.name} + + +
+ + + Add new + +
+
+
+
\ No newline at end of file diff --git a/waltz-ng/client/system/system-admin-list.js b/waltz-ng/client/system/system-admin-list.js index ad726839a1..1390243146 100644 --- a/waltz-ng/client/system/system-admin-list.js +++ b/waltz-ng/client/system/system-admin-list.js @@ -128,6 +128,12 @@ const referenceDataOptions= [ description: "View and edit relationship kinds", state: "main.system.relationship-kinds", icon: "link" + }, { + name: "Static Diagrams", + role: "ADMIN", + description: "View and edit static diagrams", + state: "main.system.static-diagrams", + icon: "picture-o" }, { name: "Static Panels", role: "ADMIN", diff --git a/waltz-service/src/main/java/org/finos/waltz/service/svg/SvgDiagramService.java b/waltz-service/src/main/java/org/finos/waltz/service/svg/SvgDiagramService.java index 65a45d27c4..312cb0ef85 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/svg/SvgDiagramService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/svg/SvgDiagramService.java @@ -101,4 +101,5 @@ public Boolean remove(long id) { public Boolean save(SvgDiagram diagram) { return svgDiagramDao.save(diagram); } + } \ No newline at end of file diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SvgDiagramEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SvgDiagramEndpoint.java index 7323e5a623..29953be06f 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SvgDiagramEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SvgDiagramEndpoint.java @@ -32,9 +32,7 @@ import static org.finos.waltz.web.WebUtilities.mkPath; import static org.finos.waltz.web.WebUtilities.readBody; import static org.finos.waltz.web.WebUtilities.requireRole; -import static org.finos.waltz.web.endpoints.EndpointUtilities.deleteForDatum; -import static org.finos.waltz.web.endpoints.EndpointUtilities.getForList; -import static org.finos.waltz.web.endpoints.EndpointUtilities.postForDatum; +import static org.finos.waltz.web.endpoints.EndpointUtilities.*; @Service @@ -57,6 +55,7 @@ public SvgDiagramEndpoint(SvgDiagramService svgDiagramService, @Override public void register() { String findAllPath = mkPath(BASE_URL); + String getById = mkPath(BASE_URL, ":id"); String removePath = mkPath(BASE_URL, ":id"); String savePath = mkPath(BASE_URL, "save"); String findByGroupsPath = mkPath(BASE_URL, "group"); @@ -67,6 +66,10 @@ public void register() { ListRoute findAllRoute = (request, response) -> svgDiagramService.findAll(); + DatumRoute getByIdRoute = (request, response) -> { + return svgDiagramService.getById(getId(request)); + }; + DatumRoute removeRoute = (request, response) -> { requireRole(userRoleService, request, SystemRole.ADMIN); return svgDiagramService.remove(getId(request)); @@ -79,6 +82,7 @@ public void register() { getForList(findByGroupsPath, findByGroupsRoute); getForList(findAllPath, findAllRoute); + getForDatum(getById, getByIdRoute); deleteForDatum(removePath, removeRoute); postForDatum(savePath, saveRoute); } From ad71e592393d1fcc840b3d5d08981c7c8fc52f22 Mon Sep 17 00:00:00 2001 From: okoyzar Date: Thu, 10 Oct 2024 13:40:10 +0100 Subject: [PATCH 7/7] Static Diagrams Admin Page - Viewlink ammend #7155 --- waltz-ng/client/common/svelte/ViewLink.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/waltz-ng/client/common/svelte/ViewLink.svelte b/waltz-ng/client/common/svelte/ViewLink.svelte index fb809d375e..f242d7f338 100644 --- a/waltz-ng/client/common/svelte/ViewLink.svelte +++ b/waltz-ng/client/common/svelte/ViewLink.svelte @@ -133,10 +133,6 @@ path: () => `system/static-diagrams/${ctx.id}`, title: "" }, - "main.system.static-diagram.create": { - path: ctx => `system/static-diagram/create`, - title: "Static Diagram Create" - }, "main.survey.instance.view": { path: ctx => `survey/instance/${ctx.id}/response/view`, title: "Survey View"