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/default units #657

Merged
merged 14 commits into from
Feb 5, 2021
Merged
8 changes: 8 additions & 0 deletions common/api/presentation-backend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DistinctValuesRequestOptions } from '@bentley/presentation-common';
import { EventSink } from '@bentley/imodeljs-backend';
import { ExtendedContentRequestOptions } from '@bentley/presentation-common';
import { ExtendedHierarchyRequestOptions } from '@bentley/presentation-common';
import { FormatProps } from '@bentley/imodeljs-quantity';
import { HierarchyRequestOptions } from '@bentley/presentation-common';
import { Id64String } from '@bentley/bentleyjs-core';
import { IDisposable } from '@bentley/bentleyjs-core';
Expand Down Expand Up @@ -254,6 +255,13 @@ export interface PresentationManagerProps {
cacheConfig?: HierarchyCacheConfig;
// @alpha (undocumented)
contentCacheSize?: number;
// @alpha
defaultFormats?: {
[phenomenon: string]: {
unitSystems: string[];
grigasp marked this conversation as resolved.
Show resolved Hide resolved
format: FormatProps;
};
};
enableSchemasPreload?: boolean;
// @internal (undocumented)
eventSink?: EventSink;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@bentley/presentation-backend",
"comment": "Added a way to set default property formatting",
"type": "none"
}
],
"packageName": "@bentley/presentation-backend",
"email": "30312645+aurislt7@users.noreply.github.com"
}
2 changes: 1 addition & 1 deletion common/config/rush/browser-approved-packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
},
{
"name": "@bentley/imodeljs-quantity",
"allowedCategories": [ "common", "extensions", "frontend", "integration-testing", "internal" ]
"allowedCategories": [ "backend", "common", "extensions", "frontend", "integration-testing", "internal" ]
},
{
"name": "@bentley/itwin-client",
Expand Down
4 changes: 2 additions & 2 deletions full-stack-tests/presentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"@bentley/imodeljs-backend": "2.12.0-dev.10",
"@bentley/imodeljs-frontend": "2.12.0-dev.10",
"@bentley/oidc-signin-tool": "2.12.0-dev.10",
"@bentley/presentation-common": "2.12.0-dev.10",
"@bentley/presentation-backend": "2.12.0-dev.10",
"@bentley/presentation-common": "2.12.0-dev.10",
"@bentley/presentation-frontend": "2.12.0-dev.10",
"@bentley/presentation-components": "2.12.0-dev.10",
"@bentley/presentation-testing": "2.12.0-dev.10",
Expand Down Expand Up @@ -90,4 +90,4 @@
],
"extends": "plugin:@bentley/imodeljs-recommended"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
grigasp marked this conversation as resolved.
Show resolved Hide resolved
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { expect } from "chai";
import { ClientRequestContext, Guid } from "@bentley/bentleyjs-core";
import { IModelDb, SnapshotDb } from "@bentley/imodeljs-backend";
import { PresentationManager } from "@bentley/presentation-backend";
import { UnitSystemFormat } from "@bentley/presentation-backend/lib/presentation-backend/PresentationManager";
import {
ContentSpecificationTypes, DisplayValuesArray, DisplayValuesMap, findFieldByLabel, KeySet, PresentationUnitSystem, Ruleset, RuleTypes,
} from "@bentley/presentation-common";
import { initialize, terminate } from "../IntegrationTests";

describe("PresentationManager", () => {

let imodel: IModelDb;
before(async () => {
await initialize();
imodel = SnapshotDb.openFile("assets/datasets/Properties_60InstancesWithUrl2.ibim");
expect(imodel).is.not.null;
});

after(async () => {
imodel.close();
await terminate();
});

describe("Property value formatting", () => {

const ruleset: Ruleset = {
id: Guid.createValue(),
rules: [{
ruleType: RuleTypes.Content,
specifications: [{ specType: ContentSpecificationTypes.SelectedNodeInstances }],
}],
};
const keys = KeySet.fromJSON({ instanceKeys: [["Generic:PhysicalObject", ["0x74"]]], nodeKeys: [] });

it("formats property with default kind of quantity format when it doesn't have format for requested unit system", async () => {
await checkExpectedAreaDisplayValue("150.1235 cm²", PresentationUnitSystem.BritishImperial, imodel, keys, ruleset);
});

it("formats property using default format when it doesn't have format for requested unit system", async () => {
const formatProps = {
composite: {
includeZero:true,
spacer:" ",
units: [
{ label:"ft²", name:"SQ_FT" },
],
},
formatTraits:"KeepSingleZero|KeepDecimalPoint|ShowUnitLabel",
precision:4,
type:"Decimal",
uomSeparator:"",
};

const defaultFormats = {
// eslint-disable-next-line @typescript-eslint/naming-convention
AREA: { unitSystems: [PresentationUnitSystem.BritishImperial], format: formatProps },
};

await checkExpectedAreaDisplayValue("0.1616 ft²", PresentationUnitSystem.BritishImperial, imodel, keys, ruleset, defaultFormats);
});

grigasp marked this conversation as resolved.
Show resolved Hide resolved
it("formats property using provided format when it has provided format and default format for requested unit system", async () => {
const formatProps = {
composite: {
includeZero:true,
spacer:" ",
units: [
{ label:"ft²", name:"SQ_FT" },
],
},
formatTraits:"KeepSingleZero|KeepDecimalPoint|ShowUnitLabel",
precision:4,
type:"Decimal",
uomSeparator:"",
};

const defaultFormats = {
// eslint-disable-next-line @typescript-eslint/naming-convention
AREA: { unitSystems: [PresentationUnitSystem.Metric], format: formatProps },
};

await checkExpectedAreaDisplayValue("150.1235 cm²", PresentationUnitSystem.Metric, imodel, keys, ruleset, defaultFormats);
});
});

});

