Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Presentation: Fix merged categorized nested content building #5067

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/presentation-components",
"comment": "Fix failure to load property data when nested properties are placed in root categories different from their parent property category",
"type": "none"
}
],
"packageName": "@itwin/presentation-components"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import {
ArrayValue, PrimitiveValue, PropertyDescription, PropertyEditorInfo, PropertyRecord, StructValue, PropertyValueFormat as UiPropertyValueFormat,
} from "@itwin/appui-abstract";
/** @packageDocumentation
* @module Core
*/
Expand All @@ -11,9 +14,6 @@ import {
ProcessFieldHierarchiesProps, ProcessMergedValueProps, ProcessPrimitiveValueProps, RendererDescription, StartArrayProps,
StartCategoryProps, StartContentProps, StartFieldProps, StartItemProps, StartStructProps, TypeDescription,
} from "@itwin/presentation-common";
import {
ArrayValue, PrimitiveValue, PropertyDescription, PropertyEditorInfo, PropertyRecord, StructValue, PropertyValueFormat as UiPropertyValueFormat,
} from "@itwin/appui-abstract";

/** @internal */
export interface FieldRecord {
Expand Down Expand Up @@ -182,17 +182,18 @@ export abstract class PropertyRecordsBuilder implements IContentVisitor {
}

public processMergedValue(props: ProcessMergedValueProps): void {
const propertyField = props.requestedField;
const value: PrimitiveValue = {
valueFormat: UiPropertyValueFormat.Primitive,
};
const record = new PropertyRecord(
value,
createPropertyDescriptionFromFieldInfo(createFieldInfo(props.mergedField, props.parentFieldName)),
createPropertyDescriptionFromFieldInfo(createFieldInfo(propertyField, props.parentFieldName)),
);
record.isMerged = true;
record.isReadonly = true;
record.autoExpand = props.mergedField.isNestedContentField() && props.mergedField.autoExpand;
this.currentPropertiesAppender.append({ record, fieldHierarchy: { field: props.mergedField, childFields: [] } });
record.autoExpand = propertyField.isNestedContentField() && propertyField.autoExpand;
this.currentPropertiesAppender.append({ record, fieldHierarchy: { field: propertyField, childFields: [] } });
}

public processPrimitiveValue(props: ProcessPrimitiveValueProps): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import { IModelConnection } from "@itwin/core-frontend";
import {
addFieldHierarchy, CategoryDescription, ContentFlags, DefaultContentDisplayTypes, Descriptor, DescriptorOverrides, Field, FieldHierarchy,
InstanceKey, NestedContentValue, PropertyValueFormat as PresentationPropertyValueFormat, ProcessFieldHierarchiesProps, ProcessPrimitiveValueProps,
RelationshipMeaning, Ruleset, StartArrayProps, StartCategoryProps, StartContentProps, StartStructProps, traverseContentItem, traverseFieldHierarchy,
Value, ValuesMap,
RelationshipMeaning, Ruleset, StartArrayProps, StartContentProps, StartStructProps, traverseContentItem, traverseFieldHierarchy, Value, ValuesMap,
} from "@itwin/presentation-common";
import { FavoritePropertiesScope, Presentation } from "@itwin/presentation-frontend";
import { FieldHierarchyRecord, IPropertiesAppender, PropertyRecordsBuilder } from "../common/ContentBuilder";
Expand Down Expand Up @@ -309,7 +308,6 @@ class PropertyDataBuilder extends PropertyRecordsBuilder {
private _categoriesCache: PropertyCategoriesCache;
private _categorizedRecords = new Map<string, FieldHierarchyRecord[]>();
private _favoriteFieldHierarchies: FieldHierarchy[] = [];
private _categoriesStack: CategoryDescription[] = [];

constructor(props: PropertyDataBuilderProps) {
super();
Expand All @@ -325,12 +323,7 @@ class PropertyDataBuilder extends PropertyRecordsBuilder {
protected createRootPropertiesAppender(): IPropertiesAppender {
return {
append: (record: FieldHierarchyRecord): void => {
// Note: usually the last category on the stack should be what we want, but in some cases,
// when record's parent is merged, we need another category. In any case it's always expected
// to be on the stack.
const category = this._categoriesStack.find((c) => c.name === record.fieldHierarchy.field.category.name);
assert(category !== undefined);

const category = record.fieldHierarchy.field.category;
let records = this._categorizedRecords.get(category.name);
if (!records) {
records = [];
Expand Down Expand Up @@ -485,12 +478,6 @@ class PropertyDataBuilder extends PropertyRecordsBuilder {
props.hierarchies.push(...this._favoriteFieldHierarchies);
}

public override startCategory(props: StartCategoryProps): boolean {
this._categoriesStack.push(props.category);
return true;
}
public override finishCategory(): void { this._categoriesStack.pop(); }

public override startStruct(props: StartStructProps): boolean {
if (this.shouldSkipField(props.hierarchy.field, () => !Object.keys(props.rawValues).length))
return false;
Expand Down Expand Up @@ -538,6 +525,9 @@ class PropertyCategoriesCache {
constructor(private _enableCategoryNesting: boolean) { }

public initFromDescriptor(descriptor: Descriptor) {
descriptor.categories.forEach((category) => {
this.cache(category);
});
this.initFromFields(descriptor.fields);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Object {
Object {
"expand": false,
"label": "Test Category",
"name": "test-category",
"name": "my-category",
"renderer": Object {
"name": "test",
},
Expand All @@ -26,7 +26,7 @@ Object {
},
},
"records": Object {
"test-category": Array [
"my-category": Array [
PropertyRecord {
"property": Object {
"displayLabel": "Simple Field",
Expand Down Expand Up @@ -87,6 +87,49 @@ Object {
}
`;

exports[`PropertyDataProvider getData with flat categories nested content handling merges parent field when child field's category is different and parent is merged 1`] = `
Object {
"categories": Array [
Object {
"expand": false,
"label": "Test Category",
"name": "Category2",
},
],
"description": undefined,
"label": PropertyRecord {
"property": Object {
"displayLabel": "Label",
"name": "label",
"typename": "string",
},
"value": Object {
"displayValue": "",
"value": "",
"valueFormat": 0,
},
},
"records": Object {
"Category2": Array [
PropertyRecord {
"autoExpand": false,
"isMerged": true,
"isReadonly": true,
"property": Object {
"displayLabel": "Simple Field",
"name": "nested-field-1",
"typename": "string",
},
"value": Object {
"valueFormat": 0,
},
},
],
},
"reusePropertyDataState": true,
}
`;

exports[`PropertyDataProvider getData with flat categories nested content handling moves all nested fields into separate category and hides nested content field when all nested fields are categorized 1`] = `
Object {
"categories": Array [
Expand Down Expand Up @@ -1301,7 +1344,7 @@ Object {
Object {
"expand": false,
"label": "Test Category",
"name": "test-category",
"name": "my-category",
"renderer": Object {
"name": "test",
},
Expand All @@ -1321,7 +1364,7 @@ Object {
},
},
"records": Object {
"test-category": Array [
"my-category": Array [
PropertyRecord {
"property": Object {
"displayLabel": "Simple Field",
Expand Down Expand Up @@ -1382,6 +1425,49 @@ Object {
}
`;

exports[`PropertyDataProvider getData with nested categories nested content handling merges parent field when child field's category is different and parent is merged 1`] = `
Object {
"categories": Array [
Object {
"expand": false,
"label": "Test Category",
"name": "Category2",
},
],
"description": undefined,
"label": PropertyRecord {
"property": Object {
"displayLabel": "Label",
"name": "label",
"typename": "string",
},
"value": Object {
"displayValue": "",
"value": "",
"valueFormat": 0,
},
},
"records": Object {
"Category2": Array [
PropertyRecord {
"autoExpand": false,
"isMerged": true,
"isReadonly": true,
"property": Object {
"displayLabel": "Simple Field",
"name": "nested-field-1",
"typename": "string",
},
"value": Object {
"valueFormat": 0,
},
},
],
},
"reusePropertyDataState": true,
}
`;

exports[`PropertyDataProvider getData with nested categories nested content handling moves all nested fields into separate category and hides nested content field when all nested fields are categorized 1`] = `
Object {
"categories": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ describe("PropertyDataProvider", () => {
it("assigns category renderer", async () => {
const field = createPrimitiveField({
category: createTestCategoryDescription({
name: "my-category",
renderer: { name: "test" },
}),
});
Expand Down Expand Up @@ -797,6 +798,23 @@ describe("PropertyDataProvider", () => {
expect(await provider.getData()).to.matchSnapshot();
});

it("merges parent field when child field's category is different and parent is merged", async () => {
const category1 = createTestCategoryDescription({ name: "Category1" });
const category2 = createTestCategoryDescription({ name: "Category2" });
const nestedField1 = createPrimitiveField({ name: "nested-field-1", category: category2 });
const field = createTestNestedContentField({ name: "root-field", category: category1, nestedFields: [nestedField1] });
const descriptor = createTestContentDescriptor({ categories: [category1, category2], fields: [field] });
const values = {
[field.name]: undefined,
};
const displayValues = {
[field.name]: "*** Varies ***",
};
const record = createTestContentItem({ values, displayValues, mergedFieldNames: [field.name] });
(provider as any).getContent = async () => new Content(descriptor, [record]);
expect(await provider.getData()).to.matchSnapshot();
});

it("moves all nested fields into separate category and hides nested content field when all nested fields are categorized", async () => {
const category1 = createTestCategoryDescription({ name: "Category1" });
const category2 = createTestCategoryDescription({ name: "Category2" });
Expand Down Expand Up @@ -1340,7 +1358,7 @@ describe("PropertyDataProvider", () => {
}]);
});

it("puts properties field parent record into favorites category if property is merged", async () => {
it("puts nested properties field into favorites category when parent field is merged", async () => {
const parentCategory = createTestCategoryDescription({ name: "parent-category", label: "Parent" });
const childCategory = createTestCategoryDescription({ name: "child-category", label: "Child", parent: parentCategory });
const propertiesField = createTestSimpleContentField({ name: "primitive-property", label: "Primitive", category: childCategory });
Expand All @@ -1363,17 +1381,21 @@ describe("PropertyDataProvider", () => {

if (provider.isNestedPropertyCategoryGroupingEnabled) {
const favoritesCategory = data.categories.find((c) => c.name === FAVORITES_CATEGORY_NAME)!;
expect(favoritesCategory.childCategories!.length).to.eq(1);
expect(favoritesCategory.childCategories).to.containSubset([{
label: "Parent",
}]);
expect(data.records[`${FAVORITES_CATEGORY_NAME}-${parentCategory.name}`].length).to.eq(1);
expect(data.records[`${FAVORITES_CATEGORY_NAME}-${parentCategory.name}`]).to.containSubset([{
property: { displayLabel: "Nested Content" },
expect(favoritesCategory).to.containSubset({
childCategories: [{
label: parentCategory.label,
childCategories: [{
label: childCategory.label,
}],
}],
});
expect(data.records[`${FAVORITES_CATEGORY_NAME}-${childCategory.name}`].length).to.eq(1);
expect(data.records[`${FAVORITES_CATEGORY_NAME}-${childCategory.name}`]).to.containSubset([{
property: { displayLabel: propertiesField.label },
}]);
} else {
expect(data.records[FAVORITES_CATEGORY_NAME].length).to.eq(1);
expect(data.records[FAVORITES_CATEGORY_NAME][0].property.displayLabel).to.be.eq(nestedContentField.label);
expect(data.records[FAVORITES_CATEGORY_NAME][0].property.displayLabel).to.eq(propertiesField.label);
}
});

Expand Down