UNPKG

@itwin/presentation-hierarchies

Version:

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

172 lines 9.58 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { concat, concatAll, delay, EMPTY, expand, finalize, from, last, map, merge, mergeMap, of, reduce, tap, toArray, } from "rxjs"; import { assert, StopWatch } from "@itwin/core-bentley"; import { HierarchyNode } from "../../HierarchyNode.js"; import { LOGGING_NAMESPACE_PERFORMANCE as BASE_LOGGING_NAMESPACE_PERFORMANCE, LOGGING_NAMESPACE_PERFORMANCE_INTERNAL as BASE_LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, createNodeIdentifierForLogging, createOperatorLoggingNamespace, LOGGING_NAMESPACE_INTERNAL, } from "../../internal/Common.js"; import { doLog, log } from "../../internal/LoggingUtils.js"; import { releaseMainThreadOnItemsCount } from "../../internal/operators/ReleaseMainThread.js"; import { tapOnce } from "../../internal/operators/TapOnce.js"; import { assignAutoExpand } from "./grouping/AutoExpand.js"; import { createBaseClassGroupingHandlers } from "./grouping/BaseClassGrouping.js"; import { createClassGroups } from "./grouping/ClassGrouping.js"; import { applyGroupHidingParams } from "./grouping/GroupHiding.js"; import { createLabelGroups } from "./grouping/LabelGrouping.js"; import { createPropertiesGroupingHandlers } from "./grouping/PropertiesGrouping.js"; const OPERATOR_NAME = "Grouping"; /** @internal */ export const LOGGING_NAMESPACE = createOperatorLoggingNamespace(OPERATOR_NAME, LOGGING_NAMESPACE_INTERNAL); const LOGGING_NAMESPACE_PERFORMANCE = createOperatorLoggingNamespace(OPERATOR_NAME, BASE_LOGGING_NAMESPACE_PERFORMANCE); const LOGGING_NAMESPACE_PERFORMANCE_INTERNAL = createOperatorLoggingNamespace(OPERATOR_NAME, BASE_LOGGING_NAMESPACE_PERFORMANCE_INTERNAL); /** @internal */ export function createGroupingOperator(imodelAccess, parentNode, valueFormatter, localizedStrings, onNodesGrouped, onGroupingNodeCreated, groupingHandlers) { return function (nodes) { return nodes.pipe( /* v8 ignore next -- @preserve */ log({ category: LOGGING_NAMESPACE, message: (n) => `in: ${createNodeIdentifierForLogging(n)}` }), tapOnce(() => { /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE, message: () => `Starting grouping (parent: ${createNodeIdentifierForLogging(parentNode)})`, }); }), reduce((resolvedNodes, node) => { if (HierarchyNode.isInstancesNode(node)) { resolvedNodes.instanceNodes.push(node); } else { resolvedNodes.restNodes.push(node); } return resolvedNodes; }, { instanceNodes: [], restNodes: [] }), tap(({ instanceNodes, restNodes }) => { /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Nodes partitioned. Got ${instanceNodes.length} instance nodes and ${restNodes.length} rest nodes.`, }); }), mergeMap((res) => { const out = of(res); const totalNodes = res.instanceNodes.length + res.restNodes.length; return totalNodes <= 1000 ? out : /* v8 ignore next */ out.pipe(delay(0)); }), mergeMap(({ instanceNodes, restNodes }) => { const timer = new StopWatch(undefined, true); const groupingHandlersObs = groupingHandlers ? from(groupingHandlers) : createGroupingHandlers(imodelAccess, parentNode, instanceNodes, valueFormatter, localizedStrings); return merge(groupingHandlersObs.pipe(toArray(), mergeMap((createdGroupingHandlers) => groupInstanceNodes(instanceNodes, restNodes.length, createdGroupingHandlers, parentNode, onNodesGrouped, onGroupingNodeCreated)), finalize(() => { /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE, message: () => `Grouping ${instanceNodes.length} nodes took ${timer.elapsedSeconds.toFixed(3)} s`, }); })), from(restNodes)); }), releaseMainThreadOnItemsCount(500), /* v8 ignore next -- @preserve */ log({ category: LOGGING_NAMESPACE, message: (n) => `out: ${createNodeIdentifierForLogging(n)}` })); }; } function groupInstanceNodes(nodes, extraSiblings, groupingHandlers, parentNode, onNodesGrouped, onGroupingNodeCreated) { if (groupingHandlers.length === 0) { return from(nodes); } return of({ handlerIndex: 0 }).pipe(expand(({ handlerIndex, result: curr }) => { if (handlerIndex >= groupingHandlers.length) { return EMPTY; } const timer = new StopWatch(undefined, true); const currentHandler = groupingHandlers[handlerIndex]; return from(currentHandler(curr?.ungrouped ?? nodes, curr?.grouped ?? [])).pipe( /* v8 ignore next -- @preserve */ log({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Grouping handler ${handlerIndex} exclusively took ${timer.elapsedSeconds.toFixed(3)} s.`, }), tap((result) => { onNodesGrouped?.(result, currentHandler); }), mergeMap((result) => { if (result.grouped.length === 0) { return of({ handlerIndex: handlerIndex + 1, result: { ...result, grouped: curr?.grouped ?? [] } }); } const groupingPostProcessingTimer = new StopWatch(undefined, true); return of(result).pipe(map((r) => applyGroupHidingParams(r, extraSiblings)), map((r) => assignAutoExpand(r)), map((r) => ({ handlerIndex: handlerIndex + 1, result: { ...r, grouped: mergeInPlace(curr?.grouped, r.grouped) }, })), /* v8 ignore next -- @preserve */ log({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Post-processing grouping handler ${handlerIndex} exclusively took ${groupingPostProcessingTimer.elapsedSeconds.toFixed(3)} s.`, }), delay(0)); }), /* v8 ignore next -- @preserve */ log({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Total time for grouping handler ${handlerIndex}: ${timer.elapsedSeconds.toFixed(3)} s.`, })); }), last(), mergeMap(({ result }) => { result.grouped.forEach((groupingNode) => { onGroupingNodeCreated?.(groupingNode); if (!parentNode) { return; } if (HierarchyNode.isGroupingNode(parentNode)) { groupingNode.nonGroupingAncestor = parentNode.nonGroupingAncestor; return; } // not sure why type checker doesn't pick this up assert(HierarchyNode.isGeneric(parentNode) || HierarchyNode.isInstancesNode(parentNode)); groupingNode.nonGroupingAncestor = parentNode; }); return mergeInPlace(result.grouped, result.ungrouped); })); } function mergeInPlace(target, source) { if (!target || target.length === 0) { return source; } for (const item of source) { target.push(item); } return target; } function createGroupingHandlers(imodelAccess, parentNode, processedInstanceNodes, valueFormatter, localizedStrings) { const timer = new StopWatch(); const groupingLevel = getNodeGroupingLevel(parentNode); return concat(groupingLevel <= GroupingLevel.Class ? concat(from(createBaseClassGroupingHandlers(imodelAccess, parentNode, processedInstanceNodes)).pipe(concatAll()), of(async (allNodes) => createClassGroups(imodelAccess, parentNode, allNodes))) : EMPTY, groupingLevel <= GroupingLevel.Property ? from(createPropertiesGroupingHandlers(imodelAccess, parentNode, processedInstanceNodes, valueFormatter, localizedStrings)).pipe(concatAll()) : EMPTY, groupingLevel < GroupingLevel.Label ? of(async (allNodes) => createLabelGroups(allNodes)) : EMPTY).pipe(tap({ subscribe: () => { /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Start creating grouping handlers` }); timer.start(); }, }), finalize(() => { /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE_INTERNAL, message: () => `Creating grouping handlers took ${timer.elapsedSeconds.toFixed(3)} s`, }); })); } function getNodeGroupingLevel(node) { if (node && HierarchyNode.isClassGroupingNode(node)) { return GroupingLevel.Class; } if (node && HierarchyNode.isPropertyGroupingNode(node)) { return GroupingLevel.Property; } if (node && HierarchyNode.isLabelGroupingNode(node)) { return GroupingLevel.Label; } return GroupingLevel.None; } var GroupingLevel; (function (GroupingLevel) { GroupingLevel[GroupingLevel["None"] = 0] = "None"; GroupingLevel[GroupingLevel["Class"] = 2] = "Class"; GroupingLevel[GroupingLevel["Property"] = 3] = "Property"; GroupingLevel[GroupingLevel["Label"] = 4] = "Label"; })(GroupingLevel || (GroupingLevel = {})); //# sourceMappingURL=Grouping.js.map