async function checkExpectedAreaDisplayValue(value: string, unitSystem: PresentationUnitSystem, imodel: IModelDb, keys: KeySet, ruleset: Ruleset, defaultFormats?: {[phenomenon: string]: UnitSystemFormat} ){
grigasp marked this conversation as resolved.
Show resolved Hide resolved
const manager: PresentationManager = new PresentationManager({defaultFormats});
const descriptor = await manager.getContentDescriptor({
imodel,
rulesetOrId: ruleset,
keys,
displayType: "Grid",
requestContext: new ClientRequestContext(),
unitSystem,
});
expect(descriptor).to.not.be.undefined;
const field = findFieldByLabel(descriptor!.fields, "cm2")!;
expect(field).not.to.be.undefined;
const content = await manager.getContent({ imodel, rulesetOrId: ruleset, keys, descriptor: descriptor!, requestContext: new ClientRequestContext(), unitSystem });
const displayValues = content!.contentSet[0].displayValues.rc_generic_PhysicalObject_ncc_MyProp_areaElementAspect as DisplayValuesArray;
expect(displayValues.length).is.eq(1);
expect(((displayValues[0] as DisplayValuesMap).displayValues as DisplayValuesMap)[field.name]!).to.eq(value);
}
25 changes: 1 addition & 24 deletions full-stack-tests/presentation/src/frontend/Content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { expect } from "chai";
import { Guid, Id64, Id64String } from "@bentley/bentleyjs-core";
import { IModelConnection, SnapshotConnection } from "@bentley/imodeljs-frontend";
import {
ContentSpecificationTypes, DefaultContentDisplayTypes, Descriptor, DisplayValueGroup, Field, FieldDescriptor, InstanceKey, KeySet,
ContentSpecificationTypes, DefaultContentDisplayTypes, Descriptor, DisplayValueGroup, Field, FieldDescriptor, findFieldByLabel, InstanceKey, KeySet,
NestedContentField, PresentationError, PresentationStatus, RelationshipDirection, Ruleset, RuleTypes,
} from "@bentley/presentation-common";
import { Presentation } from "@bentley/presentation-frontend";
Expand Down Expand Up @@ -574,26 +574,3 @@ class ECClassHierarchy {
};
}
}

function findFieldByLabel(fields: Field[], label: string, allFields?: Field[]): Field | undefined {
const isTopLevel = (undefined === allFields);
if (!allFields)
allFields = new Array<Field>();
for (const field of fields) {
if (field.label === label)
return field;

if (field.isNestedContentField()) {
const nestedMatchingField = findFieldByLabel(field.nestedFields, label, allFields);
if (nestedMatchingField)
return nestedMatchingField;
}

allFields.push(field);
}
if (isTopLevel) {
// eslint-disable-next-line no-console
console.error(`Field '${label}' not found. Available fields: [${allFields.map((f) => `"${f.label}"`).join(", ")}]`);
}
return undefined;
}
4 changes: 3 additions & 1 deletion presentation/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@bentley/bentleyjs-core": "^2.12.0-dev.10",
"@bentley/imodeljs-backend": "^2.12.0-dev.10",
"@bentley/imodeljs-common": "^2.12.0-dev.10",
"@bentley/imodeljs-quantity": "^2.12.0-dev.10",
"@bentley/presentation-common": "^2.12.0-dev.10"
},
"devDependencies": {
Expand All @@ -48,6 +49,7 @@
"@bentley/eslint-plugin": "2.12.0-dev.10",
"@bentley/imodeljs-backend": "2.12.0-dev.10",
"@bentley/imodeljs-common": "2.12.0-dev.10",
"@bentley/imodeljs-quantity": "2.12.0-dev.10",
"@bentley/presentation-common": "2.12.0-dev.10",
"@types/chai": "^4.1.4",
"@types/chai-as-promised": "^7",
Expand Down Expand Up @@ -96,4 +98,4 @@
],
"extends": "plugin:@bentley/imodeljs-recommended"
}
}
}
20 changes: 18 additions & 2 deletions presentation/backend/src/presentation-backend/NativePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { IModelDb, IModelHost, IModelJsNative } from "@bentley/imodeljs-backend"
import {
DiagnosticsScopeLogs, PresentationError, PresentationStatus, UpdateInfoJSON, VariableValueJSON, VariableValueTypes,
} from "@bentley/presentation-common";
import { HierarchyCacheMode, PresentationManagerMode } from "./PresentationManager";
import { HierarchyCacheMode, PresentationManagerMode, UnitSystemFormat } from "./PresentationManager";

