diff --git a/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java b/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java index 0e4b460ba5..a6801ebdc1 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java @@ -104,6 +104,7 @@ public class FlowClassificationRuleDao { private final static EntityHierarchy ehDataType = ENTITY_HIERARCHY.as("ehDataType"); public static final Field vantagePointId = DSL.coalesce(ehOrgUnit.ID, FLOW_CLASSIFICATION_RULE.PARENT_ID); public static final Field vantagePointLevel = DSL.coalesce(ehOrgUnit.LEVEL, 0).as("parentLevel"); + public static final Field dataTypeLevel = DSL.coalesce(ehDataType.LEVEL, 0).as("dataTypeLevel"); private static final Field PARENT_NAME_FIELD = InlineSelectFieldFactory.mkNameField( FLOW_CLASSIFICATION_RULE.PARENT_ID, FLOW_CLASSIFICATION_RULE.PARENT_KIND, @@ -164,18 +165,20 @@ public class FlowClassificationRuleDao { }; - private static final RecordMapper TO_VANTAGE_MAPPER = r -> ImmutableFlowClassificationRuleVantagePoint - .builder() - .vantagePoint(mkRef(EntityKind.valueOf(r.get(FLOW_CLASSIFICATION_RULE.PARENT_KIND)), r.get(vantagePointId))) // could be child org unit - .vantagePointRank(r.get(vantagePointLevel)) - .subjectReference(mkRef(EntityKind.valueOf(r.get(FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_KIND)), r.get(FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID))) - .classificationCode(r.get(FLOW_CLASSIFICATION.CODE)) - .dataType(mkRef(EntityKind.DATA_TYPE, r.get(ehDataType.ID))) - .dataTypeRank(r.get(ehDataType.LEVEL)) - .ruleId(r.get(FLOW_CLASSIFICATION_RULE.ID)) - .message(r.get(FLOW_CLASSIFICATION_RULE.MESSAGE)) - .messageSeverity(Severity.valueOf(r.get(FLOW_CLASSIFICATION_RULE.MESSAGE_SEVERITY))) - .build(); + private static final RecordMapper TO_VANTAGE_MAPPER = r -> { + return ImmutableFlowClassificationRuleVantagePoint + .builder() + .vantagePoint(mkRef(EntityKind.valueOf(r.get(FLOW_CLASSIFICATION_RULE.PARENT_KIND)), r.get(vantagePointId))) // could be child org unit + .vantagePointRank(r.get(vantagePointLevel)) + .subjectReference(mkRef(EntityKind.valueOf(r.get(FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_KIND)), r.get(FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID))) + .classificationCode(r.get(FLOW_CLASSIFICATION.CODE)) + .dataTypeId(r.get(ehDataType.ID)) + .dataTypeRank(r.get(dataTypeLevel)) + .ruleId(r.get(FLOW_CLASSIFICATION_RULE.ID)) + .message(r.get(FLOW_CLASSIFICATION_RULE.MESSAGE)) + .messageSeverity(Severity.valueOf(r.get(FLOW_CLASSIFICATION_RULE.MESSAGE_SEVERITY))) + .build(); + }; @@ -366,7 +369,7 @@ public List findExpandedFlowClassificationRu FLOW_CLASSIFICATION_RULE.PARENT_KIND, vantagePointLevel, ehDataType.ID, - ehDataType.LEVEL, + dataTypeLevel, FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID, FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_KIND, FLOW_CLASSIFICATION.CODE, @@ -376,7 +379,7 @@ public List findExpandedFlowClassificationRu .from(FLOW_CLASSIFICATION_RULE) .innerJoin(FLOW_CLASSIFICATION).on(FLOW_CLASSIFICATION.DIRECTION.eq(direction.name()) .and(FLOW_CLASSIFICATION_RULE.FLOW_CLASSIFICATION_ID.eq(FLOW_CLASSIFICATION.ID))) - .innerJoin(ehDataType) + .leftJoin(ehDataType) .on(ehDataType.KIND.eq(EntityKind.DATA_TYPE.name()) .and(ehDataType.ANCESTOR_ID.eq(FLOW_CLASSIFICATION_RULE.DATA_TYPE_ID))) .leftJoin(ehOrgUnit) @@ -401,7 +404,7 @@ private List findFlowClassificationRuleVanta FLOW_CLASSIFICATION_RULE.PARENT_KIND, vantagePointLevel, ehDataType.ID, - ehDataType.LEVEL, + dataTypeLevel, FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID, FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_KIND, FLOW_CLASSIFICATION.CODE, @@ -413,10 +416,8 @@ private List findFlowClassificationRuleVanta .on(ehOrgUnit.ANCESTOR_ID.eq(FLOW_CLASSIFICATION_RULE.PARENT_ID) .and(ehOrgUnit.KIND.eq(EntityKind.ORG_UNIT.name())) .and(ehOrgUnit.ID.eq(ehOrgUnit.ANCESTOR_ID))) - .innerJoin(DataType.DATA_TYPE) - .on(DataType.DATA_TYPE.ID.eq(FLOW_CLASSIFICATION_RULE.DATA_TYPE_ID)) - .innerJoin(ehDataType) - .on(ehDataType.ANCESTOR_ID.eq(DataType.DATA_TYPE.ID) + .leftJoin(ehDataType) + .on(ehDataType.ANCESTOR_ID.eq(FLOW_CLASSIFICATION_RULE.DATA_TYPE_ID) .and(ehDataType.KIND.eq(EntityKind.DATA_TYPE.name())) .and(ehDataType.ID.eq(ehDataType.ANCESTOR_ID))) .innerJoin(FLOW_CLASSIFICATION).on(FLOW_CLASSIFICATION_RULE.FLOW_CLASSIFICATION_ID.eq(FLOW_CLASSIFICATION.ID)) @@ -424,7 +425,7 @@ private List findFlowClassificationRuleVanta .orderBy( FLOW_CLASSIFICATION_RULE.PARENT_KIND, //ACTOR, APPLICATION, ORG_UNIT vantagePointLevel.desc(), - ehDataType.LEVEL.desc(), + dataTypeLevel.desc(), vantagePointId, ehDataType.ID, FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID diff --git a/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java index 3095d566e9..d7521e1876 100644 --- a/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java +++ b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java @@ -230,7 +230,7 @@ private static Map> applyVantagePoints(FlowDire .forEach(rvp -> { Set childOUs = findChildren(ouHierarchy, rvp.vantagePoint().id()); - Set childDTs = findChildren(dtHierarchy, rvp.dataType().id()); + Set childDTs = findChildren(dtHierarchy, rvp.dataTypeId()); population.forEach(p -> { Tuple2 currentRuleAndOutcome = lfdIdToRuleAndOutcomeMap.get(p.lfdId()); if (currentRuleAndOutcome != null && currentRuleAndOutcome.v2 == MatchOutcome.POSITIVE_MATCH) { diff --git a/waltz-model/src/main/java/org/finos/waltz/model/entity_hierarchy/EntityHierarchy.java b/waltz-model/src/main/java/org/finos/waltz/model/entity_hierarchy/EntityHierarchy.java index 1d1039148f..56a51d1342 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/entity_hierarchy/EntityHierarchy.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/entity_hierarchy/EntityHierarchy.java @@ -2,6 +2,7 @@ import org.immutables.value.Value; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -11,19 +12,27 @@ public abstract class EntityHierarchy { public abstract List hierarchyItems(); - public Set findChildren(long parentId) { - return hierarchyItems() - .stream() - .filter(t -> t.id().isPresent() && t.parentId().isPresent() && t.parentId().get() == parentId) - .map(t -> t.id().get()) - .collect(Collectors.toSet()); + public Set findChildren(Long parentId) { + if(parentId == null) { + return Collections.emptySet(); + } else { + return hierarchyItems() + .stream() + .filter(t -> t.id().isPresent() && t.parentId().isPresent() && t.parentId().get().equals(parentId)) + .map(t -> t.id().get()) + .collect(Collectors.toSet()); + } } - public Set findAncestors(long childId) { - return hierarchyItems() - .stream() - .filter(t -> t.id().isPresent() && t.parentId().isPresent() && t.id().get() == childId) - .map(t -> t.parentId().get()) - .collect(Collectors.toSet()); + public Set findAncestors(Long childId) { + if(childId == null) { + return Collections.emptySet(); + } else { + return hierarchyItems() + .stream() + .filter(t -> t.id().isPresent() && t.parentId().isPresent() && t.id().get().equals(childId)) + .map(t -> t.parentId().get()) + .collect(Collectors.toSet()); + } } } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/flow_classification_rule/FlowClassificationRuleVantagePoint.java b/waltz-model/src/main/java/org/finos/waltz/model/flow_classification_rule/FlowClassificationRuleVantagePoint.java index 5d26c620ab..aa1ebb1f30 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/flow_classification_rule/FlowClassificationRuleVantagePoint.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/flow_classification_rule/FlowClassificationRuleVantagePoint.java @@ -33,7 +33,8 @@ public abstract class FlowClassificationRuleVantagePoint { public abstract EntityReference vantagePoint(); public abstract int vantagePointRank(); - public abstract EntityReference dataType(); + @Nullable + public abstract Long dataTypeId(); public abstract int dataTypeRank(); public abstract EntityReference subjectReference(); diff --git a/waltz-ng/client/common/services/enums/entity.js b/waltz-ng/client/common/services/enums/entity.js index 4590be429c..3c8e9449f6 100644 --- a/waltz-ng/client/common/services/enums/entity.js +++ b/waltz-ng/client/common/services/enums/entity.js @@ -166,7 +166,7 @@ export const entity = { }, END_USER_APPLICATION: { key: "END_USER_APPLICATION", - name: "End User App", + name: "End User Application", icon: "table", description: null, position: 1200 diff --git a/waltz-ng/client/data-flow/components/application-flow-summary-graph/application-flow-summary-graph.js b/waltz-ng/client/data-flow/components/application-flow-summary-graph/application-flow-summary-graph.js index 4483ecedc1..3d0816593f 100644 --- a/waltz-ng/client/data-flow/components/application-flow-summary-graph/application-flow-summary-graph.js +++ b/waltz-ng/client/data-flow/components/application-flow-summary-graph/application-flow-summary-graph.js @@ -27,7 +27,8 @@ import {loadFlowClassificationRatings} from "../../../flow-classification-rule/f const bindings = { - summaryData: "<" + summaryData: "<", + ratingDirection: "<" }; @@ -197,12 +198,18 @@ function controller($element, serviceBroker) { const vm = initialiseData(this, initialState); let svg = null; + const redraw = () => drawData( svg, enrichData(vm.summaryData, vm.flowClassifications), vm.flowClassifications); - vm.$onChanges = () => redraw(); + vm.$onChanges = () => { + + loadFlowClassificationRatings(serviceBroker) + .then(xs => vm.flowClassifications = _.filter(xs, d => d.direction === vm.ratingDirection)) + .then(redraw); + } vm.$onInit = () => { const holder = $element.find("svg")[0]; @@ -214,10 +221,6 @@ function controller($element, serviceBroker) { drawBackground(svg); drawTitleBar(svg); drawCenterLabels(svg); - - loadFlowClassificationRatings(serviceBroker) - .then(xs => vm.flowClassifications = xs) - .then(redraw); }; } diff --git a/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.html b/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.html index a51f18c58c..2e27cb1fa7 100644 --- a/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.html +++ b/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.html @@ -24,7 +24,8 @@
- +

diff --git a/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.js b/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.js index e16091be7c..827559e750 100644 --- a/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.js +++ b/waltz-ng/client/data-flow/components/application-flow-summary-pane/application-flow-summary-pane.js @@ -25,15 +25,14 @@ import {categorizeDirection} from "../../../logical-flow/logical-flow-utils"; import {nest} from "d3-collection"; import template from "./application-flow-summary-pane.html"; -import {tallyBy} from "../../../common/tally-utils"; -import {color} from "d3-color"; -import indexByKeyForType from "../../../enum-value/enum-value-utilities"; import {entity} from "../../../common/services/enums/entity"; import {loadFlowClassificationRatings} from "../../../flow-classification-rule/flow-classification-utils"; +import {flowDirection as FlowDirection} from "../../../common/services/enums/flow-direction"; const bindings = { - parentEntityRef: "<" + parentEntityRef: "<", + ratingDirection: "<" }; @@ -64,21 +63,24 @@ function enrichDecorators(parentEntityRef, unknownDataTypeId, logicalFlows = [], } -function calcStats(enrichedDecorators = []) { +function calcStats(enrichedDecorators = [], ratingDirection = FlowDirection.OUTBOUND.key) { + const byDirectionAndMappingStatus = nest() .key(d => d.direction) .key(d => d.mappingStatus) .object(enrichedDecorators); + const ratingMapper = d => ratingDirection === FlowDirection.OUTBOUND.key ? d.decorator.rating : d.decorator.targetInboundRating; + const byDirectionAndAuthoritativeness = nest() .key(d => d.direction) - .key(d => d.decorator.rating) + .key(ratingMapper) .object(enrichedDecorators); const chartData = nest() .key(d => d.direction) .key(d => d.mappingStatus) - .key(d => d.decorator.rating) + .key(ratingMapper) .rollup(xs => xs.length) .object(enrichedDecorators); @@ -119,29 +121,23 @@ function controller($q, serviceBroker) { logicalFlows, decorators); - vm.stats = calcStats(vm.enrichedDecorators); + vm.stats = calcStats(vm.enrichedDecorators, vm.ratingDirection); }); - - const physicalFlowPromise = serviceBroker - .loadViewData( - CORE_API.PhysicalFlowStore.findByEntityReference, - [vm.parentEntityRef]) - .then(r => r.data); }; - const loadUnknownDataType = () => { + const loadUnknownDataTypeId = () => { return serviceBroker .loadAppData(CORE_API.DataTypeStore.findAll) - .then(r => findUnknownDataTypeId(r.data)); + .then(r => console.log(r.data) || findUnknownDataTypeId(r.data)); }; - - vm.$onInit = () => { - loadUnknownDataType() - .then(unknownDataType => reload(unknownDataType.id)); + vm.$onChanges = () => { + loadUnknownDataTypeId() + .then(dtId => reload(dtId)); loadFlowClassificationRatings(serviceBroker) - .then(xs => vm.flowClassificationCols = xs); + .then(xs => vm.flowClassificationCols = console.log({xs, rd: vm.ratingDirection}) + || _.filter(xs, d => d.direction === vm.ratingDirection)); } } diff --git a/waltz-ng/client/data-flow/components/data-flow-section/data-flow-section.html b/waltz-ng/client/data-flow/components/data-flow-section/data-flow-section.html index f65654eb2f..8eda6a3fcf 100644 --- a/waltz-ng/client/data-flow/components/data-flow-section/data-flow-section.html +++ b/waltz-ng/client/data-flow/components/data-flow-section/data-flow-section.html @@ -262,7 +262,8 @@

Bulk Insert Logical Data Flows

- +
diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/FlowDetailPanel.svelte b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/FlowDetailPanel.svelte index 466f69842c..4204b7bc7f 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/FlowDetailPanel.svelte +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/FlowDetailPanel.svelte @@ -107,7 +107,6 @@ assessmentDefinitions={allAssessmentDefinitions}/>
import SearchInput from "../../../../common/svelte/SearchInput.svelte"; import {termSearch} from "../../../../common"; - import RatingIndicatorCell from "../../../../ratings/components/rating-indicator-cell/RatingIndicatorCell.svelte"; import _ from "lodash"; import {filters, selectedLogicalFlow, selectedPhysicalFlow, updateFilters} from "./flow-details-store"; - import {truncate} from "../../../../common/string-utils"; - import Tooltip from "../../../../common/svelte/Tooltip.svelte"; - import DataTypeTooltipContent from "./DataTypeMiniTable.svelte"; - import NoData from "../../../../common/svelte/NoData.svelte"; - import Icon from "../../../../common/svelte/Icon.svelte"; + import {SlickGrid, SlickRowSelectionModel} from "slickgrid"; + import {mkSortFn} from "../../../../common/slick-grid-utils"; + import {mkLogicalFlowTableColumns, showDataTypeTooltip + } from "./flow-detail-utils"; export let logicalFlows = []; export let flowClassifications = []; export let assessmentDefinitions = []; + const gridOptions = { + enableCellNavigation: false, + enableColumnReorder: false, + frozenColumn: 4 + }; + + let elem = null; + let grid; let qry; let selectionLatchOpen = true; let defs = []; + let flowClassificationsByCode = {}; function isSameFlow(a, b) { const aId = _.get(a, ["logicalFlow", "id"]); @@ -24,15 +31,6 @@ return aId === bId; } - function mkDataTypeString(dataTypes) { - return _ - .chain(dataTypes) - .map(d => d.decoratorEntity.name) - .orderBy(d => d) - .join(", ") - .value(); - } - function selectLogicalFlow(flow) { const addFilter = () => { @@ -63,28 +61,38 @@ selectionLatchOpen = true; $selectedLogicalFlow = flow; $selectedPhysicalFlow = null; - addFilter() + addFilter(); } } - function mkDataTypeTooltipProps(row) { - return { - decorators: row.dataTypesForLogicalFlow, - flowClassifications - }; - } + function initGrid(elem) { + let columns = mkLogicalFlowTableColumns(defs); + grid = new SlickGrid(elem, [], columns, gridOptions); + grid.setSelectionModel(new SlickRowSelectionModel()); + grid.onSort.subscribe((e, args) => { + const sortCol = args.sortCol; + grid.data.sort(mkSortFn(sortCol, args.sortAsc)); + grid.invalidate(); + }); + grid.onMouseEnter.subscribe(function(e, args) { + const cell = grid.getCellFromEvent(e); + if (! cell) return; + + const columnDef = columns[cell.cell]; + if (columnDef.id === 'data_types') { + const rowData = flowList[cell.row]; + const cellElem = e.target; + showDataTypeTooltip(cellElem, rowData.dataTypesForLogicalFlow, flowClassificationsByCode); + } + }); + grid.onClick.subscribe((a,b) => selectLogicalFlow(flowList[b.row])) - function flowCountToIcon(flowCount) { - switch (flowCount) { - case 0: - return ""; - case 1: - return "file-o"; - default: - return "folder-o"; - } + grid.data = flowList; + grid.invalidate() } + $: flowClassificationsByCode = _.keyBy(flowClassifications, d => d.code); + $: visibleFlows = _.filter(logicalFlows, d => d.visible); $: flowList = _.isEmpty(qry) @@ -105,6 +113,24 @@ assessmentDefinitions, d => d.entityKind === 'LOGICAL_DATA_FLOW'); + + $: { + if (elem && !_.isNil(flowList)) { + initGrid(elem); + } + } + + $: { + if (grid) { + if (flowList && $selectedLogicalFlow) { + const rowIdx = _.chain(flowList).map(f => f.logicalFlow).indexOf($selectedLogicalFlow.logicalFlow).value(); + grid.setSelectedRows([rowIdx]); + } else { + grid.setSelectedRows([]); + } + } + } +

@@ -123,111 +149,8 @@

-
10}> - - - - - - - - - - {#each defs as defn} - - {/each} - - - - {#each flowList as flow} - selectLogicalFlow(flow)}> - - - - - - - {#each defs as defn} - {@const assessmentRatingsForFlow = _.get(flow.logicalFlowRatingsByDefId, defn.id, [])} - - {/each} - - {:else} - - - - {/each} - -
SourceSrc Ext IDTargetTarget Ext IDData Types{defn.name}
- - - - {#if flow.logicalFlow.isReadOnly} - - - - {/if} - - {flow.logicalFlow.source.name} - - {flow.logicalFlow.source.externalId} - - {flow.logicalFlow.target.name} - - {flow.logicalFlow.target.externalId} - - - - {truncate(mkDataTypeString(flow.dataTypesForLogicalFlow), 30)} - - - -
- {#each assessmentRatingsForFlow as rating} - - {/each} -
-
- There are no logical flows to show, these may have been filtered. -
+
- - \ No newline at end of file diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/PhysicalFlowTable.svelte b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/PhysicalFlowTable.svelte index d43f1869b1..7bc80e2ba8 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/PhysicalFlowTable.svelte +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/PhysicalFlowTable.svelte @@ -5,70 +5,71 @@ import _ from "lodash"; import {enumValueStore} from "../../../../svelte-stores/enum-value-store"; import {nestEnums} from "../../../../common/svelte/enum-utils"; - import { - toCriticalityName, - toFrequencyKindName, - toTransportKindName - } from "../../../../physical-flows/svelte/physical-flow-registration-utils"; import {selectedPhysicalFlow, selectedLogicalFlow} from "./flow-details-store"; - import NoData from "../../../../common/svelte/NoData.svelte"; - import DataTypeTooltipContent from "./DataTypeMiniTable.svelte"; - import {truncate} from "../../../../common/string-utils"; - import Tooltip from "../../../../common/svelte/Tooltip.svelte"; - import RatingIndicatorCell from "../../../../ratings/components/rating-indicator-cell/RatingIndicatorCell.svelte"; - import EntityIcon from "../../../../common/svelte/EntityIcon.svelte"; - import Icon from "../../../../common/svelte/Icon.svelte"; + import { + mkPhysicalFlowTableColumns, + showDataTypeTooltip + } from "./flow-detail-utils"; + import {SlickGrid, SlickRowSelectionModel} from "slickgrid"; + import {mkSortFn} from "../../../../common/slick-grid-utils"; + import {entity as EntityKinds} from "../../../../common/services/enums/entity"; + export let physicalFlows = []; - export let flowClassifications = []; export let assessmentDefinitions = []; + + function initGrid(elem, nestedEnums) { + let columns = mkPhysicalFlowTableColumns(defs, nestedEnums); + + grid = new SlickGrid(elem, [], columns, gridOptions); + grid.setSelectionModel(new SlickRowSelectionModel()); + grid.onSort.subscribe((e, args) => { + const sortCol = args.sortCol; + grid.data.sort(mkSortFn(sortCol, args.sortAsc)); + grid.invalidate(); + }); + grid.onMouseEnter.subscribe(function(e, args) { + const cell = grid.getCellFromEvent(e); + if (! cell) return; + + const columnDef = columns[cell.cell]; + if (columnDef.id === 'data_types') { + const rowData = flowList[cell.row]; + const cellElem = e.target; + showDataTypeTooltip(cellElem, rowData.dataTypesForSpecification); + } + }); + grid.onClick.subscribe((a,b) => selectPhysicalFlow(flowList[b.row])) + + grid.data = flowList; + grid.invalidate() + } + + function selectPhysicalFlow(flow) { if ($selectedPhysicalFlow === flow) { $selectedPhysicalFlow = null; + $selectedLogicalFlow = null; } else { $selectedPhysicalFlow = flow; $selectedLogicalFlow = flow; } } - function mkDataTypeTooltipProps(row) { - row.dataTypesForSpecification; - - const ratingByDataTypeId = _.reduce( - row.dataTypesForLogicalFlow, - (acc, d) => { - acc[d.decoratorEntity.id] = d.rating; - return acc; - }, - {}); - - const decorators = _.map( - row.dataTypesForSpecification, - d => Object.assign( - {}, - d, - { rating: ratingByDataTypeId[d.decoratorEntity?.id] })); - - return { - decorators, - flowClassifications - }; - } - - function mkDataTypeString(dataTypes) { - return _ - .chain(dataTypes) - .map(d => d.decoratorEntity.name) - .orderBy(d => d) - .join(", ") - .value(); - } + const gridOptions = { + enableCellNavigation: false, + enableColumnReorder: false, + frozenColumn: 3 + }; let qry; + let grid; + let elem = null; + let visibleFlows; let enumsCall = enumValueStore.load(); - let nestedEnums; + let nestedEnums = null; $: nestedEnums = nestEnums($enumsCall.data); @@ -97,7 +98,20 @@ $: defs = _.filter( assessmentDefinitions, - d => d.entityKind === 'PHYSICAL_FLOW' || d.entityKind === 'PHYSICAL_SPECIFICATION'); + d => d.entityKind === EntityKinds.PHYSICAL_FLOW.key || + d.entityKind === EntityKinds.PHYSICAL_SPECIFICATION.key); + + $: { + if (elem && !_.isNil(flowList) && nestedEnums) { + initGrid(elem, nestedEnums); + } + } + + $: { + if (grid) { + grid.setSelectedRows([_.indexOf(flowList, $selectedPhysicalFlow)]); + } + } @@ -117,134 +131,8 @@
-
10}> - - - - - - - - - - - - - - - {#each defs as defn} - - {/each} - - - - {#each flowList as flow} - {@const ratingsForFlowByDefId = _.merge(flow.physicalSpecRatingsByDefId, flow.physicalFlowRatingsByDefId)} - - selectPhysicalFlow(flow)}> - - - - - - - - - - - - {#each defs as defn} - {@const assessmentRatingsForFlow = _.get(ratingsForFlowByDefId, defn.id, [])} - - {/each} - - - {:else} - - - - {/each} - -
SourceSrc Ext IDTargetTarget Ext IDNameExternal IDData TypesCriticalityFrequencyTransport Kind - - {defn.name} -
- {#if flow.physicalFlow.isReadOnly} - - - - {/if} - - {flow.logicalFlow.source.name} - - {flow.logicalFlow.source.externalId} - - {flow.logicalFlow.target.name} - - {flow.logicalFlow.target.externalId} - - {flow.physicalFlow.name || flow.specification?.name || ""} - - {flow.physicalFlow.externalId || ""} - - - - {truncate(mkDataTypeString(flow.dataTypesForSpecification), 30)} - - - - {toCriticalityName(nestedEnums, flow.physicalFlow.criticality)} - - {toFrequencyKindName(nestedEnums, flow.physicalFlow.frequency)} - - {toTransportKindName(nestedEnums, flow.physicalFlow.transport)} - -
- {#each assessmentRatingsForFlow as rating} - - {/each} -
-
- There are no physical flows to show, these may have been filtered. -
-
- - - \ No newline at end of file +
+
diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/FlowDetailFilters.svelte b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/FlowDetailFilters.svelte index 8112e6b0f3..b883328287 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/FlowDetailFilters.svelte +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/FlowDetailFilters.svelte @@ -7,8 +7,8 @@ import Icon from "../../../../../common/svelte/Icon.svelte"; import AssessmentFilters from "./AssessmentFilters.svelte"; import InboundOutboundFilters from "./InboundOutboundFilters.svelte"; import PhysicalFlowAttributeFilters from "./PhysicalFlowAttributeFilters.svelte"; -import DataTypeFilters from "./DataTypeFilters.svelte"; import FlowClassificationFilters from "./FlowClassificationFilters.svelte"; +import SourceTargetKindFilters from "./SourceTargetKindFilters.svelte"; export let dataTypes = []; export let assessmentFilters = []; @@ -60,15 +60,15 @@ $: directionFilter = _.find($filters, d => d.kind === FilterKinds.DIRECTION);
- Data Types - {#if _.some($filters, d => d.kind === FilterKinds.DATA_TYPE)} + Source / Target Types + {#if _.some($filters, d => d.kind === FilterKinds.NODE_KIND)} + title="Source and/or Target type filters have been applied"> {/if} - +
diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/SourceTargetKindFilters.svelte b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/SourceTargetKindFilters.svelte new file mode 100644 index 0000000000..80f7185d3f --- /dev/null +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/SourceTargetKindFilters.svelte @@ -0,0 +1,71 @@ + + +
+
+ Use the buttons to filter on the types of either the flow source or target. +
+ {#each FlowNodeTypes as kind} + + {/each} + +
+ + | + +
+
diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/filter-utils.js b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/filter-utils.js index 0b00910e42..4db7c968d6 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/filter-utils.js +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/filters/filter-utils.js @@ -7,7 +7,8 @@ export const FilterKinds = { ASSESSMENT: "ASSESSMENT", PHYSICAL_FLOW_ATTRIBUTE: "PHYSICAL_FLOW_ATTRIBUTE", SELECTED_LOGICAL: "SELECTED_LOGICAL", - FLOW_CLASSIFICATION: "FLOW_CLASSIFICATION" + FLOW_CLASSIFICATION: "FLOW_CLASSIFICATION", + NODE_KIND: "NODE_KIND" } export function getAssessmentFilters(flowView) { @@ -70,6 +71,10 @@ export function mkDirectionFilterId() { return "FLOW_DIRECTION"; } +export function mkNodeTypeFilterId() { + return "NODE_TYPE"; +} + export function mkAssessmentFilter(id, desiredRatings) { return { id, @@ -140,6 +145,18 @@ export function mkDirectionFilter(id, direction) { }; } +export function mkNodeTypeFilter(id, nodeTypes = []) { + return { + id, + kind: FilterKinds.NODE_KIND, + nodeTypes, + test: (r) => _.size(nodeTypes) === 3 + ? true + : _.includes(nodeTypes, r.logicalFlow.source.kind) + || _.includes(nodeTypes, r.logicalFlow.target.kind) + }; +} + export function mkClassificationFilter(id, desiredClassificationRatings = []) { return { id, diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-detail-utils.js b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-detail-utils.js index acc9ce3b81..64f5ca3c8a 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-detail-utils.js +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-detail-utils.js @@ -1,11 +1,24 @@ import _ from "lodash"; import {sameRef} from "../../../../common/entity-utils"; +import {cmp} from "../../../../common/sort-utils"; +import tippy from "tippy.js"; +import { + toCriticalityName, + toFrequencyKindName, toTransportKindName +} from "../../../../physical-flows/svelte/physical-flow-registration-utils"; +import {entity as EntityKind} from "../../../../common/services/enums/entity"; export const Directions = { INBOUND: "INBOUND", OUTBOUND: "OUTBOUND", ALL: "ALL" -} +}; + +export const FlowNodeTypes = [ + EntityKind.ACTOR, + EntityKind.APPLICATION, + EntityKind.END_USER_APPLICATION +]; function determineDirection(flow, parentEntityRef) { if (sameRef(flow.target, parentEntityRef)) { @@ -90,3 +103,275 @@ export function mkFlowDetails(flowView, parentEntityRef) { ]) .value(); } + +const sourceNameCol = { + id: "source_name", + name: "Source", + field: "logicalFlow", + sortable: true, + width: 150, + formatter: (row, cell, value, colDef, dataCtx) => value.source.name, + sortFn: (a, b) => cmp(a?.logicalFlow.source.name, b?.logicalFlow.source.name) +}; + +const sourceExtIdCol = { + id: "source_ext_id", + name: "Src Ext Id", + field: "logicalFlow", + sortable: true, + formatter: (row, cell, value, colDef, dataCtx) => value.source.externalId, + sortFn: (a, b) => cmp(a?.logicalFlow.source.externalId, b?.logicalFlow.source.externalId) +}; + +const targetNameCol = { + id: "target_name", + name: "Target", + field: "logicalFlow", + sortable: true, + width: 150, + formatter: (row, cell, value, colDef, dataCtx) => value.target.name, + sortFn: (a, b) => cmp(a?.logicalFlow.target.name, b?.logicalFlow.target.name) +}; + +const targetExtIdCol = { + id: "target_ext_id", + name: "Trg Ext Id", + field: "logicalFlow", + sortable: true, + formatter: (row, cell, value, colDef, dataCtx) => value.target.externalId, + sortFn: (a, b) => cmp(a?.logicalFlow.target.externalId, b?.logicalFlow.target.externalId) +}; + + +const physicalNameCol = { + id: "physical_name", + name: "Name", + sortable: true, + width: 170, + formatter: (row, cell, value, colDef, dataCtx) => dataCtx.physicalFlow.name || dataCtx.specification?.name || "" +}; + +const physicalExtIdCol = { + id: "physical_ext_id", + name: "Ext Id", + sortable: true, + field: "physicalFlow", + width: 170, + formatter: (row, cell, value, colDef, dataCtx) => value.externalId || "" +}; + + +function mkCriticalityCol(nestedEnums) { + return { + id: "criticality", + name: "Criticality", + field: "physicalFlow", + sortable: false, + width: 120, + formatter: (row, cell, value, colDef, dataCtx) => toCriticalityName(nestedEnums, value.criticality) + }; +} + + +function mkFrequencyCol(nestedEnums) { + return { + id: "frequency", + name: "Frequency", + field: "physicalFlow", + sortable: false, + width: 120, + formatter: (row, cell, value, colDef, dataCtx) => toFrequencyKindName(nestedEnums, value.frequency) + }; +} + + +function mkTransportCol(nestedEnums) { + return { + id: "transport", + name: "Transport", + field: "physicalFlow", + sortable: false, + width: 120, + formatter: (row, cell, value, colDef, dataCtx) => toTransportKindName(nestedEnums, value.transport) + }; +} + + +function mkDataTypesCol(fieldName) { + return { + id: "data_types", + name: "Data Types", + field: fieldName, + sortable: false, + width: 170, + formatter: (row, cell, value, colDef, dataCtx) => _ + .chain(value) + .map(d => d.decoratorEntity.name) + .sort() + .join(", ") + .value() + }; +} + + +const physicalFlowIndicatorCol = { + id: "phys_flow_indicator", + name: "", + field: "physicalCount", + sortable: true, + width: 16, + formatter: (row, cell, value, colDef, dataCtx) => { + switch (value) { + case 0: + return ""; + case 1: + return ``; + default: + return ``; + } + }, + sortFn: (a, b) => cmp(a?.logicalFlow.source.name, b?.logicalFlow.source.name) +}; + + +function mkLogicalFlowAssessmentColumns(defs) { + return _.map(defs, d => { + return { + id: "assessment_definition/" + d.id, + assessmentDefinitionId: d.id, + name: d.name, + field: "logicalFlowRatingsByDefId", + sortable: false, + width: 120, + formatter: (row, cell, value, colDef, dataCtx) => _ + .chain(value) + .get(colDef.assessmentDefinitionId, []) + .map(d => `${d.name}`) + .join(", ") + .value() + } + }); +} + + +function mkPhysicalFlowAssessmentColumns(defs) { + return _.map(defs, d => { + return { + id: "assessment_definition/" + d.id, + assessmentDefinitionId: d.id, + name: d.name, + sortable: false, + width: 120, + formatter: (row, cell, value, colDef, dataCtx) => { + const ratingsByDefId = _.merge(dataCtx.physicalSpecRatingsByDefId, dataCtx.physicalFlowRatingsByDefId); + return _ + .chain(ratingsByDefId) + .get(colDef.assessmentDefinitionId, []) + .map(d => `${d.name}`) + .join(", ") + .value() + } + } + }); +} + + +export function mkLogicalFlowTableColumns(defs = []) { + return _.concat( + [ + physicalFlowIndicatorCol, + sourceNameCol, + sourceExtIdCol, + targetNameCol, + targetExtIdCol, + mkDataTypesCol("dataTypesForLogicalFlow") + ], + mkLogicalFlowAssessmentColumns(defs)); +} + + +export function mkPhysicalFlowTableColumns(defs = [], nestedEnums = {}) { + return _.concat( + [ + sourceNameCol, + sourceExtIdCol, + targetNameCol, + targetExtIdCol, + physicalNameCol, + physicalExtIdCol, + mkDataTypesCol("dataTypesForSpecification"), + mkCriticalityCol(nestedEnums), + mkFrequencyCol(nestedEnums), + mkTransportCol(nestedEnums) + ], + mkPhysicalFlowAssessmentColumns(defs)); +} + + + + +function mkDataTypeTooltipTable(dataTypes, flowClassificationsByCode = null) { + const mkRatingIcon = (color) => color + ? `
+
` + : ""; + + const mkClassificationCell = (classification) => { + const name = _.get(classification, ["name"]); + const color = _.get(classification, ["color"], "None"); + return ` + + ${ mkRatingIcon(color)} + ${name ? name : 'none'} + `; + }; + + const rows = _ + .chain(dataTypes) + .map(d => ({name: d.decoratorEntity.name, outboundRatingCode: d.rating, inboundRatingCode: d.targetInboundRating})) + .map(d => ` + + ${d.name} + ${ mkClassificationCell(_.get(flowClassificationsByCode, [d.outboundRatingCode]))} + ${ mkClassificationCell(_.get(flowClassificationsByCode, [d.inboundRatingCode]))} + `) + .join("\n") + .value(); + + return ` + + + + + + + + + ${rows} +
Data TypeSource ClassificationConsumer Classification
` +} + + +export function showDataTypeTooltip(cellElem, dataTypes, flowClassificationsByCode = null) { + const tippyConfig = { + content: mkDataTypeTooltipTable( + dataTypes, + flowClassificationsByCode), + delay: [300, 100], + interactive: true, + allowHTML: true, + arrow: true, + appendTo: document.body, + theme: "light-border" + }; + tippy(cellElem, tippyConfig); +} diff --git a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-details-store.js b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-details-store.js index 4c9679d4fe..aea1e4d828 100644 --- a/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-details-store.js +++ b/waltz-ng/client/data-flow/components/svelte/flow-detail-tab/flow-details-store.js @@ -9,7 +9,13 @@ export function updateFilters(id, newFilter) { return filters.update(filtersList => { const withoutFilter = _.reject(filtersList, d => d.id === id); return _.concat(withoutFilter, newFilter); - }) + }); +} + +export function removeFilter(id) { + return filters.update(filtersList => { + return _.reject(filtersList, d => d.id === id); + }); } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolver.java b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolver.java index b57a119520..b0c2ba1752 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolver.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolver.java @@ -67,7 +67,7 @@ public FlowClassificationRuleResolver(FlowDirection direction, List groupAndThen( byVps, - byVp -> byVp.dataType().id(), + FlowClassificationRuleVantagePoint::dataTypeId, byDts -> groupAndThen( byDts, FlowClassificationRuleVantagePoint::subjectReference, @@ -149,15 +149,11 @@ private Optional determineDefaultRuleId(Map< * @return */ public static Optional getMostSpecificRanked(Collection vantagePoints) { - - Comparator comparator = Comparator - .comparingInt(FlowClassificationRuleVantagePoint::vantagePointRank) - .thenComparingInt(FlowClassificationRuleVantagePoint::dataTypeRank); - + List sorted = sort( + vantagePoints, + flowClassificationRuleVantagePointComparator); return head( - sort( - vantagePoints, - (x, y) -> comparator.compare(y, x))); //note the reversal of parameters because we want descending order + sorted); //note the reversal of parameters because we want descending order } } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleUtilities.java b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleUtilities.java index 8edb857b87..c55bbe8792 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleUtilities.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleUtilities.java @@ -15,25 +15,26 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.function.ToLongFunction; import java.util.stream.Collectors; import static org.jooq.lambda.tuple.Tuple.tuple; public class FlowClassificationRuleUtilities { - private static final Function kindComparator = d -> d.vantagePoint().kind().name(); - private static final Function vantageComparator = FlowClassificationRuleVantagePoint::vantagePointRank; - private static final Function dataTypeComparator = FlowClassificationRuleVantagePoint::dataTypeRank; - private static final Function vantagePointIdComparator = d -> d.vantagePoint().id(); - private static final Function dataTypeIdComparator = d -> d.dataType().id(); - private static final Function subjectIdComparator = d -> d.subjectReference().id(); + public static final Function kindComparator = d -> d.vantagePoint().kind().name(); + public static final Comparator vantageComparator = Comparator.comparingInt(FlowClassificationRuleVantagePoint::vantagePointRank).reversed(); + public static final Comparator dataTypeComparator = Comparator.comparingInt(FlowClassificationRuleVantagePoint::dataTypeRank).reversed(); + public static final ToLongFunction vantagePointIdComparator = d -> d.vantagePoint().id(); + public static final ToLongFunction dataTypeIdComparator = FlowClassificationRuleVantagePoint::dataTypeId; + public static final ToLongFunction subjectIdComparator = d -> d.subjectReference().id(); public static final Comparator flowClassificationRuleVantagePointComparator = Comparator .comparing(kindComparator) - .thenComparing(vantageComparator).reversed() - .thenComparing(dataTypeComparator).reversed() - .thenComparing(vantagePointIdComparator) - .thenComparing(dataTypeIdComparator) - .thenComparing(subjectIdComparator); + .thenComparing(vantageComparator) + .thenComparing(dataTypeComparator) + .thenComparingLong(vantagePointIdComparator) + .thenComparingLong(dataTypeIdComparator) + .thenComparingLong(subjectIdComparator); protected static Map> applyVantagePoints(FlowDirection direction, @@ -53,13 +54,13 @@ protected static Map> applyVantagePoints(FlowDi Set filteredRules = ruleVantagePoints .stream() - .filter(rvp -> ruleDataTypes.contains(rvp.dataType().id())) + .filter(rvp -> rvp.dataTypeId() == null || ruleDataTypes.contains(rvp.dataTypeId())) .collect(Collectors.toSet()); filteredRules .forEach(rvp -> { Set childOUs = ouHierarchy.findChildren(rvp.vantagePoint().id()); - Set childDTs = dtHierarchy.findChildren(rvp.dataType().id()); + Set childDTs = dtHierarchy.findChildren(rvp.dataTypeId()); population.forEach(p -> { Tuple2 currentRuleAndOutcome = lfdIdToRuleAndOutcomeMap.get(p.lfdId()); if (currentRuleAndOutcome != null && currentRuleAndOutcome.v2 == MatchOutcome.POSITIVE_MATCH) { @@ -93,7 +94,7 @@ private static Function4, Set, Set, FlowDataType, MatchOutcome> inboundMatcher = (rvp, childOUs, childDTs, p) -> { boolean subjectMatches = p.target().equals(rvp.subjectReference()); - boolean dtMatches = childDTs.contains(p.dtId()); + boolean dtMatches = rvp.dataTypeId() == null || childDTs.contains(p.dtId()); boolean dtAndOuMatches = dtMatches && checkScopeMatches(rvp, childOUs, p.source(), p.sourceOuId()); return determineOutcome(subjectMatches, dtAndOuMatches); }; @@ -101,7 +102,7 @@ private static Function4, Set, Set, FlowDataType, MatchOutcome> outboundMatcher = (rvp, childOUs, childDTs, p) -> { boolean subjectMatches = p.source().equals(rvp.subjectReference()); - boolean dtMatches = childDTs.contains(p.dtId()); + boolean dtMatches = rvp.dataTypeId() == null || childDTs.contains(p.dtId()); boolean dtAndOuMatches = dtMatches && checkScopeMatches(rvp, childOUs, p.target(), p.targetOuId()); return determineOutcome(subjectMatches, dtAndOuMatches); }; diff --git a/waltz-service/src/test/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolverTest.java b/waltz-service/src/test/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolverTest.java index d6ddaf28da..becace77d5 100644 --- a/waltz-service/src/test/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolverTest.java +++ b/waltz-service/src/test/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleResolverTest.java @@ -37,7 +37,6 @@ import static org.finos.waltz.service.flow_classification_rule.FlowClassificationRuleResolver.getMostSpecificRanked; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class FlowClassificationRuleResolverTest { @@ -65,7 +64,7 @@ public void whenResolveWithMissingVantagePointThenReturnsNoOpinion() { List vantagePoints = new ArrayList<>(); FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.NO_OPINION, rating.v1); } @@ -78,7 +77,7 @@ public void whenResolveWithExistingVantageButMissingDataTypeThenReturnsNoOpinion vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 10)) + .dataTypeId(10L) .dataTypeRank(1) .subjectReference(mkRef(EntityKind.APPLICATION, 200L)) .classificationCode(AuthoritativenessRatingValue.of("SECONDARY").value()) @@ -87,7 +86,7 @@ public void whenResolveWithExistingVantageButMissingDataTypeThenReturnsNoOpinion FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.NO_OPINION, rating.v1); } @@ -101,7 +100,7 @@ public void existingVantageAndDataTypeAndDifferentSourceThenDiscouraged() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(1) .subjectReference(mkRef(EntityKind.APPLICATION, 205L)) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -110,7 +109,7 @@ public void existingVantageAndDataTypeAndDifferentSourceThenDiscouraged() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.DISCOURAGED, rating.v1); } @@ -124,7 +123,7 @@ public void existingEntriesThenReturnsMostSpecificRating() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(2) .subjectReference(mkRef(EntityKind.APPLICATION, 205L)) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -135,7 +134,7 @@ public void existingEntriesThenReturnsMostSpecificRating() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(2) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(mkRef(EntityKind.APPLICATION, 200L)) .classificationCode(AuthoritativenessRatingValue.of("SECONDARY").value()) @@ -144,7 +143,7 @@ public void existingEntriesThenReturnsMostSpecificRating() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("SECONDARY"), rating.v1); @@ -157,7 +156,7 @@ public void getBestRankedIsCorrect() { ImmutableFlowClassificationRuleVantagePoint rank12 = ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(2) .subjectReference(mkRef(EntityKind.APPLICATION, 205L)) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -168,7 +167,8 @@ public void getBestRankedIsCorrect() { ImmutableFlowClassificationRuleVantagePoint rank11 = rank12.withDataTypeRank(1); - Optional bestRankedOrgUnit = getMostSpecificRanked(newArrayList(rank12, rank22)); + ArrayList orgUnitTestList = newArrayList(rank12, rank22); + Optional bestRankedOrgUnit = getMostSpecificRanked(orgUnitTestList); Optional bestRankedDataType = getMostSpecificRanked(newArrayList(rank12, rank11)); assertTrue(bestRankedOrgUnit.isPresent()); @@ -179,7 +179,6 @@ public void getBestRankedIsCorrect() { } - @Test public void getBestRankedWorksWithEmpty() { @@ -196,7 +195,7 @@ public void pointToPointRulesApplied() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(sourceApp) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -206,7 +205,7 @@ public void pointToPointRulesApplied() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(targetApp) .vantagePointRank(0) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(sourceApp) .classificationCode(AuthoritativenessRatingValue.of("SECONDARY").value()) @@ -215,10 +214,10 @@ public void pointToPointRulesApplied() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("SECONDARY"), rating.v1); - assertEquals(2, rating.v2.get()); + assertEquals(2, rating.v2.map(FlowClassificationRuleVantagePoint::ruleId).orElse(null)); } @@ -230,7 +229,7 @@ public void worksForInboundRules() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(sourceApp) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -239,10 +238,10 @@ public void worksForInboundRules() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.INBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("PRIMARY"), rating.v1); - assertEquals(1, rating.v2.get()); + assertEquals(1, rating.v2.map(FlowClassificationRuleVantagePoint::ruleId).orElse(null)); } @@ -260,7 +259,7 @@ public void inboundRulesReturnNoOpinionWhenOutOfVantageScope() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(sourceApp) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -269,10 +268,10 @@ public void inboundRulesReturnNoOpinionWhenOutOfVantageScope() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.INBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(differentVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(differentVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("NO_OPINION"), rating.v1); - assertNull(rating.v2.orElse(null)); + assertFalse(rating.v2.isPresent()); } @Test @@ -288,7 +287,7 @@ public void inboundRuleHandleActorsSource() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(1) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(actor) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -297,10 +296,10 @@ public void inboundRuleHandleActorsSource() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.INBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, sourceApp, actor, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, sourceApp, actor, 20L); assertEquals(AuthoritativenessRatingValue.of("PRIMARY"), rating.v1); - assertEquals(1, rating.v2.get()); + assertEquals(1, rating.v2.map(FlowClassificationRuleVantagePoint::ruleId).orElse(null)); } @@ -317,7 +316,7 @@ public void inboundRuleHandleActorsScope() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(actor) .vantagePointRank(0) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(sourceApp) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -326,10 +325,10 @@ public void inboundRuleHandleActorsScope() { FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.INBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(null, actor, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(null, actor, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("PRIMARY"), rating.v1); - assertEquals(1, rating.v2.get()); + assertEquals(1, rating.v2.map(FlowClassificationRuleVantagePoint::ruleId).orElse(null)); } @@ -357,7 +356,7 @@ public void whenMultipleDiscourageRulesConflictTheSmallestIdWins() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(3) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(app1) .classificationCode(AuthoritativenessRatingValue.of("PRIMARY").value()) @@ -367,7 +366,7 @@ public void whenMultipleDiscourageRulesConflictTheSmallestIdWins() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(3) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(app2) .classificationCode(AuthoritativenessRatingValue.of("SECONDARY").value()) @@ -377,7 +376,7 @@ public void whenMultipleDiscourageRulesConflictTheSmallestIdWins() { vantagePoints.add(ImmutableFlowClassificationRuleVantagePoint.builder() .vantagePoint(ouVantagePoint) .vantagePointRank(3) - .dataType(mkRef(EntityKind.DATA_TYPE, 20)) + .dataTypeId(20L) .dataTypeRank(3) .subjectReference(app3) .classificationCode(AuthoritativenessRatingValue.of("TERTIARY").value()) @@ -387,10 +386,10 @@ public void whenMultipleDiscourageRulesConflictTheSmallestIdWins() { // All the above have the same FlowClassificationRuleResolver flowClassificationRuleResolver = new FlowClassificationRuleResolver(FlowDirection.OUTBOUND, vantagePoints); - Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); + Tuple2> rating = flowClassificationRuleResolver.resolve(ouVantagePoint, targetApp, sourceApp, 20L); assertEquals(AuthoritativenessRatingValue.of("DISCOURAGED"), rating.v1); - assertEquals(1, rating.v2.orElse(null)); + assertEquals(1, rating.v2.map(FlowClassificationRuleVantagePoint::ruleId).orElse(null)); } diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationExtractor.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationExtractor.java index 122007df53..30a44cd3da 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationExtractor.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationExtractor.java @@ -39,7 +39,9 @@ import static spark.Spark.post; +// TODO: remove in 1.61 if not needed @Service +@Deprecated public class ApplicationExtractor extends DirectQueryBasedDataExtractor { private static final Logger LOG = LoggerFactory.getLogger(ApplicationExtractor.class); @@ -54,7 +56,7 @@ public ApplicationExtractor(DSLContext dsl) { @Override public void register() { - post(WebUtilities.mkPath("data-extract", "application", "by-selector"), (request, response) -> { + post(WebUtilities.mkPath("data-extract", "application", "by-selector-old"), (request, response) -> { IdSelectionOptions idSelectionOptions = WebUtilities.readIdSelectionOptionsFromBody(request); Select> idSelector = applicationIdSelectorFactory.apply(idSelectionOptions); Condition condition = diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationViewExtractor.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationViewExtractor.java new file mode 100644 index 0000000000..613c5f6311 --- /dev/null +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/extracts/ApplicationViewExtractor.java @@ -0,0 +1,203 @@ +/* + * Waltz - Enterprise Architecture + * Copyright (C) 2016, 2017, 2018, 2019 Waltz open source project + * See README.md for more information + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific + * + */ + +package org.finos.waltz.web.endpoints.extracts; + +import org.finos.waltz.common.ListUtilities; +import org.finos.waltz.common.MapUtilities; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.IdProvider; +import org.finos.waltz.model.IdSelectionOptions; +import org.finos.waltz.model.NameProvider; +import org.finos.waltz.model.application.ApplicationsView; +import org.finos.waltz.model.measurable.Measurable; +import org.finos.waltz.model.measurable_rating.MeasurableRating; +import org.finos.waltz.model.rating.RatingSchemeItem; +import org.finos.waltz.service.application.ApplicationViewService; +import org.finos.waltz.service.orgunit.OrganisationalUnitService; +import org.finos.waltz.web.WebUtilities; +import org.jooq.DSLContext; +import org.jooq.lambda.tuple.Tuple2; +import org.jooq.lambda.tuple.Tuple3; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; +import static org.finos.waltz.common.MapUtilities.groupBy; +import static org.finos.waltz.model.utils.IdUtilities.indexByOptionalId; +import static org.jooq.lambda.tuple.Tuple.tuple; +import static spark.Spark.post; + + +@Service +public class ApplicationViewExtractor extends CustomDataExtractor { + + private final ApplicationViewService applicationViewService; + private final OrganisationalUnitService orgUnitService; + + private final DSLContext dsl; + + @Autowired + public ApplicationViewExtractor(DSLContext dsl, + ApplicationViewService applicationViewService, + OrganisationalUnitService orgUnitService) { + this.dsl = dsl; + this.applicationViewService = applicationViewService; + this.orgUnitService = orgUnitService; + } + + + @Override + public void register() { + post(WebUtilities.mkPath("data-extract", "application", "by-selector"), (request, response) -> { + IdSelectionOptions options = WebUtilities.readIdSelectionOptionsFromBody(request); + + return writeReportResults( + response, + prepareFlows( + options, + parseExtractFormat(request), + "applications")); + }); + } + + + private Tuple3 prepareFlows(IdSelectionOptions options, + ExtractFormat format, + String reportName) throws IOException { + + ApplicationsView view = applicationViewService.getViewBySelector(options); + + Map, String> ouNameById = MapUtilities.indexBy( + orgUnitService.findAll(), + IdProvider::id, + NameProvider::name); + + List> reportRows = prepareReportRows(view, ouNameById); + + List staticHeaders = ListUtilities.asList( + "Waltz Id", + "Name", + "Asset Code", + "Application Kind", + "Org Unit", + "Lifecycle Phase"); + + List categoryHeaders = view + .primaryRatings() + .measurableCategories() + .stream() + .map(d -> String.format("Primary %s", d.name())) + .sorted() + .collect(toList()); + + List assessmentHeaders = view + .primaryAssessments() + .assessmentDefinitions() + .stream() + .map(NameProvider::name) + .sorted() + .collect(toList()); + + return formatReport( + format, + reportName, + reportRows, + ListUtilities.concat(staticHeaders, categoryHeaders, assessmentHeaders)); + } + + + private List> prepareReportRows(ApplicationsView viewData, + Map, String> ouNameById) { + + Map measurablesById = indexByOptionalId(viewData.primaryRatings().measurables()); + Map>> entityToRatingsAndMeasurables = groupBy( + viewData.primaryRatings().measurableRatings(), + MeasurableRating::entityReference, + mr -> tuple(mr, measurablesById.get(mr.measurableId()))); + + Map ratingSchemeItemsById = viewData.primaryAssessments().ratingSchemeItemsById(); + Map, Collection> assessmentsByEntityAndDefId = groupBy( + viewData.primaryAssessments().assessmentRatings(), + r -> tuple(r.entityReference(), r.assessmentDefinitionId()), + r -> ratingSchemeItemsById.get(r.ratingId())); + + return viewData + .applications() + .stream() + .map(app -> { + ArrayList reportRow = new ArrayList<>(); + + String ouName = ouNameById.getOrDefault(Optional.of(app.organisationalUnitId()), "?"); + + reportRow.add(app.id().get()); + reportRow.add(app.name()); + reportRow.add(app.assetCode()); + reportRow.add(app.kind().prettyName()); + reportRow.add(ouName); + reportRow.add(app.lifecyclePhase()); + + Collection> associatedPrimaryRatings = entityToRatingsAndMeasurables.getOrDefault( + app.entityReference(), + Collections.emptySet()); + + viewData.primaryRatings() + .measurableCategories() + .stream() + .sorted(Comparator.comparing(NameProvider::name)) + .forEach(category -> { + String cellValue = associatedPrimaryRatings + .stream() + .filter(t -> Optional.of(t.v2.categoryId()).equals(category.id())) + .findFirst() + .map(t -> t.v2.name()) + .orElse(null); + reportRow.add(cellValue); + }); + + viewData.primaryAssessments() + .assessmentDefinitions() + .stream() + .sorted(Comparator.comparing(NameProvider::name)) + .forEach(def -> { + Collection ratings = assessmentsByEntityAndDefId.getOrDefault( + tuple(app.entityReference(), def.id().get()), + Collections.emptySet()); + + reportRow.add(ratings + .stream() + .map(NameProvider::name) + .sorted() + .collect(Collectors.joining(", "))); + }); + + return reportRow; + }) + .collect(toList()); + } +}