Skip to content

Commit

Permalink
Add ability to get content field by property description (#98)
Browse files Browse the repository at this point in the history
* Add ability to get content field by `PropertyDescription`

* Change files
  • Loading branch information
grigasp committed Mar 13, 2023
1 parent 2e1be15 commit 4c31428
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 55 deletions.
Expand Up @@ -196,7 +196,7 @@ interface PropertiesWidgetContextMenuProps {
onCloseContextMenu: () => void;
}
function PropertiesWidgetContextMenu(props: PropertiesWidgetContextMenuProps) {
const { dataProvider, args: { propertyRecord: property }, onCloseContextMenu } = props;
const { dataProvider, args: { propertyRecord: record }, onCloseContextMenu } = props;
const imodel = dataProvider.imodel;

const addFavorite = useCallback(async (propertyField: Field) => {
Expand All @@ -210,7 +210,7 @@ function PropertiesWidgetContextMenu(props: PropertiesWidgetContextMenuProps) {
}, [onCloseContextMenu, imodel]);

const asyncItems = useDebouncedAsyncValue(useCallback(async () => {
const field = await dataProvider.getFieldByPropertyRecord(property);
const field = await dataProvider.getFieldByPropertyDescription(record.property);
const items: ContextMenuItemInfo[] = [];
if (field !== undefined) {
if (Presentation.favoriteProperties.has(field, imodel, FAVORITES_SCOPE)) {
Expand All @@ -230,7 +230,7 @@ function PropertiesWidgetContextMenu(props: PropertiesWidgetContextMenuProps) {
}
}
return items;
}, [imodel, dataProvider, property, addFavorite, removeFavorite]));
}, [imodel, dataProvider, record, addFavorite, removeFavorite]));

if (!asyncItems.value || asyncItems.value.length === 0)
return null;
Expand All @@ -254,8 +254,8 @@ function PropertiesWidgetContextMenu(props: PropertiesWidgetContextMenuProps) {
}

function FavoritePropertyActionButton(props: ActionButtonRendererProps & { dataProvider: PresentationPropertyDataProvider }) {
const { property, dataProvider } = props;
const field = useAsyncValue(useMemo(async () => dataProvider.getFieldByPropertyRecord(property), [dataProvider, property]));
const { property: record, dataProvider } = props;
const field = useAsyncValue(useMemo(async () => dataProvider.getFieldByPropertyDescription(record.property), [dataProvider, record.property]));
return (
<div>
{
Expand Down
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Deprecate `IContentDataProvider.getFieldByPropertyRecord` in favor of the new `IContentDataProvider.getFieldByPropertyDescription`.",
"packageName": "@itwin/presentation-components",
"email": "35135765+grigasp@users.noreply.github.com",
"dependentChangeType": "patch"
}
Expand Up @@ -7,7 +7,7 @@
*/

import memoize from "micro-memoize";
import { PropertyRecord } from "@itwin/appui-abstract";
import { PropertyDescription, PropertyRecord } from "@itwin/appui-abstract";
import { Logger } from "@itwin/core-bentley";
import { IModelConnection } from "@itwin/core-frontend";
import {
Expand Down Expand Up @@ -93,8 +93,14 @@ export interface IContentDataProvider extends IPresentationDataProvider {
*/
getContent: (pageOptions?: PageOptions) => Promise<Content | undefined>;

/** Get field that was used to create the given property record */
/**
* Get field that was used to create the given property record.
* @deprecated in 4.0. Use [[getFieldByPropertyDescription]] instead.
*/
getFieldByPropertyRecord: (propertyRecord: PropertyRecord) => Promise<Field | undefined>;

/** Get field that was used to create a property record with given property description. */
getFieldByPropertyDescription: (descr: PropertyDescription) => Promise<Field | undefined>;
}

/**
Expand Down Expand Up @@ -330,10 +336,16 @@ export class ContentDataProvider implements IContentDataProvider {

/**
* Get field using PropertyRecord.
* @deprecated in 4.0. Use [[getFieldByPropertyDescription]] instead.
*/
public async getFieldByPropertyRecord(propertyRecord: PropertyRecord): Promise<Field | undefined> {
return this.getFieldByPropertyDescription(propertyRecord.property);
}

/** Get field that was used to create a property record with given property description. */
public async getFieldByPropertyDescription(descr: PropertyDescription): Promise<Field | undefined> {
const descriptor = await this.getContentDescriptor();
return descriptor ? findField(descriptor, propertyRecord.property.name) : undefined;
return descriptor ? findField(descriptor, descr.name) : undefined;
}

private _getContentAndSize = memoize(async (pageOptions?: PageOptions): Promise<{ content: Content, size: number } | undefined> => {
Expand Down
Expand Up @@ -8,7 +8,7 @@
*/

import { createContext, createRef, forwardRef, PureComponent, useContext, useMemo } from "react";
import { PropertyDescription, PropertyRecord, PropertyValueFormat, StandardTypeNames } from "@itwin/appui-abstract";
import { PropertyDescription, StandardTypeNames } from "@itwin/appui-abstract";
import { PropertyEditorBase, PropertyEditorManager, PropertyEditorProps, TypeEditor } from "@itwin/components-react";
import { IModelConnection } from "@itwin/core-frontend";
import { NavigationPropertyInfo } from "@itwin/presentation-common";
Expand Down Expand Up @@ -64,7 +64,7 @@ export function useNavigationPropertyEditingContext(imodel: IModelConnection, da
return useMemo<NavigationPropertyEditorContextProps>(() => ({
imodel,
getNavigationPropertyInfo: async (property) => {
const field = await dataProvider.getFieldByPropertyRecord(new PropertyRecord({ valueFormat: PropertyValueFormat.Primitive }, property));
const field = await dataProvider.getFieldByPropertyDescription(property);
if (!field || !field.isPropertiesField())
return undefined;
return field.properties[0].property.navigationPropertyInfo;
Expand Down
Expand Up @@ -59,7 +59,7 @@ export class FavoritePropertiesDataFilterer extends PropertyDataFiltererBase {
}

private async isFavorite(record: PropertyRecord): Promise<boolean> {
const field = await this._source.getFieldByPropertyRecord(record);
const field = await this._source.getFieldByPropertyDescription(record.property);
return !!field && this._favoritesCheckCallback(field, this._source.imodel, this._favoritesScope);
}

Expand Down
50 changes: 37 additions & 13 deletions packages/components/src/test/common/ContentDataProvider.test.ts
Expand Up @@ -451,26 +451,50 @@ describe("ContentDataProvider", () => {

});

describe("getFieldByPropertyRecord", () => {
describe("[deprecated] getFieldByPropertyRecord", () => {

let propertyRecord: PropertyRecord;

before(() => {
it("passes record's description to `getFieldByPropertyDescription`", async () => {
const value: PrimitiveValue = {
displayValue: "displayValue",
value: "rawValue",
valueFormat: 0,
};

const description: PropertyDescription = {
name: "propertyName",
displayLabel: "labelString",
typename: "number",
editor: undefined,
};
const record = new PropertyRecord(value, description);

const field = createTestPropertiesContentField({
name: "test-field",
properties: [{
property: createTestPropertyInfo({ name: "test-property" }),
}],
});
provider.getFieldByPropertyDescription = sinon.fake(async () => field);

// eslint-disable-next-line deprecation/deprecation
const actualField = await provider.getFieldByPropertyRecord(record);

propertyRecord = new PropertyRecord(value, description);
propertyRecord.isReadonly = false;
expect(provider.getFieldByPropertyDescription).to.be.calledOnceWith(record.property);
expect(actualField).to.eq(field);
});

});

describe("getFieldByPropertyDescription", () => {

let propertyDescription: PropertyDescription;

before(() => {
propertyDescription = {
name: "propertyName",
displayLabel: "labelString",
typename: "number",
editor: undefined,
};
});

beforeEach(() => {
Expand All @@ -483,7 +507,7 @@ describe("ContentDataProvider", () => {
.returns(async () => undefined)
.verifiable(moq.Times.once());

const field = await provider.getFieldByPropertyRecord(propertyRecord);
const field = await provider.getFieldByPropertyDescription(propertyDescription);
presentationManagerMock.verifyAll();
expect(field).to.be.undefined;
});
Expand All @@ -496,7 +520,7 @@ describe("ContentDataProvider", () => {
.returns(async () => descriptor)
.verifiable(moq.Times.once());

const resultField = await provider.getFieldByPropertyRecord(propertyRecord);
const resultField = await provider.getFieldByPropertyDescription(propertyDescription);
presentationManagerMock.verifyAll();
expect(resultField).to.be.undefined;
});
Expand All @@ -509,11 +533,11 @@ describe("ContentDataProvider", () => {
}],
});
const descriptor = createTestContentDescriptor({ fields: [field] });
propertyRecord.property.name = "test-field";
propertyDescription.name = "test-field";

presentationManagerMock.setup(async (x) => x.getContentDescriptor(moq.It.isAny())).returns(async () => descriptor).verifiable(moq.Times.once());

const resultField = await provider.getFieldByPropertyRecord(propertyRecord);
const resultField = await provider.getFieldByPropertyDescription(propertyDescription);
presentationManagerMock.verifyAll();
expect(resultField).to.eq(field);
});
Expand All @@ -525,11 +549,11 @@ describe("ContentDataProvider", () => {
nestedFields: [nestedField],
});
const descriptor = createTestContentDescriptor({ fields: [nestingField] });
propertyRecord.property.name = `${nestingField.name}${FIELD_NAMES_SEPARATOR}${nestedField.name}`;
propertyDescription.name = `${nestingField.name}${FIELD_NAMES_SEPARATOR}${nestedField.name}`;

presentationManagerMock.setup(async (x) => x.getContentDescriptor(moq.It.isAny())).returns(async () => descriptor).verifiable(moq.Times.once());

const resultField = await provider.getFieldByPropertyRecord(propertyRecord);
const resultField = await provider.getFieldByPropertyDescription(propertyDescription);
presentationManagerMock.verifyAll();
expect(resultField).to.eq(nestedField);
});
Expand Down
Expand Up @@ -158,6 +158,7 @@ describe("useNavigationPropertyEditingContext", () => {
getContentDescriptor: async () => undefined,
getContentSetSize: async () => 0,
getFieldByPropertyRecord: async () => undefined,
getFieldByPropertyDescription: async () => undefined,
keys: new KeySet(),
selectionInfo: undefined,
};
Expand All @@ -172,7 +173,7 @@ describe("useNavigationPropertyEditingContext", () => {
isTargetPolymorphic: true,
};

testDataProvider.getFieldByPropertyRecord = async () => createTestPropertiesContentField({
testDataProvider.getFieldByPropertyDescription = async () => createTestPropertiesContentField({
properties: [{
property: {
classInfo: { id: "3", label: "Field Class", name: "TestSchema:FieldClass" },
Expand All @@ -194,7 +195,7 @@ describe("useNavigationPropertyEditingContext", () => {

it("returns undefined if non properties field is returned", async () => {
const propertyDescription: PropertyDescription = { displayLabel: "TestProp", name: "test_prop", typename: "navigation" };
testDataProvider.getFieldByPropertyRecord = async () => createTestSimpleContentField();
testDataProvider.getFieldByPropertyDescription = async () => createTestSimpleContentField();

const { result } = renderHook(
({ imodel, dataProvider }: Props) => useNavigationPropertyEditingContext(imodel, dataProvider),
Expand Down
Expand Up @@ -6,7 +6,7 @@
import { expect } from "chai";
import sinon from "sinon";
import * as moq from "typemoq";
import { PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
import { PropertyDescription, PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
import { IModelConnection } from "@itwin/core-frontend";
import { Field } from "@itwin/presentation-common";
import { FavoritePropertiesManager, FavoritePropertiesScope, Presentation } from "@itwin/presentation-frontend";
Expand All @@ -20,7 +20,7 @@ describe("FavoritePropertiesDataFilterer", () => {
let matchingField: Field | undefined;
beforeEach(() => {
mockDataProvider = moq.Mock.ofType<IPresentationPropertyDataProvider>();
mockDataProvider.setup(async (x) => x.getFieldByPropertyRecord(moq.It.isAny())).returns(async () => matchingField);
mockDataProvider.setup(async (x) => x.getFieldByPropertyDescription(moq.It.isAny())).returns(async () => matchingField);
mockDataProvider.setup((x) => x.imodel).returns(() => moq.Mock.ofType<IModelConnection>().object);
matchingField = undefined;
});
Expand All @@ -29,7 +29,7 @@ describe("FavoritePropertiesDataFilterer", () => {
sinon.restore();
});

it("uses FavoritePropertiesManager to determine favorites if callback is not provided through props", async () => {
it("uses `FavoritePropertiesManager` to determine favorites if callback is not provided through props", async () => {
const record = createPrimitiveStringProperty("Property", "Value");
matchingField = createTestSimpleContentField();

Expand Down Expand Up @@ -89,13 +89,13 @@ describe("FavoritePropertiesDataFilterer", () => {

for (const record of recordsToTest) {
const recordType = PropertyValueFormat[record.value.valueFormat];
it(`Should always match propertyRecord (type: ${recordType})`, async () => {
it(`should always match \`propertyRecord\` (type: ${recordType})`, async () => {
const matchResult = await filterer.recordMatchesFilter(record, []);
expect(matchResult).to.deep.eq({ matchesFilter: true });
});
}

it(`Should always return 'matchesFilter: true' when calling categoryMatchesFilter`, async () => {
it(`should always return \`'matchesFilter: true\` when calling \`categoryMatchesFilter\``, async () => {
const matchResult = await filterer.categoryMatchesFilter();
expect(matchResult).to.deep.eq({ matchesFilter: true });
});
Expand Down Expand Up @@ -123,39 +123,39 @@ describe("FavoritePropertiesDataFilterer", () => {
for (const record of recordsToTest) {
const recordType = PropertyValueFormat[record.value.valueFormat];

it(`Should not match propertyRecord when getFieldByPropertyRecord cannot find record field (type: ${recordType})`, async () => {
it(`should not match propertyRecord when \`getFieldByPropertyDescription\` cannot find record field (type: ${recordType})`, async () => {
matchingField = undefined;
const matchResult = await filterer.recordMatchesFilter(record, []);
expect(matchResult).to.deep.eq({ matchesFilter: false });
});

it(`Should not match propertyRecord when record is not favorite and has no parents (type: ${recordType})`, async () => {
it(`should not match \`propertyRecord\` when record is not favorite and has no parents (type: ${recordType})`, async () => {
isFavoriteStub.returns(false);
matchingField = createTestSimpleContentField();
const matchResult = await filterer.recordMatchesFilter(record, []);
expect(matchResult).to.deep.eq({ matchesFilter: false });
});

it(`Should not match propertyRecord when record is not favorite and has non favorite parents (type: ${recordType})`, async () => {
it(`should not match \`propertyRecord\` when record is not favorite and has non favorite parents (type: ${recordType})`, async () => {
isFavoriteStub.returns(false);
matchingField = createTestSimpleContentField();
const matchResult = await filterer.recordMatchesFilter(record, [createStructProperty("Struct"), createArrayProperty("Array")]);
expect(matchResult).to.deep.eq({ matchesFilter: false });
});

it(`Should match propertyRecord when record is favorite and has no parents (type: ${recordType})`, async () => {
it(`should match \`propertyRecord\` when record is favorite and has no parents (type: ${recordType})`, async () => {
isFavoriteStub.returns(true);
matchingField = createTestSimpleContentField();
const matchResult = await filterer.recordMatchesFilter(record, []);
expect(matchResult).to.deep.eq({ matchesFilter: true, shouldExpandNodeParents: true });
});

it(`Should match propertyRecord when record is not favorite and has favorite parents (type: ${recordType})`, async () => {
it(`should match \`propertyRecord\` when record is not favorite and has favorite parents (type: ${recordType})`, async () => {
const favoriteParentRecord = createStructProperty("FavoriteStruct");
const favoriteParentField = createTestSimpleContentField();
mockDataProvider.reset();
mockDataProvider.setup(async (x) => x.getFieldByPropertyRecord(moq.It.isAny())).returns(async (argRecord: PropertyRecord) => {
if (argRecord.property.name === favoriteParentRecord.property.name)
mockDataProvider.setup(async (x) => x.getFieldByPropertyDescription(moq.It.isAny())).returns(async (arg: PropertyDescription) => {
if (arg.name === favoriteParentRecord.property.name)
return favoriteParentField;
return createTestSimpleContentField();
});
Expand All @@ -168,7 +168,7 @@ describe("FavoritePropertiesDataFilterer", () => {
});
}

it("Should not match when calling `categoryMatchesFilter`", async () => {
it("should not match when calling `categoryMatchesFilter`", async () => {
const matchResult = await filterer.categoryMatchesFilter();
expect(matchResult).to.deep.eq({ matchesFilter: false });
});
Expand Down

0 comments on commit 4c31428

Please sign in to comment.