UNPKG

@itwin/presentation-hierarchies

Version:

A package for creating hierarchies based on data in iTwin.js iModels.

296 lines 15 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.ECSQL_COLUMN_NAME_FilterClassName = exports.ECSQL_COLUMN_NAME_FilterECInstanceId = exports.FilteringHierarchyDefinition = void 0; exports.applyECInstanceIdsFilter = applyECInstanceIdsFilter; exports.applyECInstanceIdsSelector = applyECInstanceIdsSelector; const rxjs_1 = require("rxjs"); const HierarchyFiltering_js_1 = require("../HierarchyFiltering.js"); const HierarchyNodeIdentifier_js_1 = require("../HierarchyNodeIdentifier.js"); const HierarchyNodeKey_js_1 = require("../HierarchyNodeKey.js"); const IModelHierarchyDefinition_js_1 = require("../imodel/IModelHierarchyDefinition.js"); const IModelHierarchyNode_js_1 = require("../imodel/IModelHierarchyNode.js"); const NodeSelectQueryFactory_js_1 = require("../imodel/NodeSelectQueryFactory.js"); const TreeNodesReader_js_1 = require("../imodel/TreeNodesReader.js"); const Partition_js_1 = require("../internal/operators/Partition.js"); /** @internal */ class FilteringHierarchyDefinition { _imodelAccess; _source; _nodeIdentifierPaths; _nodesParser; constructor(props) { this._imodelAccess = props.imodelAccess; this._source = props.source; this._nodeIdentifierPaths = props.nodeIdentifierPaths; this._nodesParser = props.nodesParser ?? this._source.parseNode ?? ((row) => (0, rxjs_1.of)((0, TreeNodesReader_js_1.defaultNodesParser)(row))); } get preProcessNode() { return (node) => { return (this._source.preProcessNode ? this._source.preProcessNode(node) : (0, rxjs_1.of)(node)).pipe((0, rxjs_1.filter)((processedNode) => { if (processedNode.processingParams?.hideInHierarchy && processedNode.filtering?.isFilterTarget && !processedNode.filtering.hasFilterTargetAncestor) { // we want to hide target nodes if they have `hideInHierarchy` param, but only if they're not under another filter target return false; } return true; })); }; } get postProcessNode() { return (node) => { return (this._source.postProcessNode ? this._source.postProcessNode(node) : (0, rxjs_1.of)(node)).pipe((0, rxjs_1.map)((processedNode) => { // instance nodes get the auto-expand flag in `parseNode`, but grouping ones need to be handled during post-processing if (IModelHierarchyNode_js_1.ProcessedHierarchyNode.isGroupingNode(node) && shouldExpandGroupingNode(node)) { Object.assign(processedNode, { autoExpand: true }); } return processedNode; })); }; } get parseNode() { return (row, parentNode) => { return this._nodesParser(row).pipe((0, rxjs_1.mergeMap)((parsedNode) => { if (!row[exports.ECSQL_COLUMN_NAME_FilterECInstanceId] || !row[exports.ECSQL_COLUMN_NAME_FilterClassName]) { return (0, rxjs_1.of)(parsedNode); } const rowInstanceKey = { className: row[exports.ECSQL_COLUMN_NAME_FilterClassName], id: row[exports.ECSQL_COLUMN_NAME_FilterECInstanceId], }; const filteringHelper = (0, HierarchyFiltering_js_1.createHierarchyFilteringHelper)(this._nodeIdentifierPaths, parentNode); const nodeExtraPropsPossiblyPromise = filteringHelper.createChildNodePropsAsync({ pathMatcher: (identifier) => { if (identifier.id !== rowInstanceKey.id) { return false; } if (!HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.isInstanceNodeIdentifier(identifier)) { return false; } if (identifier.imodelKey && identifier.imodelKey !== this._imodelAccess.imodelKey) { return false; } if (identifier.className === rowInstanceKey.className) { return true; } return Promise.all([ this._imodelAccess.classDerivesFrom(identifier.className, rowInstanceKey.className), this._imodelAccess.classDerivesFrom(rowInstanceKey.className, identifier.className), ]).then(([isDerivedFrom, isDerivedTo]) => isDerivedFrom || isDerivedTo); }, }); return (nodeExtraPropsPossiblyPromise instanceof Promise ? (0, rxjs_1.from)(nodeExtraPropsPossiblyPromise) : (0, rxjs_1.of)(nodeExtraPropsPossiblyPromise)).pipe((0, rxjs_1.map)((nodeExtraProps) => { if (nodeExtraProps?.autoExpand) { parsedNode.autoExpand = true; } if (nodeExtraProps?.filtering) { parsedNode.filtering = nodeExtraProps.filtering; } return parsedNode; })); })); }; } defineHierarchyLevel(props) { const sourceDefinitions = this._source.defineHierarchyLevel(props); const filteringHelper = (0, HierarchyFiltering_js_1.createHierarchyFilteringHelper)(this._nodeIdentifierPaths, props.parentNode); const childNodeFilteringIdentifiers = filteringHelper.getChildNodeFilteringIdentifiers(); if (!childNodeFilteringIdentifiers) { return sourceDefinitions; } const [genericNodeDefinitions, instanceNodeDefinitions] = (0, Partition_js_1.partition)(sourceDefinitions.pipe((0, rxjs_1.mergeAll)()), IModelHierarchyDefinition_js_1.HierarchyNodesDefinition.isGenericNode); return (0, rxjs_1.merge)(genericNodeDefinitions.pipe((0, rxjs_1.map)((definition) => { if (filteringHelper.hasFilterTargetAncestor || childNodeFilteringIdentifiers.some((identifier) => HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.isGenericNodeIdentifier(identifier) && (!identifier.source || identifier.source === this._imodelAccess.imodelKey) && identifier.id === definition.node.key)) { return { ...definition, node: { ...definition.node, ...filteringHelper.createChildNodeProps({ pathMatcher: ({ id }) => id === definition.node.key }), }, }; } return undefined; }), (0, rxjs_1.filter)((def) => !!def)), instanceNodeDefinitions.pipe((0, rxjs_1.mergeMap)((definition) => { if (filteringHelper.hasFilterTargetAncestor) { // if we have a filter target ancestor, we don't need to filter the definitions - we use all of them return (0, rxjs_1.of)(applyECInstanceIdsSelector(definition)); } return (0, rxjs_1.defer)(async () => { const imodelAccess = this._imodelAccess; const matches = new Map(); async function getEntries(x) { let entries = matches.get(x.id); if (!entries) { entries = []; matches.set(x.id, entries); } // make sure that we don't have path identifiers that only differ by class name, where // class names are in the same class hierarchy, e.g.: // [{ className: "bis.Element", id: "0x1" }, { className: "bis.Subject", id: "0x1" }] // are actually the same instance, so we only need to keep one of them, or otherwise the query will // duplicate it for (const entry of entries) { // TODO: MISSING_COVERAGE /* v8 ignore if -- @preserve */ if (entry.className === x.className || (await imodelAccess.classDerivesFrom(entry.className, x.className)) || (await imodelAccess.classDerivesFrom(x.className, entry.className))) { return undefined; } } return entries; } for (const id of childNodeFilteringIdentifiers) { // take only identifiers that match the definition by type, source and class if (!HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.isInstanceNodeIdentifier(id)) { continue; } if (id.imodelKey && id.imodelKey !== imodelAccess.imodelKey) { continue; } if (id.className !== definition.fullClassName && !(await Promise.all([ imodelAccess.classDerivesFrom(id.className, definition.fullClassName), imodelAccess.classDerivesFrom(definition.fullClassName, id.className), ]).then(([isDerivedFrom, isDerivedTo]) => isDerivedFrom || isDerivedTo))) { continue; } const entriesTargetForPathIdentifier = await getEntries(id); if (entriesTargetForPathIdentifier) { entriesTargetForPathIdentifier.push(id); } } return Array.from(matches.values()).flat(); }).pipe( // only take definitions that have matching path identifiers (0, rxjs_1.filter)((pathIdentifiers) => pathIdentifiers.length > 0), // for each definition that we're going to use, apply query-level filter (0, rxjs_1.map)((pathIdentifiers) => applyECInstanceIdsFilter(definition, pathIdentifiers))); }))).pipe((0, rxjs_1.toArray)()); } } exports.FilteringHierarchyDefinition = FilteringHierarchyDefinition; /** @internal */ // eslint-disable-next-line @typescript-eslint/naming-convention exports.ECSQL_COLUMN_NAME_FilterECInstanceId = "FilterECInstanceId"; /** @internal */ // eslint-disable-next-line @typescript-eslint/naming-convention exports.ECSQL_COLUMN_NAME_FilterClassName = "FilterClassName"; function getClassECInstanceIds(filteredInstanceKeys) { const classNameECInstanceIds = new Map(); for (const key of filteredInstanceKeys) { const entry = classNameECInstanceIds.get(key.className); if (entry === undefined) { classNameECInstanceIds.set(key.className, [key.id]); continue; } entry.push(key.id); } return classNameECInstanceIds; } /** @internal */ function applyECInstanceIdsFilter(def, filteredInstanceKeys) { const instanceIdsByClass = getClassECInstanceIds(filteredInstanceKeys); return { ...def, query: { ...def.query, ctes: [ ...(def.query.ctes ?? []), `FilteringInfo(ECInstanceId, FilterClassName) AS ( ${Array.from(instanceIdsByClass) .map(([className, ecInstanceIds]) => ` SELECT ECInstanceId, '${className}' AS FilterClassName FROM ${className} WHERE ECInstanceId IN (${ecInstanceIds.join(", ")}) `) .join(" UNION ALL ")} )`, ], ecsql: ` SELECT [q].*, IdToHex([f].[ECInstanceId]) AS [${exports.ECSQL_COLUMN_NAME_FilterECInstanceId}], [f].[FilterClassName] AS [${exports.ECSQL_COLUMN_NAME_FilterClassName}] FROM ( ${def.query.ecsql} ) [q] JOIN FilteringInfo [f] ON [f].[ECInstanceId] = [q].[ECInstanceId] `, }, }; } /** @internal */ function applyECInstanceIdsSelector(def) { return { ...def, query: { ...def.query, ecsql: ` SELECT [q].*, IdToHex([q].[ECInstanceId]) AS [${exports.ECSQL_COLUMN_NAME_FilterECInstanceId}], [q].[${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.FullClassName}] AS [${exports.ECSQL_COLUMN_NAME_FilterClassName}] FROM (${def.query.ecsql}) [q] `, }, }; } function shouldExpandGroupingNode(node) { const numberOfNonGroupingParentNodes = node.parentKeys.filter((key) => !HierarchyNodeKey_js_1.HierarchyNodeKey.isGrouping(key)).length; for (const child of node.children) { if (!child.filtering) { continue; } if (child.filtering.isFilterTarget && getAutoExpandAsTrueFalse(child.filtering.filterTargetOptions?.autoExpand, numberOfNonGroupingParentNodes, node.parentKeys.length)) { return true; } if (!child.filtering.filteredChildrenIdentifierPaths) { continue; } for (const path of child.filtering.filteredChildrenIdentifierPaths) { if ("path" in path && getAutoExpandAsTrueFalse(path.options?.autoExpand, numberOfNonGroupingParentNodes, node.parentKeys.length)) { return true; } } } return false; } function getAutoExpandAsTrueFalse(autoExpand, numberOfNonGroupingParentNodes, numberOfParentNodes) { if (!autoExpand) { return false; } if (autoExpand === true) { return true; } // If grouping node's child has `autoExpandUntil` flag, // auto-expand the grouping node only if it's depth is lower than that of the grouping node in associated with the target. const nodeDepth = "key" in autoExpand || "depthInHierarchy" in autoExpand || // eslint-disable-next-line @typescript-eslint/no-deprecated (!("depthInPath" in autoExpand) && autoExpand.includeGroupingNodes) ? numberOfParentNodes : numberOfNonGroupingParentNodes; const filterTargetDepth = "depth" in autoExpand ? // eslint-disable-next-line @typescript-eslint/no-deprecated autoExpand.depth : "depthInPath" in autoExpand ? autoExpand.depthInPath : autoExpand.depthInHierarchy; if (nodeDepth < filterTargetDepth) { return true; } return false; } //# sourceMappingURL=FilteringHierarchyDefinition.js.map