Skip to content

Commit

Permalink
Customize filtered tree node items (#179)
Browse files Browse the repository at this point in the history
* Filtered tree data provider should apply customizeTreeNodeItem from parent provider

* Change files
  • Loading branch information
saskliutas committed Jul 24, 2023
1 parent da5b999 commit 3d77008
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 13 deletions.
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Make sure `FilteredPresentationTreeDataProvider` creates same tree node items as parent `PresentationTreeDataProvider`.",
"packageName": "@itwin/presentation-components",
"email": "24278440+saskliutas@users.noreply.github.com",
"dependentChangeType": "patch"
}
Expand Up @@ -248,8 +248,8 @@ export class PresentationTreeDataProvider implements IPresentationTreeDataProvid
return createNodesAndCountResult(
async () => this._dataSource.getNodesAndCount(requestOptions),
this.createBaseRequestOptions(),
(node, parentId) => this.createTreeNodeItem(node, parentId),
parentNode,
this._nodesCreateProps,
);
},
{ isMatchingKey: MemoizationHelpers.areNodesRequestsEqual as any },
Expand All @@ -265,6 +265,14 @@ export class PresentationTreeDataProvider implements IPresentationTreeDataProvid
filterText: filter,
});
}

/**
* Creates tree node item from supplied [[Node]].
* @internal
*/
public createTreeNodeItem(node: Node, parentId?: string) {
return createTreeNodeItem(node, parentId, this._nodesCreateProps);
}
}