/** @internal */
export enum NativePlatformRequestTypes {
Expand Down Expand Up @@ -68,6 +68,7 @@ export interface DefaultNativePlatformProps {
isChangeTrackingEnabled: boolean;
cacheConfig?: IModelJsNative.ECPresentationHierarchyCacheConfig;
contentCacheSize?: number;
defaultFormats?: { [phenomenon: string]: UnitSystemFormat };
}

/** @internal */
Expand All @@ -79,7 +80,8 @@ export const createDefaultNativePlatform = (props: DefaultNativePlatformProps):
public constructor() {
const mode = (props.mode === PresentationManagerMode.ReadOnly) ? IModelJsNative.ECPresentationManagerMode.ReadOnly : IModelJsNative.ECPresentationManagerMode.ReadWrite;
const cacheConfig = props.cacheConfig ?? { mode: HierarchyCacheMode.Disk, directory: "" };
this._nativeAddon = new IModelHost.platform.ECPresentationManager({ ...props, mode, cacheConfig });
const defaultFormats = props.defaultFormats ? this.getSerializedDefaultFormatsMap(props.defaultFormats) : {};
this._nativeAddon = new IModelHost.platform.ECPresentationManager({ ...props, mode, cacheConfig, defaultFormats });
}
private getStatus(responseStatus: IModelJsNative.ECPresentationStatus): PresentationStatus {
switch (responseStatus) {
Expand All @@ -88,6 +90,20 @@ export const createDefaultNativePlatform = (props: DefaultNativePlatformProps):
default: return PresentationStatus.Error;
}
}
private getSerializedDefaultFormatsMap(defaultMap: { [phenomenon: string]: UnitSystemFormat }){
const res: {
[phenomenon: string]: {
unitSystems: string[];
serializedFormat: string;
};
} = {};
Object.keys(defaultMap).forEach((key) => {
const value = defaultMap[key];
res[key] = {unitSystems: value.unitSystems, serializedFormat: JSON.stringify(value.format)};
});

return res;
}
private createSuccessResponse<T>(response: IModelJsNative.ECPresentationManagerResponse<T>): NativePlatformResponse<T> {
const retValue: NativePlatformResponse<T> = { result: response.result! };
if (response.diagnostics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as hash from "object-hash";
import * as path from "path";
import { ClientRequestContext, Id64String, Logger } from "@bentley/bentleyjs-core";
import { BriefcaseDb, EventSink, IModelDb, IModelHost, IModelJsNative } from "@bentley/imodeljs-backend";
import { FormatProps } from "@bentley/imodeljs-quantity";
import {
Content, ContentDescriptorRequestOptions, ContentFlags, ContentRequestOptions, DefaultContentDisplayTypes, Descriptor, DescriptorOverrides,
DisplayLabelRequestOptions, DisplayLabelsRequestOptions, DisplayValueGroup, DistinctValuesRequestOptions, ExtendedContentRequestOptions,
Expand Down Expand Up @@ -112,6 +113,16 @@ export interface HybridCacheConfig extends HierarchyCacheConfigBase {
disk?: DiskHierarchyCacheConfig;
}

/**
* A data structure that associates some unit systems with a format. The associations are used for
* assigning default unit formats for specific phenomenons (see [[PresentationManagerProps.defaultFormats]])
* @alpha
*/
export interface UnitSystemFormat {
unitSystems: PresentationUnitSystem[];
format: FormatProps;
};

/**
* Properties that can be used to configure [[PresentationManager]]
* @public
Expand Down Expand Up @@ -246,6 +257,14 @@ export interface PresentationManagerProps {
/** @alpha */
contentCacheSize?: number;

/**
* A map of default unit formats to use for formatting properties that don't have a presentation format
* in requested unit system.
* @alpha */
defaultFormats?: {
[phenomenon: string]: UnitSystemFormat;
};

/**
* An identifier which helps separate multiple presentation managers. It's
* mostly useful in tests where multiple presentation managers can co-exist
Expand Down Expand Up @@ -307,6 +326,7 @@ export class PresentationManager {
isChangeTrackingEnabled,
cacheConfig: createCacheConfig(this._props.cacheConfig),
contentCacheSize: this._props.contentCacheSize,
defaultFormats: this._props.defaultFormats,
});
this._nativePlatform = new nativePlatformImpl();
}
Expand Down
Loading