Skip to content

Commit

Permalink
Primary Measurable Ratings
Browse files Browse the repository at this point in the history
- ratings browser for related measurables
  - optimised query to test if worth showing the section

#CTCTOWALTZ-2746
finos#6635
  • Loading branch information
db-waltz committed Jun 20, 2023
1 parent c10cd18 commit 4e75169
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
import org.finos.waltz.data.InlineSelectFieldFactory;
import org.finos.waltz.data.JooqUtilities;
import org.finos.waltz.data.SelectorUtilities;
import org.finos.waltz.model.*;
import org.finos.waltz.model.EntityKind;
import org.finos.waltz.model.EntityLifecycleStatus;
import org.finos.waltz.model.EntityReference;
import org.finos.waltz.model.IdSelectionOptions;
import org.finos.waltz.model.ImmutableEntityReference;
import org.finos.waltz.model.Operation;
import org.finos.waltz.model.Severity;
import org.finos.waltz.model.measurable_rating.ImmutableMeasurableRating;
import org.finos.waltz.model.measurable_rating.MeasurableRating;
import org.finos.waltz.model.measurable_rating.RemoveMeasurableRatingCommand;
Expand All @@ -32,8 +38,24 @@
import org.finos.waltz.model.tally.MeasurableRatingTally;
import org.finos.waltz.model.tally.Tally;
import org.finos.waltz.schema.Tables;
import org.finos.waltz.schema.tables.EntityHierarchy;
import org.finos.waltz.schema.tables.Measurable;
import org.finos.waltz.schema.tables.records.MeasurableRatingRecord;
import org.jooq.*;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.Record2;
import org.jooq.Record3;
import org.jooq.Record4;
import org.jooq.Record9;
import org.jooq.RecordMapper;
import org.jooq.Select;
import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep;
import org.jooq.SelectJoinStep;
import org.jooq.SelectOrderByStep;
import org.jooq.impl.DSL;
import org.jooq.lambda.tuple.Tuple2;
import org.slf4j.Logger;
Expand All @@ -58,7 +80,12 @@
import static org.finos.waltz.common.SetUtilities.union;
import static org.finos.waltz.common.StringUtilities.firstChar;
import static org.finos.waltz.common.StringUtilities.notEmpty;
import static org.finos.waltz.schema.Tables.*;
import static org.finos.waltz.schema.Tables.ALLOCATION;
import static org.finos.waltz.schema.Tables.CHANGE_LOG;
import static org.finos.waltz.schema.Tables.ENTITY_HIERARCHY;
import static org.finos.waltz.schema.Tables.MEASURABLE_CATEGORY;
import static org.finos.waltz.schema.Tables.MEASURABLE_RATING_PLANNED_DECOMMISSION;
import static org.finos.waltz.schema.Tables.USER_ROLE;
import static org.finos.waltz.schema.tables.Application.APPLICATION;
import static org.finos.waltz.schema.tables.Measurable.MEASURABLE;
import static org.finos.waltz.schema.tables.MeasurableRating.MEASURABLE_RATING;
Expand Down Expand Up @@ -334,6 +361,45 @@ public int removeForCategory(EntityReference ref, long categoryId) {
.execute();
}

/**
* Given a measurable and a selector, will determine if any other measurables are mapped by apps
* which map to this measurable. This is used to provide functionality for features like: "apps that
* do this function, also do these functions..."
*
* @param measurableId starting measurable
* @param selector set of apps to consider
* @return boolean indicating if there are implicitly related measurables
*/
public boolean hasImplicitlyRelatedMeasurables(long measurableId, Select<Record1<Long>> selector) {

org.finos.waltz.schema.tables.MeasurableRating mr1 = MEASURABLE_RATING.as("mr1");
org.finos.waltz.schema.tables.MeasurableRating mr2 = MEASURABLE_RATING.as("mr2");
Measurable m1 = MEASURABLE.as("m1");
Measurable m2 = MEASURABLE.as("m2");
EntityHierarchy eh = ENTITY_HIERARCHY.as("eh");

Condition appCondition = mr1.ENTITY_ID.in(selector)
.and(mr1.ENTITY_KIND.eq(DSL.val(EntityKind.APPLICATION.name())));

SelectConditionStep<Record> rawQry = DSL
.select()
.from(m1)
.innerJoin(eh).on(eh.ANCESTOR_ID.eq(m1.ID).and(eh.KIND.eq(EntityKind.MEASURABLE.name())))
.innerJoin(mr1).on(mr1.MEASURABLE_ID.eq(eh.ID))
.innerJoin(mr2).on(mr1.ENTITY_ID.eq(mr2.ENTITY_ID)
.and(mr1.ENTITY_KIND.eq(mr2.ENTITY_KIND))
.and(mr1.MEASURABLE_ID.ne(mr2.MEASURABLE_ID)))
.innerJoin(APPLICATION).on(mr1.ENTITY_ID.eq(APPLICATION.ID)
.and(APPLICATION.IS_REMOVED.isFalse())
.and(APPLICATION.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())))
.innerJoin(m2).on(mr2.MEASURABLE_ID.eq(m2.ID)
.and(m2.ENTITY_LIFECYCLE_STATUS.ne(EntityLifecycleStatus.REMOVED.name())))
.where(m1.ID.eq(measurableId)
.and(appCondition));

