Skip to content

Commit 92ff534

Browse files
Merge branch 'tree-widget/next' into mast/classifications-tree-rename-action
2 parents f00634f + 6722876 commit 92ff534

18 files changed

+1792
-72
lines changed

apps/performance-tests/src/util/TestReporter.cts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import asTable from "as-table";
77
import fs from "fs";
88
import Mocha from "mocha";
99
import { LOGGER } from "./Logging.cjs";
10-
import { MainThreadBlocksDetector, Summary } from "./MainThreadBlocksDetector.cjs";
10+
import { MainThreadBlocksDetector } from "./MainThreadBlocksDetector.cjs";
11+
12+
import type { Summary } from "./MainThreadBlocksDetector.cjs";
1113

1214
interface TestInfo {
1315
test: Mocha.Runnable;

apps/test-viewer/src/UiProvidersConfig.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,10 @@ const configuredUiItems = new Map<string, UiItem>([
159159
{
160160
id: ClassificationsTreeComponent.id,
161161
getLabel: () => "Classifications tree",
162-
render: () => (
162+
isSearchable: true,
163+
render: (props) => (
163164
<ClassificationsTreeComponent
165+
filter={props.filter}
164166
selectionStorage={unifiedSelectionStorage}
165167
hierarchyConfig={{ rootClassificationSystemCode: "50k classifications" }}
166168
getEditingProps={(node) => {

packages/itwin/tree-widget/api/tree-widget-react.api.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const ClassificationsTreeComponent: {
128128
};
129129

130130
// @alpha (undocumented)
131-
interface ClassificationsTreeComponentProps extends Pick<ClassificationsTreeProps, "selectionStorage" | "hierarchyLevelConfig" | "selectionMode" | "emptyTreeContent" | "getActions" | "getDecorations" | "hierarchyConfig" | "getEditingProps"> {
131+
interface ClassificationsTreeComponentProps extends Pick<ClassificationsTreeProps, "selectionStorage" | "hierarchyLevelConfig" | "selectionMode" | "filter" | "emptyTreeContent" | "getActions" | "getDecorations" | "hierarchyConfig" | "getEditingProps"> {
132132
// (undocumented)
133133
onFeatureUsed?: (feature: string) => void;
134134
// (undocumented)
@@ -531,7 +531,7 @@ interface UseCategoriesTreeResult {
531531
}
532532

533533
// @alpha
534-
export function useClassificationsTree({ activeView, emptyTreeContent, ...rest }: UseClassificationsTreeProps): UseClassificationsTreeResult;
534+
export function useClassificationsTree({ activeView, emptyTreeContent, filter, ...rest }: UseClassificationsTreeProps): UseClassificationsTreeResult;
535535

536536
// @alpha (undocumented)
537537
interface UseClassificationsTreeProps {
@@ -540,13 +540,15 @@ interface UseClassificationsTreeProps {
540540
// (undocumented)
541541
emptyTreeContent?: ReactNode;
542542
// (undocumented)
543+
filter?: string;
544+
// (undocumented)
543545
hierarchyConfig: ClassificationsTreeHierarchyConfiguration;
544546
}
545547

546548
// @alpha (undocumented)
547549
interface UseClassificationsTreeResult {
548550
// (undocumented)
549-
categoriesTreeProps: Pick<VisibilityTreeProps, "treeName" | "getHierarchyDefinition" | "visibilityHandlerFactory" | "emptyTreeContent">;
551+
classificationsTreeProps: Pick<VisibilityTreeProps, "treeName" | "getHierarchyDefinition" | "visibilityHandlerFactory" | "getFilteredPaths" | "emptyTreeContent" | "highlight">;
550552
// (undocumented)
551553
rendererProps: Required<Pick<VisibilityTreeRendererProps, "getDecorations">>;
552554
}

packages/itwin/tree-widget/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@itwin/tree-widget-react",
3-
"version": "4.0.0-alpha.13",
3+
"version": "4.0.0-alpha.14",
44
"description": "Tree Widget React",
55
"keywords": [
66
"Bentley",

packages/itwin/tree-widget/public/locales/en/TreeWidget.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,14 @@
5656
}
5757
},
5858
"classificationsTree": {
59-
"label": "Classifications"
59+
"label": "Classifications",
60+
"filtering": {
61+
"noMatches": "No results were found for your search query.",
62+
"noMatchesRetry": "Please modify your search and try again.",
63+
"unknownFilterError": "An unknown error occurred while filtering the tree.",
64+
"tooManyFilterMatches": "There are too many matches for your search query.",
65+
"tooManyFilterMatchesRetry": "Please refine your search and try again."
66+
}
6067
},
6168
"modelsTree": {
6269
"label": "Models",
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3+
* See LICENSE.md in the project root for license terms and full copyright notice.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { expect } from "chai";
7+
import { ClassificationsTreeDefinition, ClassificationsTreeIdsCache } from "../../../tree-widget-react-internal.js";
8+
import {
9+
CLASS_NAME_Classification,
10+
CLASS_NAME_ClassificationTable,
11+
CLASS_NAME_GeometricElement2d,
12+
CLASS_NAME_GeometricElement3d,
13+
} from "../../../tree-widget-react/components/trees/common/internal/ClassNameDefinitions.js";
14+
import {
15+
buildIModel,
16+
insertDrawingCategory,
17+
insertDrawingGraphic,
18+
insertDrawingModelWithPartition,
19+
insertPhysicalElement,
20+
insertPhysicalModelWithPartition,
21+
insertSpatialCategory,
22+
} from "../../IModelUtils.js";
23+
import { initializeITwinJs, terminateITwinJs } from "../../Initialize.js";
24+
import { createIModelAccess } from "../Common.js";
25+
import {
26+
importClassificationSchema,
27+
insertClassification,
28+
insertClassificationSystem,
29+
insertClassificationTable,
30+
insertElementHasClassificationsRelationship,
31+
} from "./Utils.js";
32+
33+
const rootClassificationSystemCode = "TestClassificationSystem";
34+
const defaultHierarchyConfiguration = {
35+
rootClassificationSystemCode,
36+
};
37+
38+
describe("Classifications tree", () => {
39+
describe("Hierarchy filtering", () => {
40+
before(async function () {
41+
await initializeITwinJs();
42+
});
43+
44+
after(async function () {
45+
await terminateITwinJs();
46+
});
47+
48+
["Test", "_", "%"].forEach((label) => {
49+
it(`finds classification table by label when it contains '${label}'`, async function () {
50+
await using buildIModelResult = await buildIModel(this, async (builder) => {
51+
await importClassificationSchema(builder);
52+
53+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
54+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable", userLabel: `${label}Table` });
55+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification" });
56+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "Model" });
57+
const spatialCategory = insertSpatialCategory({ builder, codeValue: "Category" });
58+
const element = insertPhysicalElement({
59+
builder,
60+
modelId: physicalModel.id,
61+
categoryId: spatialCategory.id,
62+
codeValue: "Element",
63+
});
64+
insertElementHasClassificationsRelationship({ builder, elementId: element.id, classificationId: classification.id });
65+
66+
return { table };
67+
});
68+
const { imodel, ...keys } = buildIModelResult;
69+
const imodelAccess = createIModelAccess(imodel);
70+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
71+
expect(
72+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
73+
imodelAccess,
74+
label,
75+
idsCache,
76+
hierarchyConfig: defaultHierarchyConfiguration,
77+
}),
78+
).to.deep.eq([
79+
{
80+
path: [{ id: keys.table.id, className: CLASS_NAME_ClassificationTable }],
81+
options: { autoExpand: true },
82+
},
83+
]);
84+
});
85+
86+
it(`finds classification by label when it contains '${label}'`, async function () {
87+
await using buildIModelResult = await buildIModel(this, async (builder) => {
88+
await importClassificationSchema(builder);
89+
90+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
91+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable" });
92+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification", userLabel: `${label}Cl` });
93+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "Model" });
94+
const spatialCategory = insertSpatialCategory({ builder, codeValue: "Category" });
95+
const element = insertPhysicalElement({
96+
builder,
97+
modelId: physicalModel.id,
98+
categoryId: spatialCategory.id,
99+
codeValue: "Element",
100+
});
101+
insertElementHasClassificationsRelationship({ builder, elementId: element.id, classificationId: classification.id });
102+
103+
return { table, classification };
104+
});
105+
const { imodel, ...keys } = buildIModelResult;
106+
const imodelAccess = createIModelAccess(imodel);
107+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
108+
expect(
109+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
110+
imodelAccess,
111+
label,
112+
idsCache,
113+
hierarchyConfig: defaultHierarchyConfiguration,
114+
}),
115+
).to.deep.eq([
116+
{
117+
path: [
118+
{ id: keys.table.id, className: CLASS_NAME_ClassificationTable },
119+
{ id: keys.classification.id, className: CLASS_NAME_Classification },
120+
],
121+
options: { autoExpand: true },
122+
},
123+
]);
124+
});
125+
126+
it(`finds 3d element by label when it contains '${label}'`, async function () {
127+
await using buildIModelResult = await buildIModel(this, async (builder) => {
128+
await importClassificationSchema(builder);
129+
130+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
131+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable" });
132+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification" });
133+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "Model" });
134+
const spatialCategory = insertSpatialCategory({ builder, codeValue: "Category" });
135+
const element = insertPhysicalElement({
136+
builder,
137+
modelId: physicalModel.id,
138+
categoryId: spatialCategory.id,
139+
codeValue: "Element",
140+
userLabel: `${label}El`,
141+
});
142+
insertElementHasClassificationsRelationship({ builder, elementId: element.id, classificationId: classification.id });
143+
144+
return { table, classification, element };
145+
});
146+
const { imodel, ...keys } = buildIModelResult;
147+
const imodelAccess = createIModelAccess(imodel);
148+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
149+
expect(
150+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
151+
imodelAccess,
152+
label,
153+
idsCache,
154+
hierarchyConfig: defaultHierarchyConfiguration,
155+
}),
156+
).to.deep.eq([
157+
{
158+
path: [
159+
{ id: keys.table.id, className: CLASS_NAME_ClassificationTable },
160+
{ id: keys.classification.id, className: CLASS_NAME_Classification },
161+
{ id: keys.element.id, className: CLASS_NAME_GeometricElement3d },
162+
],
163+
options: { autoExpand: true },
164+
},
165+
]);
166+
});
167+
168+
it(`finds 3d child element by label when it contains '${label}'`, async function () {
169+
await using buildIModelResult = await buildIModel(this, async (builder) => {
170+
await importClassificationSchema(builder);
171+
172+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
173+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable" });
174+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification" });
175+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "Model" });
176+
const spatialCategory = insertSpatialCategory({ builder, codeValue: "Category" });
177+
const parentElement = insertPhysicalElement({
178+
builder,
179+
modelId: physicalModel.id,
180+
categoryId: spatialCategory.id,
181+
codeValue: "Parent Element",
182+
});
183+
insertElementHasClassificationsRelationship({ builder, elementId: parentElement.id, classificationId: classification.id });
184+
const childElement = insertPhysicalElement({
185+
builder,
186+
modelId: physicalModel.id,
187+
categoryId: spatialCategory.id,
188+
codeValue: "Child Element",
189+
userLabel: `${label}ChildEl`,
190+
parentId: parentElement.id,
191+
});
192+
193+
return { table, classification, parentElement, childElement };
194+
});
195+
const { imodel, ...keys } = buildIModelResult;
196+
const imodelAccess = createIModelAccess(imodel);
197+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
198+
expect(
199+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
200+
imodelAccess,
201+
label,
202+
idsCache,
203+
hierarchyConfig: defaultHierarchyConfiguration,
204+
}),
205+
).to.deep.eq([
206+
{
207+
path: [
208+
{ id: keys.table.id, className: CLASS_NAME_ClassificationTable },
209+
{ id: keys.classification.id, className: CLASS_NAME_Classification },
210+
{ id: keys.parentElement.id, className: CLASS_NAME_GeometricElement3d },
211+
{ id: keys.childElement.id, className: CLASS_NAME_GeometricElement3d },
212+
],
213+
options: { autoExpand: true },
214+
},
215+
]);
216+
});
217+
218+
it(`finds 2d element by label when it contains '${label}'`, async function () {
219+
await using buildIModelResult = await buildIModel(this, async (builder) => {
220+
await importClassificationSchema(builder);
221+
222+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
223+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable" });
224+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification" });
225+
const drawingModel = insertDrawingModelWithPartition({ builder, codeValue: "model" });
226+
const drawingCategory = insertDrawingCategory({ builder, codeValue: "category" });
227+
const element = insertDrawingGraphic({
228+
builder,
229+
modelId: drawingModel.id,
230+
categoryId: drawingCategory.id,
231+
codeValue: "element",
232+
userLabel: `${label}El`,
233+
});
234+
insertElementHasClassificationsRelationship({ builder, elementId: element.id, classificationId: classification.id });
235+
236+
return { table, classification, element };
237+
});
238+
const { imodel, ...keys } = buildIModelResult;
239+
const imodelAccess = createIModelAccess(imodel);
240+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
241+
expect(
242+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
243+
imodelAccess,
244+
label,
245+
idsCache,
246+
hierarchyConfig: defaultHierarchyConfiguration,
247+
}),
248+
).to.deep.eq([
249+
{
250+
path: [
251+
{ id: keys.table.id, className: CLASS_NAME_ClassificationTable },
252+
{ id: keys.classification.id, className: CLASS_NAME_Classification },
253+
{ id: keys.element.id, className: CLASS_NAME_GeometricElement2d },
254+
],
255+
options: { autoExpand: true },
256+
},
257+
]);
258+
});
259+
});
260+
261+
it("returns empty array when nothing matches provided filter", async function () {
262+
await using buildIModelResult = await buildIModel(this, async (builder) => {
263+
await importClassificationSchema(builder);
264+
265+
const system = insertClassificationSystem({ builder, codeValue: rootClassificationSystemCode });
266+
const table = insertClassificationTable({ builder, parentId: system.id, codeValue: "ClassificationTable" });
267+
const classification = insertClassification({ builder, modelId: table.id, codeValue: "Classification" });
268+
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "physical model" });
269+
const spatialCategory = insertSpatialCategory({ builder, codeValue: "physical category" });
270+
const physicalElement = insertPhysicalElement({
271+
builder,
272+
modelId: physicalModel.id,
273+
categoryId: spatialCategory.id,
274+
codeValue: "Physical element",
275+
});
276+
insertElementHasClassificationsRelationship({ builder, elementId: physicalElement.id, classificationId: classification.id });
277+
const drawingModel = insertDrawingModelWithPartition({ builder, codeValue: "drawing model" });
278+
const drawingCategory = insertDrawingCategory({ builder, codeValue: "drawing category" });
279+
const drawingElement = insertDrawingGraphic({
280+
builder,
281+
modelId: drawingModel.id,
282+
categoryId: drawingCategory.id,
283+
codeValue: "Drawing element",
284+
});
285+
insertElementHasClassificationsRelationship({ builder, elementId: drawingElement.id, classificationId: classification.id });
286+
});
287+
const { imodel } = buildIModelResult;
288+
const imodelAccess = createIModelAccess(imodel);
289+
using idsCache = new ClassificationsTreeIdsCache(imodelAccess, defaultHierarchyConfiguration);
290+
expect(
291+
await ClassificationsTreeDefinition.createInstanceKeyPaths({
292+
imodelAccess,
293+
label: "Test",
294+
idsCache,
295+
hierarchyConfig: defaultHierarchyConfiguration,
296+
}),
297+
).to.deep.eq([]);
298+
});
299+
});
300+
});

0 commit comments

Comments
 (0)