From 4e75169295ad9082d361edb5cfe3462f563aad14 Mon Sep 17 00:00:00 2001 From: "david.watkins@db.com" Date: Tue, 20 Jun 2023 17:14:21 +0100 Subject: [PATCH] Primary Measurable Ratings - ratings browser for related measurables - optimised query to test if worth showing the section #CTCTOWALTZ-2746 #6635 --- .../MeasurableRatingDao.java | 72 ++++++++++++++++++- .../browser/measurable-ratings-browser.js | 15 ++-- .../related-measurables-section.html | 23 ++---- .../related-measurables-section.js | 50 ++----------- .../services/measurable-rating-store.js | 12 ++-- .../MeasurableRatingService.java | 6 ++ .../api/MeasurableRatingEndpoint.java | 32 +++++++-- 7 files changed, 126 insertions(+), 84 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java index 2b3536cd34..1a9098f3f7 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/measurable_rating/MeasurableRatingDao.java @@ -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; @@ -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; @@ -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; @@ -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> 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 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 diff --git a/waltz-ng/client/measurable-rating/components/browser/measurable-ratings-browser.js b/waltz-ng/client/measurable-rating/components/browser/measurable-ratings-browser.js index 03f1fa91a6..811e1e9894 100644 --- a/waltz-ng/client/measurable-rating/components/browser/measurable-ratings-browser.js +++ b/waltz-ng/client/measurable-rating/components/browser/measurable-ratings-browser.js @@ -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"; @@ -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); } } }; @@ -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); + } }; } diff --git a/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.html b/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.html index 3cc3f213c7..eff470cdae 100644 --- a/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.html +++ b/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.html @@ -22,26 +22,14 @@

Explicit Relationships

-
+

Implicit Relationships

-

+

Implicit relationships are derived by looking at other viewpoints associated to applications aligned to this viewpoint.

-
-
- - - No related viewpoints found. - - - -
-
-
+
@@ -49,11 +37,10 @@

Implicit Relationships

-
+

-

+

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. diff --git a/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.js b/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.js index cb0f2645dc..1d26ca6115 100644 --- a/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.js +++ b/waltz-ng/client/measurable-rating/components/related-measurables-section/related-measurables-section.js @@ -16,7 +16,6 @@ * */ -import _ from "lodash"; import {initialiseData} from "../../../common"; import {CORE_API} from "../../../common/services/core-api-utils"; @@ -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); @@ -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); }; } diff --git a/waltz-ng/client/measurable-rating/services/measurable-rating-store.js b/waltz-ng/client/measurable-rating/services/measurable-rating-store.js index a44afce31f..4ea95672e7 100644 --- a/waltz-ng/client/measurable-rating/services/measurable-rating-store.js +++ b/waltz-ng/client/measurable-rating/services/measurable-rating-store.js @@ -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); }; @@ -112,7 +112,7 @@ function store($http, baseApiUrl) { findForEntityReference, countByMeasurableCategory, statsByAppSelector, - statsForRelatedMeasurables, + hasImplicitlyRelatedMeasurables, saveRatingItem, saveRatingIsPrimary, saveRatingDescription, @@ -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, diff --git a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java index 7b4f34371f..408e03527e 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/measurable_rating/MeasurableRatingService.java @@ -215,6 +215,12 @@ public List statsByAppSelector(MeasurableRatingStatParams params.showPrimaryOnly()); } + + public boolean hasImplicitlyRelatedMeasurables(long measurableId, IdSelectionOptions options) { + Select> selector = applicationIdSelectorFactory.apply(options); + return measurableRatingDao.hasImplicitlyRelatedMeasurables(measurableId, selector); + } + // -- HELPERS -- diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java index b6f4d9da6b..48fa2b37c5 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/MeasurableRatingEndpoint.java @@ -23,7 +23,12 @@ 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; @@ -31,6 +36,7 @@ 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; @@ -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 { @@ -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"); @@ -117,8 +133,10 @@ public void register() { ListRoute statsByAppSelectorRoute = (request, response) -> measurableRatingService.statsByAppSelector(readBody(request, MeasurableRatingStatParams.class)); - ListRoute statsForRelatedMeasurableRoute = (request, response) - -> measurableRatingService.statsForRelatedMeasurable(readIdSelectionOptionsFromBody(request)); + DatumRoute hasImplicitlyRelatedMeasurablesRoute = (request, response) + -> measurableRatingService.hasImplicitlyRelatedMeasurables( + getLong(request, "measurableId"), + readIdSelectionOptionsFromBody(request)); getForList(findForEntityPath, findForEntityRoute); postForList(findByMeasurableSelectorPath, findByMeasurableSelectorRoute); @@ -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);