return dsl.fetchExists(rawQry);
}


// --- utils

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import _ from "lodash";
import {initialiseData, invokeFunction} from "../../../common";
import {buildHierarchies} from "../../../common/hierarchy-utils";
import {CORE_API} from "../../../common/services/core-api-utils";
import {distinctRatingCodes, indexRatingSchemes} from "../../../ratings/rating-utils";
import template from "./measurable-ratings-browser.html";
Expand Down Expand Up @@ -97,14 +96,14 @@ function controller(serviceBroker) {
vm.ratingSchemesById);

const lastViewedCategory = _.find(tabs, t => t.category.id === vm.lastViewedCategoryId);
const tab = lastViewedCategory || findFirstNonEmptyTab(tabs);
const startingTab = lastViewedCategory || findFirstNonEmptyTab(tabs);

vm.tabs = tabs;

if (!vm.visibility.tab) {
// no tab selected, select the first
vm.visibility.tab = _.get(tab, ["category", "id"]);
vm.onTabChange(tab);
// no startingTab selected, select the last viewed or first
vm.visibility.tab = _.get(startingTab, ["category", "id"]);
vm.onTabChange(startingTab);
}
}
};
Expand All @@ -129,7 +128,11 @@ function controller(serviceBroker) {
};

vm.onTabChange = (tc) => {
invokeFunction(vm.onCategorySelect, tc.category);
if (_.isNil(tc)) {
// no tab available yet, do nothing
} else {
invokeFunction(vm.onCategorySelect, tc.category);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,25 @@ <h4>Explicit Relationships</h4>
<waltz-related-measurables-panel parent-entity-ref="$ctrl.parentEntityRef">
</waltz-related-measurables-panel>

<div ng-if="$ctrl.relatedMeasurables.length > 0">
<div ng-if="$ctrl.hasImplicitlyRelatedMeasurables">
<h4>Implicit Relationships</h4>
<p class="text-muted">
<p class="help-block">
Implicit relationships are derived by looking at other viewpoints associated
to applications aligned to this viewpoint.
</p>
<div class="row"
ng-if="$ctrl.relatedMeasurables.length === 1">
<div class="col-sm-12">
<waltz-no-data>
<message>
<strong>No related viewpoints found.</strong>
</message>
</waltz-no-data>

</div>
</div>

<div class="row"
ng-if="$ctrl.relatedMeasurables.length > 1">
<div class="row">
<div class="col-sm-12">
<waltz-measurable-ratings-browser-tree-panel parent-entity-ref="$ctrl.parentEntityRef"
filters="$ctrl.filters">
</waltz-measurable-ratings-browser-tree-panel>
</div>
</div>

<div class="row"
ng-if="$ctrl.relatedMeasurables.length > 1">
<div class="row">
<div class="col-sm-12">
<br>
<p class="text-muted small">
<p class="help-block small">
The tree above shows correlations between this item and others. The small barchart
below each tree node shows how many applications exhibit the viewpoint in addition
to this viewpoint. The colors of the barchart indicated the relative rating counts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*
*/

import _ from "lodash";
import {initialiseData} from "../../../common";
import {CORE_API} from "../../../common/services/core-api-utils";

Expand All @@ -35,29 +34,9 @@ const bindings = {
};


const initialState = {};


function calcRelatedMeasurables(ratingTallies = [], allMeasurables = []) {
const relatedMeasurableIds = _.map(ratingTallies, "id");
const measurablesById = _.keyBy(allMeasurables, "id");
return _
.chain(allMeasurables)
.filter(m => _.includes(relatedMeasurableIds, m.id))
.reduce(
(acc, m) => {
let ptr = m;
while(ptr) {
acc[ptr.id] = ptr;
ptr = measurablesById[ptr.parentId];
}
return acc;
},
{})
.values()
.value();
}

const initialState = {
hasImplicitlyRelatedMeasurables: false
};

function controller($q, serviceBroker) {
const vm = initialiseData(this, initialState);
Expand All @@ -68,28 +47,11 @@ function controller($q, serviceBroker) {
return;
}

// QUESTION: can we do this without making the full call? Only used to hide/show implicit measurables
const selectionOptions = mkSelectionOptions(vm.parentEntityRef);

const measurablesPromise = serviceBroker
.loadAppData(CORE_API.MeasurableStore.findAll)
.then(r => vm.measurables = r.data);

const statsPromise = serviceBroker
.loadViewData(
CORE_API.MeasurableRatingStore.statsForRelatedMeasurables,
[ selectionOptions ])
.then(r => vm.stats = r.data);

const promises = [
measurablesPromise,
statsPromise
];

$q
.all(promises)
.then(() => vm.relatedMeasurables = calcRelatedMeasurables(vm.stats, vm.measurables));

serviceBroker
.loadAppData(CORE_API.MeasurableRatingStore.hasImplicitlyRelatedMeasurables, [vm.parentEntityRef.id, selectionOptions])
.then(r => vm.hasImplicitlyRelatedMeasurables = console.log(r) || r.data);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ function store($http, baseApiUrl) {
.then(d => d.data);
};

const statsForRelatedMeasurables = (options) => {
const hasImplicitlyRelatedMeasurables = (measurableId, options) => {
checkIsIdSelector(options);
return $http
.post(`${baseUrl}/related-stats/measurable`, options)
.post(`${baseUrl}/implicitly-related-measurables/${measurableId}`, options)
.then(d => d.data);
};

Expand Down Expand Up @@ -112,7 +112,7 @@ function store($http, baseApiUrl) {
findForEntityReference,
countByMeasurableCategory,
statsByAppSelector,
statsForRelatedMeasurables,
hasImplicitlyRelatedMeasurables,
saveRatingItem,
saveRatingIsPrimary,
saveRatingDescription,
Expand Down Expand Up @@ -159,10 +159,10 @@ export const MeasurableRatingStore_API = {
serviceFnName: "statsByAppSelector",
description: "return measurable stats by app selector"
},
statsForRelatedMeasurables: {
hasImplicitlyRelatedMeasurables: {
serviceName,
serviceFnName: "statsForRelatedMeasurables",
description: "return stats for related measurables"
serviceFnName: "hasImplicitlyRelatedMeasurables",
description: "return boolean if measurable has implicitly related measurables via apps [measurableId, options]"
},
saveRatingItem: {
serviceName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ public List<MeasurableRatingTally> statsByAppSelector(MeasurableRatingStatParams
params.showPrimaryOnly());
}


public boolean hasImplicitlyRelatedMeasurables(long measurableId, IdSelectionOptions options) {
Select<Record1<Long>> selector = applicationIdSelectorFactory.apply(options);
return measurableRatingDao.hasImplicitlyRelatedMeasurables(measurableId, selector);
}


// -- HELPERS --

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,20 @@
import org.finos.waltz.model.EntityReference;
import org.finos.waltz.model.Operation;
import org.finos.waltz.model.UserTimestamp;
import org.finos.waltz.model.measurable_rating.*;
import org.finos.waltz.model.measurable_rating.ImmutableRemoveMeasurableRatingCommand;
import org.finos.waltz.model.measurable_rating.ImmutableSaveMeasurableRatingCommand;
import org.finos.waltz.model.measurable_rating.MeasurableRating;
import org.finos.waltz.model.measurable_rating.MeasurableRatingStatParams;
import org.finos.waltz.model.measurable_rating.RemoveMeasurableRatingCommand;
import org.finos.waltz.model.measurable_rating.SaveMeasurableRatingCommand;
import org.finos.waltz.model.tally.MeasurableRatingTally;
import org.finos.waltz.model.tally.Tally;
import org.finos.waltz.service.measurable.MeasurableService;
import org.finos.waltz.service.measurable_rating.MeasurableRatingService;
import org.finos.waltz.service.permission.PermissionGroupService;
import org.finos.waltz.service.permission.permission_checker.MeasurableRatingPermissionChecker;
import org.finos.waltz.service.user.UserRoleService;
import org.finos.waltz.web.DatumRoute;
import org.finos.waltz.web.ListRoute;
import org.finos.waltz.web.endpoints.Endpoint;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -48,8 +54,18 @@
import static org.finos.waltz.common.StringUtilities.firstChar;
import static org.finos.waltz.model.EntityKind.MEASURABLE_RATING;
import static org.finos.waltz.model.EntityReference.mkRef;
import static org.finos.waltz.web.WebUtilities.*;
import static org.finos.waltz.web.endpoints.EndpointUtilities.*;
import static org.finos.waltz.web.WebUtilities.getEntityReference;
import static org.finos.waltz.web.WebUtilities.getId;
import static org.finos.waltz.web.WebUtilities.getLong;
import static org.finos.waltz.web.WebUtilities.getUsername;
import static org.finos.waltz.web.WebUtilities.mkPath;
import static org.finos.waltz.web.WebUtilities.readBody;
import static org.finos.waltz.web.WebUtilities.readIdSelectionOptionsFromBody;
import static org.finos.waltz.web.WebUtilities.requireRole;
import static org.finos.waltz.web.endpoints.EndpointUtilities.deleteForList;
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.postForList;

@Service
public class MeasurableRatingEndpoint implements Endpoint {
Expand Down Expand Up @@ -93,7 +109,7 @@ public void register() {
String findByCategoryPath = mkPath(BASE_URL, "category", ":id");
String countByMeasurableCategoryPath = mkPath(BASE_URL, "count-by", "measurable", "category", ":id");
String statsByAppSelectorPath = mkPath(BASE_URL, "stats-by", "app-selector");
String statsForRelatedMeasurablePath = mkPath(BASE_URL, "related-stats", "measurable");
String hasImplicitlyRelatedMeasurablesPath = mkPath(BASE_URL, "implicitly-related-measurables", ":measurableId");

String saveRatingItemPath = mkPath(BASE_URL, "entity", ":kind", ":id", "measurable", ":measurableId", "rating");
String saveRatingDescriptionPath = mkPath(BASE_URL, "entity", ":kind", ":id", "measurable", ":measurableId", "description");
Expand All @@ -117,8 +133,10 @@ public void register() {
ListRoute<MeasurableRatingTally> statsByAppSelectorRoute = (request, response)
-> measurableRatingService.statsByAppSelector(readBody(request, MeasurableRatingStatParams.class));

ListRoute<MeasurableRatingTally> statsForRelatedMeasurableRoute = (request, response)
-> measurableRatingService.statsForRelatedMeasurable(readIdSelectionOptionsFromBody(request));
DatumRoute<Boolean> hasImplicitlyRelatedMeasurablesRoute = (request, response)
-> measurableRatingService.hasImplicitlyRelatedMeasurables(
getLong(request, "measurableId"),
readIdSelectionOptionsFromBody(request));

getForList(findForEntityPath, findForEntityRoute);
postForList(findByMeasurableSelectorPath, findByMeasurableSelectorRoute);
Expand All @@ -128,7 +146,7 @@ public void register() {
deleteForList(modifyMeasurableForEntityPath, this::removeRoute);
deleteForList(modifyCategoryForEntityPath, this::removeCategoryRoute);
getForList(countByMeasurableCategoryPath, countByMeasurableCategoryRoute);
postForList(statsForRelatedMeasurablePath, statsForRelatedMeasurableRoute);
postForDatum(hasImplicitlyRelatedMeasurablesPath, hasImplicitlyRelatedMeasurablesRoute);
postForList(statsByAppSelectorPath, statsByAppSelectorRoute);

postForList(saveRatingItemPath, this::saveRatingItemRoute);
Expand Down

0 comments on commit 4e75169

Please sign in to comment.