Skip to content

Commit

Permalink
Pull request #398: CTCTOWALTZ-3352 assessment ripple 7146
Browse files Browse the repository at this point in the history
Merge in WALTZ/waltz from WALTZ/waltz-dw:CTCTOWALTZ-3352-attestation-ripple-7146 to db-feature/waltz-7146-ripple-assessments

* commit '59cddf7bdf8d8b79bd648ac5b5434b70e65a3181':
  Bulk Measurable Rating import
  Bulk Measurable Rating import
  Bulk Measurable Rating import
  Assessment Rippler
  Assessment Rippler
  • Loading branch information
db-waltz authored and kamran-b-saleem-db committed Oct 14, 2024
2 parents b124a6d + 59cddf7 commit 9c44512
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 9c44512

Please sign in to comment.