@itwin/presentation-hierarchies
Version:
A package for creating hierarchies based on data in iTwin.js iModels.
172 lines • 9.58 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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