Skip to content

Commit

Permalink
Jonas d/categories tree filtering fix (#1139)
Browse files Browse the repository at this point in the history
* Fix categories tree filtering

* Add changeset

* Update changeset message
  • Loading branch information
JonasDov authored Jan 8, 2025
1 parent 048831a commit d8b0b84
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Fixed a bug in `CategoriesTreeDefinition.createInstanceKeyPaths`, where sometimes incorrect key paths would be created. Because of this, `CategoriesTree` would show incorrect categories with applied label filter.",
"packageName": "@itwin/tree-widget-react",
"email": "100586436+JonasDov@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,19 @@ async function createInstanceKeyPathsFromInstanceLabel(
props: CategoriesTreeInstanceKeyPathsFromInstanceLabelProps & { labelsFactory: IInstanceLabelSelectClauseFactory },
) {
const { categoryClass, categoryElementClass } = getClassesByView(props.viewType);
const adjustedLabel = props.label.replace(/[%_\\]/g, "\\$&");
const reader = props.imodelAccess.createQueryReader(
{
ctes: [
`RootCategories(ClassName, ECInstanceId, ChildCount) as (
`RootCategoriesWithLabels(ClassName, ECInstanceId, ChildCount, DisplayLabel) as (
SELECT
ec_classname(this.ECClassId, 's.c'),
this.ECInstanceId,
COUNT(sc.ECInstanceId)
COUNT(sc.ECInstanceId),
${await props.labelsFactory.createSelectClause({
classAlias: "this",
className: categoryClass,
})}
FROM ${categoryClass} this
JOIN BisCore.Model m ON m.ECInstanceId = this.Model.Id
JOIN BisCore.SubCategory sc ON sc.Parent.Id = this.ECInstanceId
Expand All @@ -201,17 +206,30 @@ async function createInstanceKeyPathsFromInstanceLabel(
)`,
],
ecsql: `
SELECT * FROM (
SELECT
c.ClassName AS CategoryClass,
c.ECInstanceId AS CategoryId,
IIF(c.ChildCount > 1, sc.ClassName, NULL) AS SubcategoryClass,
IIF(c.ChildCount > 1, sc.ECInstanceId, NULL) AS SubcategoryId
FROM RootCategories c
sc.ClassName AS SubcategoryClass,
sc.ECInstanceId AS SubcategoryId
FROM RootCategoriesWithLabels c
JOIN SubCategoriesWithLabels sc ON sc.ParentId = c.ECInstanceId
WHERE sc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\'
WHERE c.ChildCount > 1 AND sc.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\'
UNION ALL
SELECT
c.ClassName AS CategoryClass,
c.ECInstanceId AS CategoryId,
CAST(NULL AS TEXT) AS SubcategoryClass,
CAST(NULL AS TEXT) AS SubcategoryId
FROM RootCategoriesWithLabels c
WHERE c.DisplayLabel LIKE '%' || ? || '%' ESCAPE '\\'
)
LIMIT ${MAX_FILTERING_INSTANCE_KEY_COUNT + 1}
`,
bindings: [{ type: "string", value: props.label.replace(/[%_\\]/g, "\\$&") }],
bindings: [
{ type: "string", value: adjustedLabel },
{ type: "string", value: adjustedLabel },
],
},
{ restartToken: "tree-widget/categories-tree/filter-by-label-query" },
);
Expand Down
7 changes: 5 additions & 2 deletions packages/itwin/tree-widget/src/test/IModelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,19 @@ export function insertDrawingSubModel(
}

export function insertSpatialCategory(
props: BaseInstanceInsertProps & { codeValue: string; modelId?: Id64String } & Partial<Omit<CategoryProps, "id" | "model" | "parent" | "code">>,
props: BaseInstanceInsertProps & { codeValue: string; modelId?: Id64String; userLabel?: string } & Partial<
Omit<CategoryProps, "id" | "model" | "parent" | "code">
>,
) {
const { builder, classFullName, modelId, codeValue, ...categoryProps } = props;
const { builder, classFullName, modelId, codeValue, userLabel, ...categoryProps } = props;
const defaultClassName = `BisCore${props.fullClassNameSeparator ?? "."}SpatialCategory`;
const className = classFullName ?? defaultClassName;
const model = modelId ?? IModel.dictionaryId;
const id = builder.insertElement({
classFullName: className,
model,
code: builder.createCode(model, BisCodeSpec.spatialCategory, codeValue),
userLabel,
...categoryProps,
});
return { className, id };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,81 @@ describe("Categories tree", () => {
).to.deep.eq([{ path: [keys.category, keys.subCategory2], options: { autoExpand: true } }]);
});

it("finds 3d categories by label when subCategory count is 1 and labels of category and subCategory differ", async function () {
const { imodel, keys } = await buildIModel(this, async (builder) => {
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" });
// SubCategory gets inserted by default
const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", userLabel: "Test" });
insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id });

return {
keys: {
category,
},
};
});
expect(
await CategoriesTreeDefinition.createInstanceKeyPaths({
imodelAccess: createIModelAccess(imodel),
label: "Test",
viewType: "3d",
}),
).to.deep.eq([{ path: [keys.category], options: { autoExpand: true } }]);

expect(
await CategoriesTreeDefinition.createInstanceKeyPaths({
imodelAccess: createIModelAccess(imodel),
label: "SpatialCategory",
viewType: "3d",
}),
).to.deep.eq([]);
});

it("finds 3d categories and subCategories by label when subCategory count is > 1", async function () {
const { imodel, keys } = await buildIModel(this, async (builder) => {
const physicalModel = insertPhysicalModelWithPartition({ builder, codeValue: "TestPhysicalModel" });

const category = insertSpatialCategory({ builder, codeValue: "SpatialCategory", userLabel: "Test" });
insertPhysicalElement({ builder, modelId: physicalModel.id, categoryId: category.id });

const subCategory1 = insertSubCategory({ builder, codeValue: "SubCategory1", parentCategoryId: category.id });

const subCategory2 = insertSubCategory({ builder, codeValue: "SubCategory2", parentCategoryId: category.id });

return {
keys: {
category,
subCategory1,
subCategory2,
},
};
});

expect(
await CategoriesTreeDefinition.createInstanceKeyPaths({
imodelAccess: createIModelAccess(imodel),
label: "Test",
viewType: "3d",
}),
).to.deep.eq([{ path: [keys.category], options: { autoExpand: true } }]);

expect(
await CategoriesTreeDefinition.createInstanceKeyPaths({
imodelAccess: createIModelAccess(imodel),
label: "SubCategory1",
viewType: "3d",
}),
).to.deep.eq([{ path: [keys.category, keys.subCategory1], options: { autoExpand: true } }]);

expect(
await CategoriesTreeDefinition.createInstanceKeyPaths({
imodelAccess: createIModelAccess(imodel),
label: "SubCategory2",
viewType: "3d",
}),
).to.deep.eq([{ path: [keys.category, keys.subCategory2], options: { autoExpand: true } }]);
});

it("finds 2d categories by label containing special SQLite characters", async function () {
const { imodel, keys } = await buildIModel(this, async (builder) => {
const drawingModel = insertDrawingModelWithPartition({ builder, codeValue: "TestDrawingModel" });
Expand Down

0 comments on commit d8b0b84

Please sign in to comment.