Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from finos:master #101

Merged
merged 11 commits into from
Oct 15, 2024

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
Loading