Skip to content

Commit

Permalink
Merge pull request #7165 from deutschebank/db-contrib/waltz-7146-ripp…
Browse files Browse the repository at this point in the history
…le-assessments

Db contrib/waltz 7146 ripple assessments
  • Loading branch information
davidwatkins73 authored Oct 15, 2024
2 parents f9986a5 + 9c44512 commit 54d6261
Show file tree
Hide file tree
Showing 16 changed files with 1,059 additions and 16 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void baseSetup() {
}


private DSLContext getDsl() {
protected DSLContext getDsl() {
return ctx.getBean(DSLContext.class);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
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;
import org.finos.waltz.test_common.helpers.MeasurableHelper;
import org.finos.waltz.test_common.helpers.RatingSchemeHelper;
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;
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 {

private static final AssessmentRating ar = ASSESSMENT_RATING;

@Autowired
private AppHelper appHelper;

@Autowired
private MeasurableHelper measurableHelper;

@Autowired
private AssessmentHelper assessmentHelper;

@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"));
long schemeB = ratingSchemeHelper.createEmptyRatingScheme(mkName(stem, "bad_ripple_b"));

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() 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 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);
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);
}


}
Original file line number Diff line number Diff line change
@@ -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<A, B> {

public abstract Collection<Tuple2<A, B>> allIntersection();
public abstract Collection<B> otherOnly();
public abstract Collection<A> waltzOnly();
public abstract Collection<Tuple2<A, B>> differingIntersection();


public static <A, B, K> PairDiffResult<A, B> mkPairDiff(Collection<A> waltzRecords,
Collection<B> otherRecords,
Function<A, K> aKeyFn,
Function<B, K> bKeyFn,
BiPredicate<A, B> equalityPredicate) {
Map<K, A> waltzByKey = indexBy(waltzRecords, aKeyFn);
Map<K, B> othersByKey = indexBy(otherRecords, bKeyFn);

Set<B> otherOnly = otherRecords.stream()
.filter(f -> !waltzByKey.containsKey(bKeyFn.apply(f)))
.collect(Collectors.toSet());

Set<A> waltzOnly = waltzRecords.stream()
.filter(f -> !othersByKey.containsKey(aKeyFn.apply(f)))
.collect(Collectors.toSet());

Set<Tuple2<A, B>> intersect = otherRecords.stream()
.map(other -> tuple(
waltzByKey.get(bKeyFn.apply(other)),
other))
.filter(t -> t.v1 != null)
.collect(Collectors.toSet());

Set<Tuple2<A, B>> differingIntersection = intersect.stream()
.filter(t -> ! equalityPredicate.test(t.v1, t.v2))
.collect(Collectors.toSet());

return ImmutablePairDiffResult
.<A, B>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());
}
}
Original file line number Diff line number Diff line change
@@ -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<AssessmentRipplerJobStep> steps();

}
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ public enum JobKey {
REPORT_GRID_RECALCULATE_APP_GROUPS_FROM_FILTERS,

ALLOCATED_COSTS_POPULATOR,
RIPPLE_ASSESSMENTS,
COMPLEXITY_REBUILD_MEASURABLE
}
26 changes: 25 additions & 1 deletion waltz-ng/client/assessments/services/assessment-rating-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -106,7 +118,9 @@ export function store($http, BaseApiUrl) {
unlock,
bulkStore,
bulkRemove,
remove
remove,
ripple,
findRippleConfig
};
}

Expand Down Expand Up @@ -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"
}
};

Loading

0 comments on commit 54d6261

Please sign in to comment.