@itwin/presentation-hierarchies
Version:
A package for creating hierarchies based on data in iTwin.js iModels.
449 lines • 26.7 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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.createIModelHierarchyProvider = createIModelHierarchyProvider;
require("../internal/DisposePolyfill.js");
const rxjs_1 = require("rxjs");
const core_bentley_1 = require("@itwin/core-bentley");
const presentation_shared_1 = require("@itwin/presentation-shared");
const HierarchyErrors_js_1 = require("../HierarchyErrors.js");
const HierarchyNode_js_1 = require("../HierarchyNode.js");
const HierarchyNodeKey_js_1 = require("../HierarchyNodeKey.js");
const Common_js_1 = require("../internal/Common.js");
const EachValueFrom_js_1 = require("../internal/EachValueFrom.js");
const LoggingUtils_js_1 = require("../internal/LoggingUtils.js");
const Partition_js_1 = require("../internal/operators/Partition.js");
const ReduceToMergeMap_js_1 = require("../internal/operators/ReduceToMergeMap.js");
const ShareReplayWithErrors_js_1 = require("../internal/operators/ShareReplayWithErrors.js");
const Sorting_js_1 = require("../internal/operators/Sorting.js");
const RxjsHierarchyDefinition_js_1 = require("../internal/RxjsHierarchyDefinition.js");
const SubscriptionScheduler_js_1 = require("../internal/SubscriptionScheduler.js");
const FilteringHierarchyDefinition_js_1 = require("./FilteringHierarchyDefinition.js");
const HierarchyCache_js_1 = require("./HierarchyCache.js");
const IModelHierarchyDefinition_js_1 = require("./IModelHierarchyDefinition.js");
const NodeSelectQueryFactory_js_1 = require("./NodeSelectQueryFactory.js");
const DetermineChildren_js_1 = require("./operators/DetermineChildren.js");
const Grouping_js_1 = require("./operators/Grouping.js");
const HideIfNoChildren_js_1 = require("./operators/HideIfNoChildren.js");
const HideNodesInHierarchy_js_1 = require("./operators/HideNodesInHierarchy.js");
const TreeNodesReader_js_1 = require("./TreeNodesReader.js");
const LOGGING_NAMESPACE = `${Common_js_1.LOGGING_NAMESPACE}.IModelHierarchyProvider`;
const LOGGING_NAMESPACE_INTERNAL = `${Common_js_1.LOGGING_NAMESPACE_INTERNAL}.IModelHierarchyProvider`;
const LOGGING_NAMESPACE_PERFORMANCE = `${Common_js_1.LOGGING_NAMESPACE_PERFORMANCE}.IModelHierarchyProvider`;
const DEFAULT_QUERY_CONCURRENCY = 10;
const DEFAULT_QUERY_CACHE_SIZE = 1;
/**
* Creates an instance of `HierarchyProvider` that creates a hierarchy based on given iModel and
* a hierarchy definition, which defines each hierarchy level through ECSQL queries.
*
* @public
*/
function createIModelHierarchyProvider(props) {
return new IModelHierarchyProviderImpl(props);
}
class IModelHierarchyProviderImpl {
_imodelAccess;
_hierarchyChanged;
_valuesFormatter;
_sourceHierarchyDefinition;
_activeHierarchyDefinition;
_localizedStrings;
_queryScheduler;
_nodesCache;
_unsubscribe;
_dispose = new rxjs_1.Subject();
constructor(props) {
this._imodelAccess = props.imodelAccess;
this._hierarchyChanged = new core_bentley_1.BeEvent();
this._activeHierarchyDefinition = this._sourceHierarchyDefinition = (0, RxjsHierarchyDefinition_js_1.getRxjsHierarchyDefinition)(props.hierarchyDefinition);
this._valuesFormatter = props?.formatter ?? (0, presentation_shared_1.createDefaultValueFormatter)();
this._localizedStrings = { other: "Other", unspecified: "Not specified", ...props?.localizedStrings };
this._queryScheduler = new SubscriptionScheduler_js_1.SubscriptionScheduler(props.queryConcurrency ?? DEFAULT_QUERY_CONCURRENCY);
this.setHierarchyFilter(props.filtering);
const queryCacheSize = props.queryCacheSize ?? DEFAULT_QUERY_CACHE_SIZE;
if (queryCacheSize !== 0) {
this._nodesCache = new HierarchyCache_js_1.HierarchyCache({
// we divide the size by 2, because each variation also counts as a query that we cache
size: Math.ceil(queryCacheSize / 2),
variationsCount: 1,
});
}
this._unsubscribe = props.imodelChanged?.addListener(() => {
this.invalidateHierarchyCache("Data source changed");
this._dispose.next();
this._hierarchyChanged.raiseEvent();
});
}
[Symbol.dispose]() {
this._dispose.next();
this._unsubscribe?.();
}
/* c8 ignore next 3 */
dispose() {
this[Symbol.dispose]();
}
get hierarchyChanged() {
return this._hierarchyChanged;
}
invalidateHierarchyCache(reason) {
(0, LoggingUtils_js_1.doLog)({
category: `${LOGGING_NAMESPACE}.Events`,
message: /* c8 ignore next */ () => (reason ? `${reason}: clear hierarchy cache` : `Clear hierarchy cache`),
});
this._nodesCache?.clear();
}
/**
* Sets `HierarchyProvider` values formatter that formats nodes' labels. If provided `undefined`, then defaults to the
* result of `createDefaultValueFormatter` called with default parameters.
*/
setFormatter(formatter) {
this._valuesFormatter = formatter ?? (0, presentation_shared_1.createDefaultValueFormatter)();
this._hierarchyChanged.raiseEvent({ formatterChange: { newFormatter: this._valuesFormatter } });
}
setHierarchyFilter(props) {
if (!props) {
if (this._sourceHierarchyDefinition !== this._activeHierarchyDefinition) {
this._activeHierarchyDefinition = this._sourceHierarchyDefinition;
this.invalidateHierarchyCache("Hierarchy filter reset");
}
this._hierarchyChanged.raiseEvent({ filterChange: { newFilter: undefined } });
return;
}
this._activeHierarchyDefinition = new FilteringHierarchyDefinition_js_1.FilteringHierarchyDefinition({
imodelAccess: this._imodelAccess,
source: this._sourceHierarchyDefinition,
nodeIdentifierPaths: props.paths,
});
this.invalidateHierarchyCache("Hierarchy filter set");
this._dispose.next();
this._hierarchyChanged.raiseEvent({ filterChange: { newFilter: props } });
}
onGroupingNodeCreated(groupingNode, props) {
this._nodesCache?.set({ ...props, parentNode: groupingNode }, { observable: (0, rxjs_1.from)(groupingNode.children), processingStatus: "pre-processed" });
}
createHierarchyLevelDefinitionsObservable(props) {
const { requestContext, ...defineHierarchyLevelProps } = props;
(0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting hierarchy level definitions for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
});
let parentNode = props.parentNode;
if (parentNode && HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) && parentNode.key.source && parentNode.key.source !== this._imodelAccess.imodelKey) {
return rxjs_1.EMPTY;
}
if (parentNode && HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode)) {
parentNode = {
...parentNode,
key: {
...parentNode.key,
instanceKeys: parentNode.key.instanceKeys.filter((ik) => !ik.imodelKey || ik.imodelKey === this._imodelAccess.imodelKey),
},
};
(0, core_bentley_1.assert)(HierarchyNodeKey_js_1.HierarchyNodeKey.isInstances(parentNode.key));
if (parentNode.key.instanceKeys.length === 0) {
return rxjs_1.EMPTY;
}
}
return this._activeHierarchyDefinition.defineHierarchyLevel({ ...defineHierarchyLevelProps, parentNode }).pipe((0, rxjs_1.mergeAll)(), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${requestContext.requestId}] Received all hierarchy level definitions for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
})));
}
createSourceNodesObservable(props) {
// pipe definitions to nodes and put "share replay" on it
return this.createHierarchyLevelDefinitionsObservable(props).pipe((0, rxjs_1.mergeMap)((def) => {
if (IModelHierarchyDefinition_js_1.HierarchyNodesDefinition.isGenericNode(def)) {
return (0, rxjs_1.of)({ ...def.node, key: { type: "generic", id: def.node.key, source: this._imodelAccess.imodelKey } });
}
return this._queryScheduler.scheduleSubscription((0, rxjs_1.of)(def.query).pipe((0, rxjs_1.map)((query) => filterQueryByInstanceKeys(query, props.filteredInstanceKeys)), (0, rxjs_1.mergeMap)((query) => (0, TreeNodesReader_js_1.readNodes)({
queryExecutor: this._imodelAccess,
query,
limit: props.hierarchyLevelSizeLimit,
parser: this._activeHierarchyDefinition.parseNode ? (row) => this._activeHierarchyDefinition.parseNode(row, props.parentNode) : undefined,
})), (0, rxjs_1.map)((node) => ({
...node,
key: { ...node.key, instanceKeys: node.key.instanceKeys.map((key) => ({ ...key, imodelKey: this._imodelAccess.imodelKey })) },
}))));
}), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Read all child nodes ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
})), (0, ShareReplayWithErrors_js_1.shareReplayWithErrors)());
}
createPreProcessedNodesObservable(queryNodesObservable, props) {
return queryNodesObservable.pipe(
// we're going to be mutating the nodes, but don't want to mutate the original one, so just clone it here once
(0, rxjs_1.map)((node) => ({ ...node })),
// set parent node keys on the source node
(0, rxjs_1.map)((node) => Object.assign(node, { parentKeys: createParentNodeKeysList(props.parentNode) })),
// format `ConcatenatedValue` labels into string labels
(0, rxjs_1.mergeMap)((node) => applyLabelsFormatting(node, this._valuesFormatter)),
// we have `ProcessedHierarchyNode` from here
preProcessNodes(this._activeHierarchyDefinition),
// process hiding
(0, HideIfNoChildren_js_1.createHideIfNoChildrenOperator)((n) => this.getChildNodesObservables({ parentNode: n, requestContext: props.requestContext }).hasNodes), (0, HideNodesInHierarchy_js_1.createHideNodesInHierarchyOperator)(
// note: for child nodes created because of hidden parent, we want to use parent's request props (instance filter, limit)
(n) => this.getChildNodesObservables({ ...props, parentNode: n }).processedNodes, false), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished pre-processing child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
})));
}
createProcessedNodesObservable(preprocessedNodesObservable, props) {
return preprocessedNodesObservable.pipe((0, Grouping_js_1.createGroupingOperator)(this._imodelAccess, props.parentNode, this._valuesFormatter, this._localizedStrings, undefined, (gn) => this.onGroupingNodeCreated(gn, props)), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished grouping child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
})));
}
createFinalizedNodesObservable(processedNodesObservable, props) {
return processedNodesObservable.pipe((0, DetermineChildren_js_1.createDetermineChildrenOperator)((n) => this.getChildNodesObservables({ parentNode: n, requestContext: props.requestContext }).hasNodes), postProcessNodes(this._activeHierarchyDefinition), Sorting_js_1.sortNodesByLabelOperator, (0, rxjs_1.map)((n) => {
if ("processingParams" in n) {
delete n.processingParams;
}
return Object.assign(n, {
children: (0, Common_js_1.hasChildren)(n),
});
}), (0, rxjs_1.takeUntil)(this._dispose), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({
category: LOGGING_NAMESPACE_PERFORMANCE,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished finalizing child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
})));
}
createHasNodesObservable(preprocessedNodesObservable, possiblyKnownChildrenObservable, props) {
const loggingCategory = `${LOGGING_NAMESPACE_INTERNAL}.HasNodes`;
return (0, rxjs_1.concat)((possiblyKnownChildrenObservable ?? rxjs_1.EMPTY).pipe((0, rxjs_1.filter)((n) => (0, Common_js_1.hasChildren)(n))), preprocessedNodesObservable).pipe((0, LoggingUtils_js_1.log)({
category: loggingCategory,
message: /* c8 ignore next */ (n) => `[${props.requestContext.requestId}] Node before mapping to 'true': ${(0, Common_js_1.createNodeIdentifierForLogging)(n)}`,
}), (0, rxjs_1.take)(1), (0, rxjs_1.map)(() => true), (0, rxjs_1.defaultIfEmpty)(false), (0, rxjs_1.catchError)((e) => {
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Error while determining children: ${e.message}`,
});
if (e instanceof HierarchyErrors_js_1.RowsLimitExceededError) {
return (0, rxjs_1.of)(true);
}
throw e;
}), (0, LoggingUtils_js_1.log)({ category: loggingCategory, message: /* c8 ignore next */ (r) => `[${props.requestContext.requestId}] Result: ${r}` }));
}
getCachedObservableEntry(props) {
const loggingCategory = `${LOGGING_NAMESPACE}.QueryResultsCache`;
const { parentNode, ...restProps } = props;
const cached = props.ignoreCache || !this._nodesCache ? undefined : this._nodesCache.get(props);
if (cached) {
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Found query nodes observable for ${(0, Common_js_1.createNodeIdentifierForLogging)(parentNode)}`,
});
return cached;
}
// if we don't find an entry for a grouping node, we load its instances by getting a query and applying
// a filter based on grouped instance keys
let filteredInstanceKeys;
let parentNonGroupingNode;
if (parentNode) {
if (HierarchyNode_js_1.HierarchyNode.isGroupingNode(parentNode)) {
parentNonGroupingNode = parentNode.nonGroupingAncestor;
filteredInstanceKeys = parentNode.groupedInstanceKeys;
}
else {
// not sure why type checker doesn't pick this up
(0, core_bentley_1.assert)(HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) || HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode));
parentNonGroupingNode = parentNode;
}
}
const nonGroupingNodeChildrenRequestProps = {
...restProps,
parentNode: parentNonGroupingNode,
...(filteredInstanceKeys ? { filteredInstanceKeys } : undefined),
};
const value = { observable: this.createSourceNodesObservable(nonGroupingNodeChildrenRequestProps), processingStatus: "none" };
this._nodesCache?.set(nonGroupingNodeChildrenRequestProps, value);
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Saved query nodes observable for ${(0, Common_js_1.createNodeIdentifierForLogging)(parentNode)}`,
});
return value;
}
getChildNodesObservables(props) {
const entry = this.getCachedObservableEntry(props);
switch (entry.processingStatus) {
case "none": {
const pre = this.createPreProcessedNodesObservable(entry.observable, props);
const post = this.createProcessedNodesObservable(pre, props);
return {
processedNodes: post,
hasNodes: this.createHasNodesObservable(pre, entry.observable, props),
finalizedNodes: this.createFinalizedNodesObservable(post, props),
};
}
case "pre-processed": {
const post = this.createProcessedNodesObservable(entry.observable, props);
return {
processedNodes: post,
hasNodes: this.createHasNodesObservable(entry.observable, undefined, props),
finalizedNodes: this.createFinalizedNodesObservable(post, props),
};
}
}
}
/**
* Creates and runs a query based on provided props, then processes retrieved nodes and returns them.
*/
getNodes(props) {
const requestContext = { requestId: core_bentley_1.Guid.createValue() };
const loggingCategory = `${LOGGING_NAMESPACE}.GetNodes`;
const timer = new core_bentley_1.StopWatch(undefined, true);
let error;
let nodesCount = 0;
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
});
return (0, EachValueFrom_js_1.eachValueFrom)(this.getChildNodesObservables({ ...props, requestContext }).finalizedNodes.pipe((0, rxjs_1.tap)(() => ++nodesCount), (0, rxjs_1.catchError)((e) => {
error = e;
throw e;
}), (0, rxjs_1.finalize)(() => {
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () =>
/* c8 ignore next 3 */ error
? `[${requestContext.requestId}] Error creating child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}: ${error instanceof Error ? error.message : error.toString()}`
: `[${requestContext.requestId}] Returned ${nodesCount} child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)} in ${timer.currentSeconds.toFixed(2)} s.`,
});
})));
}
getNodeInstanceKeysObs(props) {
const { parentNode, instanceFilter, hierarchyLevelSizeLimit = "unbounded", requestContext } = props;
if (parentNode && HierarchyNode_js_1.HierarchyNode.isGroupingNode(parentNode)) {
return (0, rxjs_1.from)(parentNode.groupedInstanceKeys);
}
(0, core_bentley_1.assert)(!parentNode || HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) || HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode));
const hierarchyLevelDefinitions = this.createHierarchyLevelDefinitionsObservable({ parentNode, instanceFilter, requestContext });
// split the definitions based on whether they're for generic nodes or for instance nodes
const [genericDefs, instanceDefs] = (0, Partition_js_1.partition)(hierarchyLevelDefinitions, IModelHierarchyDefinition_js_1.HierarchyNodesDefinition.isGenericNode);
// query instance keys and a flag whether they should be hidden
const instanceKeys = instanceDefs.pipe((0, rxjs_1.mergeMap)((def) => this._queryScheduler.scheduleSubscription((0, rxjs_1.of)(def.query).pipe((0, rxjs_1.mergeMap)(async (query) => {
const ecsql = `
SELECT
${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.FullClassName},
${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.ECInstanceId},
${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.HideNodeInHierarchy}
FROM (
${query.ecsql}
)
`;
const reader = this._imodelAccess.createQueryReader({ ...query, ecsql }, { rowFormat: "Indexes", limit: hierarchyLevelSizeLimit });
return (0, rxjs_1.from)(reader).pipe((0, rxjs_1.map)((row) => ({
key: {
className: (0, presentation_shared_1.normalizeFullClassName)(row[0]),
id: row[1],
imodelKey: this._imodelAccess.imodelKey,
},
hide: !!row[2],
})));
}), (0, rxjs_1.mergeAll)()))));
// split the instance keys observable based on whether they should be hidden or not
const [visibleNodeInstanceKeys, hiddenNodeInstanceKeys] = (0, Partition_js_1.partition)(instanceKeys, ({ hide }) => !hide);
// hidden items' handling:
// - if a generic node is hidden, we'll want to retrieve instance keys of its children, otherwise we don't care about it,
// - if an instance key is hidden, we want to create a merged node similar to what we do in `createHideNodesInHierarchyOperator`
const hiddenParentNodes = (0, rxjs_1.merge)(genericDefs.pipe((0, rxjs_1.filter)((def) => !!def.node.processingParams?.hideInHierarchy), (0, rxjs_1.map)((def) => ({
type: "generic",
id: def.node.key,
source: this._imodelAccess.imodelKey,
}))), hiddenNodeInstanceKeys.pipe(
// first merge all keys by class
(0, ReduceToMergeMap_js_1.reduceToMergeMapList)(({ key }) => key.className, ({ key }) => key.id),
// then, for each class, create an instance key
(0, rxjs_1.mergeMap)((mergedMap) => [...mergedMap.entries()].map(([className, ids]) => ({
type: "instances",
instanceKeys: ids.map((id) => ({ className, id, imodelKey: this._imodelAccess.imodelKey })),
}))))).pipe((0, rxjs_1.map)((key) => ({
key,
parentKeys: [],
label: "",
})));
// merge visible instance keys from this level & the ones we get recursively requesting from deeper levels
return (0, rxjs_1.merge)(visibleNodeInstanceKeys.pipe((0, rxjs_1.map)(({ key }) => key)), hiddenParentNodes.pipe((0, rxjs_1.mergeMap)((hiddenNode) => this.getNodeInstanceKeysObs({ parentNode: hiddenNode, requestContext })))).pipe((0, rxjs_1.takeUntil)(this._dispose));
}
/**
* Creates an iterator for all child hierarchy level instance keys, taking into account any hidden hierarchy levels
* that there may be under the given parent node.
*/
getNodeInstanceKeys(props) {
const requestContext = { requestId: core_bentley_1.Guid.createValue() };
const loggingCategory = `${LOGGING_NAMESPACE}.GetNodeInstanceKeys`;
const timer = new core_bentley_1.StopWatch(undefined, true);
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`,
});
let error;
let keysCount = 0;
return (0, EachValueFrom_js_1.eachValueFrom)(this.getNodeInstanceKeysObs({ ...props, requestContext }).pipe((0, rxjs_1.tap)(() => ++keysCount), (0, rxjs_1.catchError)((e) => {
error = e;
throw e;
}), (0, rxjs_1.finalize)(() => {
(0, LoggingUtils_js_1.doLog)({
category: loggingCategory,
message: /* c8 ignore next */ () =>
/* c8 ignore next 3 */ error
? `[${requestContext.requestId}] Error creating node instance keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}: ${error instanceof Error ? error.message : error.toString()}`
: `[${requestContext.requestId}] Returned ${keysCount} instance keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)} in ${timer.currentSeconds.toFixed(2)} s.`,
});
})));
}
}
function preProcessNodes(hierarchyFactory) {
return hierarchyFactory.preProcessNode ? processNodes(hierarchyFactory.preProcessNode) : rxjs_1.identity;
}
function postProcessNodes(hierarchyFactory) {
return hierarchyFactory.postProcessNode ? processNodes(hierarchyFactory.postProcessNode) : rxjs_1.identity;
}
function processNodes(processor) {
return (nodes) => nodes.pipe((0, rxjs_1.mergeMap)(processor));
}
function applyLabelsFormatting(node, valueFormatter) {
return (0, rxjs_1.from)((0, presentation_shared_1.formatConcatenatedValue)({
value: node.label,
valueFormatter,
})).pipe((0, rxjs_1.map)((label) => ({
...node,
label,
})));
}
function createParentNodeKeysList(parentNode) {
if (!parentNode) {
return [];
}
return [...parentNode.parentKeys, parentNode.key];
}
function filterQueryByInstanceKeys(query, filteredInstanceKeys) {
if (!filteredInstanceKeys || !filteredInstanceKeys.length) {
return query;
}
const MAX_ALLOWED_BINDINGS = 1000;
if (filteredInstanceKeys.length < MAX_ALLOWED_BINDINGS) {
return {
...query,
ecsql: `
SELECT *
FROM (${query.ecsql}) q
WHERE q.ECInstanceId IN (${filteredInstanceKeys.map(() => "?").join(",")})
`,
bindings: [...(query.bindings ?? []), ...filteredInstanceKeys.map((k) => ({ type: "id", value: k.id }))],
};
}
/* c8 ignore start */
return {
...query,
ecsql: `
SELECT *
FROM (${query.ecsql}) q
WHERE InVirtualSet(?, q.ECInstanceId)
`,
bindings: [...(query.bindings ?? []), { type: "idset", value: filteredInstanceKeys.map((k) => k.id) }],
};
/* c8 ignore end */
}
//# sourceMappingURL=IModelHierarchyProvider.js.map