async function getFilterDefinition(imodel: IModelConnection, node?: TreeNodeItem) {
Expand All @@ -277,8 +285,8 @@ async function getFilterDefinition(imodel: IModelConnection, node?: TreeNodeItem
async function createNodesAndCountResult(
resultFactory: () => Promise<{ nodes: Node[]; count: number }>,
baseOptions: RequestOptionsWithRuleset<IModelConnection>,
treeItemFactory: (node: Node, parentId?: string) => PresentationTreeNodeItem,
parentNode?: TreeNodeItem,
nodesCreateProps?: CreateTreeNodeItemProps,
) {
try {
const result = await resultFactory();
Expand All @@ -287,7 +295,7 @@ async function createNodesAndCountResult(
if (nodes.length === 0 && isParentFiltered) {
return createStatusNodeResult(parentNode, "tree.no-filtered-children");
}
return { nodes: createTreeItems(nodes, baseOptions, parentNode, nodesCreateProps), count };
return { nodes: createTreeItems(nodes, baseOptions, treeItemFactory, parentNode), count };
} catch (e) {
if (e instanceof PresentationError) {
switch (e.errorNumber) {
Expand Down Expand Up @@ -318,12 +326,12 @@ function createStatusNodeResult(parentNode: TreeNodeItem | undefined, labelKey:
function createTreeItems(
nodes: Node[],
baseOptions: RequestOptionsWithRuleset<IModelConnection>,
treeItemFactory: (node: Node, parentId?: string) => PresentationTreeNodeItem,
parentNode?: TreeNodeItem,
nodesCreateProps?: CreateTreeNodeItemProps,
) {
const items: PresentationTreeNodeItem[] = [];
for (const node of nodes) {
const item = createTreeNodeItem(node, parentNode?.id, nodesCreateProps);
const item = treeItemFactory(node, parentNode?.id);
if (node.supportsFiltering) {
item.filtering = {
descriptor: async () => {
Expand Down
Expand Up @@ -16,8 +16,10 @@ import {
TreeNodeItem,
} from "@itwin/components-react";
import { IModelConnection } from "@itwin/core-frontend";
import { NodeKey, NodePathElement } from "@itwin/presentation-common";
import { Node, NodeKey, NodePathElement } from "@itwin/presentation-common";
import { PresentationTreeDataProvider } from "./DataProvider";
import { IPresentationTreeDataProvider } from "./IPresentationTreeDataProvider";
import { PresentationTreeNodeItem } from "./PresentationTreeNodeItem";
import { createTreeNodeItem } from "./Utils";

/**
Expand Down Expand Up @@ -63,8 +65,14 @@ export class FilteredPresentationTreeDataProvider implements IFilteredPresentati
public constructor(props: FilteredPresentationTreeDataProviderProps) {
this._parentDataProvider = props.parentDataProvider;
this._filter = props.filter;

const treeNodeItemFactory: (node: Node, parentId?: string) => PresentationTreeNodeItem =
this._parentDataProvider instanceof PresentationTreeDataProvider
? this._parentDataProvider.createTreeNodeItem.bind(this._parentDataProvider)
: createTreeNodeItem;

const hierarchy: SimpleTreeDataProviderHierarchy = new Map<string | undefined, TreeNodeItem[]>();
this.createHierarchy(props.paths, hierarchy);
this.createHierarchy(props.paths, hierarchy, treeNodeItemFactory);
this._filteredDataProvider = new SimpleTreeDataProvider(hierarchy);
}

Expand All @@ -87,17 +95,22 @@ export class FilteredPresentationTreeDataProvider implements IFilteredPresentati
return this._parentDataProvider;
}

private createHierarchy(paths: ReadonlyArray<Readonly<NodePathElement>>, hierarchy: SimpleTreeDataProviderHierarchy, parentId?: string) {
private createHierarchy(
paths: ReadonlyArray<Readonly<NodePathElement>>,
hierarchy: SimpleTreeDataProviderHierarchy,
treeNodeItemFactory: (node: Node, parentId?: string) => PresentationTreeNodeItem,
parentId?: string,
) {
const treeNodes: DelayLoadedTreeNodeItem[] = [];
for (let i = 0; i < paths.length; i++) {
const node = createTreeNodeItem(paths[i].node, parentId);
const node = treeNodeItemFactory(paths[i].node, parentId);

if (paths[i].filteringData && paths[i].filteringData!.matchesCount) {
this._filteredResultMatches.push({ id: node.id, matchesCount: paths[i].filteringData!.matchesCount });
}

if (paths[i].children.length !== 0) {
this.createHierarchy(paths[i].children, hierarchy, node.id);
this.createHierarchy(paths[i].children, hierarchy, treeNodeItemFactory, node.id);
node.hasChildren = true;
node.autoExpand = true;
} else {
Expand Down
38 changes: 35 additions & 3 deletions packages/components/src/test/tree/FilteredDataProvider.test.ts
Expand Up @@ -4,16 +4,20 @@
*--------------------------------------------------------------------------------------------*/

import { expect } from "chai";
import sinon from "sinon";
import * as moq from "typemoq";
import { PageOptions } from "@itwin/components-react";
import { BeEvent } from "@itwin/core-bentley";
import { IModelConnection } from "@itwin/core-frontend";
import { LabelDefinition, NodePathElement } from "@itwin/presentation-common";
import { PageOptions } from "@itwin/components-react";
import { Presentation, PresentationManager, RulesetVariablesManager } from "@itwin/presentation-frontend";
import { PresentationTreeDataProvider } from "../../presentation-components/tree/DataProvider";
import { FilteredPresentationTreeDataProvider } from "../../presentation-components/tree/FilteredDataProvider";
import { IPresentationTreeDataProvider } from "../../presentation-components/tree/IPresentationTreeDataProvider";
import { createTreeNodeItem } from "../../presentation-components/tree/Utils";
import { createTestTreeNodeItem } from "../_helpers/UiComponents";
import { createTestECInstancesNode, createTestECInstancesNodeKey, createTestNodePathElement } from "../_helpers/Hierarchy";
import { createTestECInstanceKey } from "../_helpers/Common";
import { createTestECInstancesNode, createTestECInstancesNodeKey, createTestNodePathElement } from "../_helpers/Hierarchy";
import { createTestTreeNodeItem } from "../_helpers/UiComponents";

describe("FilteredTreeDataProvider", () => {
function createTestNodePathElementWithId(id: string) {
Expand Down Expand Up @@ -69,6 +73,13 @@ describe("FilteredTreeDataProvider", () => {
const pageOptions: PageOptions = { size: 0, start: 0 };

beforeEach(() => {
const onVariableChanged = new BeEvent();
const presentationManagerMock = moq.Mock.ofType<PresentationManager>();
const rulesetVariablesManagerMock = moq.Mock.ofType<RulesetVariablesManager>();
presentationManagerMock.setup((x) => x.vars(moq.It.isAny())).returns(() => rulesetVariablesManagerMock.object);
rulesetVariablesManagerMock.setup((x) => x.onVariableChanged).returns(() => onVariableChanged);
sinon.stub(Presentation, "presentation").get(() => presentationManagerMock.object);

parentProviderMock.reset();
filter = "test_filter";
paths = createPaths();
Expand All @@ -79,6 +90,10 @@ describe("FilteredTreeDataProvider", () => {
});
});

afterEach(() => {
sinon.restore();
});

describe("filter", () => {
it("returns filter with which it was initialized", () => {
expect(provider.filter).to.be.equal(filter);
Expand Down Expand Up @@ -126,6 +141,23 @@ describe("FilteredTreeDataProvider", () => {
const result = await provider.getNodes(parentNode, pageOptions);
expect(result).to.matchSnapshot();
});

it("applies same node customizations as parent data provider", async () => {
const customizeStub = sinon.stub();
const parentProvider = new PresentationTreeDataProvider({
imodel: imodelMock.object,
ruleset: "test-rules",
customizeTreeNodeItem: customizeStub,
});
const testPaths = [createTestNodePathElement()];
const filteredProvider = new FilteredPresentationTreeDataProvider({
parentDataProvider: parentProvider,
filter: "Test",
paths: testPaths,
});
await filteredProvider.getNodes(undefined, pageOptions);
expect(customizeStub).to.be.calledOnce;
});
});

describe("getNodesCount", () => {
Expand Down

0 comments on commit 3d77008

Please sign in to comment.