Skip to content

Commit

Permalink
Add property constraints (#7534)
Browse files Browse the repository at this point in the history
Co-authored-by: Grigas <[email protected]>
Co-authored-by: imodeljs-admin <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2025
1 parent 4148165 commit 546a0a4
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 0 deletions.
28 changes: 28 additions & 0 deletions common/api/presentation-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ export interface ArrayPropertiesFieldJSON<TClassInfoJSON = ClassInfo> extends Pr
itemsField: PropertiesFieldJSON<TClassInfoJSON>;
}

// @public
export interface ArrayPropertyValueConstraints {
// (undocumented)
maxOccurs?: number;
// (undocumented)
minOccurs?: number;
}

// @public
export interface ArrayTypeDescription extends BaseTypeDescription {
memberType: TypeDescription;
Expand Down Expand Up @@ -2122,6 +2130,14 @@ export interface NodeUpdateInfoJSON {
type: "Update";
}

// @public
export interface NumericPropertyValueConstraints {
// (undocumented)
maximumValue?: number;
// (undocumented)
minimumValue?: number;
}

// @public
type Omit_2<T, K> = Pick<T, Exclude<keyof T, K>>;
export { Omit_2 as Omit }
Expand Down Expand Up @@ -2464,6 +2480,7 @@ export enum PropertyGroupingValue {
// @public
export interface PropertyInfo {
classInfo: ClassInfo;
constraints?: PropertyValueConstraints;
enumerationInfo?: EnumerationInfo;
extendedType?: string;
kindOfQuantity?: KindOfQuantityInfo;
Expand Down Expand Up @@ -2538,6 +2555,9 @@ export interface PropertySpecification extends PropertyOverrides {
name: string;
}

// @public
export type PropertyValueConstraints = StringPropertyValueConstraints | ArrayPropertyValueConstraints | NumericPropertyValueConstraints;

// @public
export enum PropertyValueFormat {
Array = "Array",
Expand Down Expand Up @@ -3111,6 +3131,14 @@ export interface StartStructProps {
valueType: TypeDescription;
}

// @public
export interface StringPropertyValueConstraints {
// (undocumented)
maximumLength?: number;
// (undocumented)
minimumLength?: number;
}

// @public
export interface StringQuerySpecification extends QuerySpecificationBase {
query: string;
Expand Down
4 changes: 4 additions & 0 deletions common/api/summary/presentation-common.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Release Tag;API Item Type;API Item Name
public;function;addFieldHierarchy
public;class;ArrayPropertiesField
public;interface;ArrayPropertiesFieldJSON
public;interface;ArrayPropertyValueConstraints
public;interface;ArrayTypeDescription
internal;class;AsyncTasksTracker
public;interface;BaseFieldJSON
Expand Down Expand Up @@ -297,6 +298,7 @@ deprecated;interface;NodePathFilteringDataJSON
public;interface;NodeUpdateInfo
public;interface;NodeUpdateInfoJSON
deprecated;interface;NodeUpdateInfoJSON
public;interface;NumericPropertyValueConstraints
public;type;Paged
public;interface;PagedResponse
public;interface;PageOptions
Expand Down Expand Up @@ -354,6 +356,7 @@ public;interface;PropertyOverrides
public;interface;PropertyRangeGroupSpecification
public;interface;PropertySortingRule
public;interface;PropertySpecification
public;type;PropertyValueConstraints
public;enum;PropertyValueFormat
public;type;QuerySpecification
public;interface;QuerySpecificationBase
Expand Down Expand Up @@ -428,6 +431,7 @@ public;interface;StartContentProps
public;interface;StartFieldProps
public;interface;StartItemProps
public;interface;StartStructProps
public;interface;StringPropertyValueConstraints
public;interface;StringQuerySpecification
public;interface;StringRulesetVariable
public;interface;StringRulesetVariableJSON
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-backend",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-backend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/presentation-common",
"comment": "Add value constraints to `PropertyInfo`",
"type": "none"
}
],
"packageName": "@itwin/presentation-common"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*---------------------------------------------------------------------------------------------
* 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 { assert, Guid, using } from "@itwin/core-bentley";
import { IModelConnection } from "@itwin/core-frontend";
import { Content, ContentSpecificationTypes, DefaultContentDisplayTypes, InstanceKey, KeySet, Ruleset, RuleTypes } from "@itwin/presentation-common";
import { PresentationManager } from "@itwin/presentation-frontend";
import {
buildTestIModelConnection,
importSchema,
insertPhysicalElement,
insertPhysicalModelWithPartition,
insertSpatialCategory,
} from "../../IModelSetupUtils";
import { collect, getFieldByLabel } from "../../Utils";
import { describeContentTestSuite } from "./Utils";

describeContentTestSuite("Primitive properties", () => {
it("sets constraints for numeric type properties", async function () {
let elementKey!: InstanceKey;
const imodel = await buildTestIModelConnection(this.test!.title, async (db) => {
const schema = importSchema(
this,
db,
`
<ECSchemaReference name="BisCore" version="01.00.16" alias="bis" />
<ECEntityClass typeName="X">
<BaseClass>bis:PhysicalElement</BaseClass>
<ECProperty propertyName="Prop1" typeName="int" />
<ECProperty propertyName="Prop2" typeName="int" minimumValue="0" maximumValue="2" />
<ECProperty propertyName="Prop3" typeName="long" minimumValue="123456789876" />
<ECProperty propertyName="Prop4" typeName="double" maximumValue="2.6" />
</ECEntityClass>
`,
);
const model = insertPhysicalModelWithPartition({ db, codeValue: "model" });
const category = insertSpatialCategory({ db, codeValue: "category" });
elementKey = insertPhysicalElement({
db,
classFullName: schema.items.X.fullName,
modelId: model.id,
categoryId: category.id,
});
});

const content = await getContent(imodel, elementKey);
const field1 = getFieldByLabel(content.descriptor.fields, "Prop1");
const field2 = getFieldByLabel(content.descriptor.fields, "Prop2");
const field3 = getFieldByLabel(content.descriptor.fields, "Prop3");
const field4 = getFieldByLabel(content.descriptor.fields, "Prop4");

assert(field1.isPropertiesField());
assert(field2.isPropertiesField());
assert(field3.isPropertiesField());
assert(field4.isPropertiesField());

expect(field1.properties[0].property.constraints).to.be.undefined;
expect(field2.properties[0].property.constraints).to.deep.eq({ minimumValue: 0, maximumValue: 2 });
expect(field3.properties[0].property.constraints).to.deep.eq({ minimumValue: 123456789876 });
expect(field4.properties[0].property.constraints).to.deep.eq({ maximumValue: 2.6 });
});

it("sets constraints for string type properties", async function () {
let elementKey!: InstanceKey;
const imodel = await buildTestIModelConnection(this.test!.title, async (db) => {
const schema = importSchema(
this,
db,
`
<ECSchemaReference name="BisCore" version="01.00.16" alias="bis" />
<ECEntityClass typeName="X">
<BaseClass>bis:PhysicalElement</BaseClass>
<ECProperty propertyName="Prop1" typeName="string" />
<ECProperty propertyName="Prop2" typeName="string" minimumLength="1" maximumLength="5" />
<ECProperty propertyName="Prop3" typeName="string" minimumLength="1" />
<ECProperty propertyName="Prop4" typeName="string" maximumLength="5" />
</ECEntityClass>
`,
);
const model = insertPhysicalModelWithPartition({ db, codeValue: "model" });
const category = insertSpatialCategory({ db, codeValue: "category" });
elementKey = insertPhysicalElement({
db,
classFullName: schema.items.X.fullName,
modelId: model.id,
categoryId: category.id,
});
});

const content = await getContent(imodel, elementKey);
const field1 = getFieldByLabel(content.descriptor.fields, "Prop1");
const field2 = getFieldByLabel(content.descriptor.fields, "Prop2");
const field3 = getFieldByLabel(content.descriptor.fields, "Prop3");
const field4 = getFieldByLabel(content.descriptor.fields, "Prop4");

assert(field1.isPropertiesField());
assert(field2.isPropertiesField());
assert(field3.isPropertiesField());
assert(field4.isPropertiesField());

expect(field1.properties[0].property.constraints).to.be.undefined;
expect(field2.properties[0].property.constraints).to.deep.eq({ minimumLength: 1, maximumLength: 5 });
expect(field3.properties[0].property.constraints).to.deep.eq({ minimumLength: 1 });
expect(field4.properties[0].property.constraints).to.deep.eq({ maximumLength: 5 });
});

it("sets constraints for array type properties", async function () {
let elementKey!: InstanceKey;
const imodel = await buildTestIModelConnection(this.test!.title, async (db) => {
const schema = importSchema(
this,
db,
`
<ECSchemaReference name="BisCore" version="01.00.16" alias="bis" />
<ECEntityClass typeName="X">
<BaseClass>bis:PhysicalElement</BaseClass>
<ECArrayProperty propertyName="Prop1" typeName="string" />
<ECArrayProperty propertyName="Prop2" typeName="string" minOccurs="1" maxOccurs="unbounded" />
<ECArrayProperty propertyName="Prop3" typeName="string" minOccurs="1" />
<ECArrayProperty propertyName="Prop4" typeName="string" maxOccurs="5" />
</ECEntityClass>
`,
);
const model = insertPhysicalModelWithPartition({ db, codeValue: "model" });
const category = insertSpatialCategory({ db, codeValue: "category" });
elementKey = insertPhysicalElement({
db,
classFullName: schema.items.X.fullName,
modelId: model.id,
categoryId: category.id,
});
});

const content = await getContent(imodel, elementKey);
const field1 = getFieldByLabel(content.descriptor.fields, "Prop1");
const field2 = getFieldByLabel(content.descriptor.fields, "Prop2");
const field3 = getFieldByLabel(content.descriptor.fields, "Prop3");
const field4 = getFieldByLabel(content.descriptor.fields, "Prop4");

assert(field1.isPropertiesField());
assert(field2.isPropertiesField());
assert(field3.isPropertiesField());
assert(field4.isPropertiesField());

// ECArrayProperty doesn't have a way to determine if minOccurs is defined. By default minOccurs is set to 0.
// If maxOccurs is set to "unbounded" then maxOccurs is set to undefined.
expect(field1.properties[0].property.constraints).to.deep.eq({ minOccurs: 0 });
expect(field2.properties[0].property.constraints).to.deep.eq({ minOccurs: 1 });
expect(field3.properties[0].property.constraints).to.deep.eq({ minOccurs: 1 });
expect(field4.properties[0].property.constraints).to.deep.eq({ minOccurs: 0, maxOccurs: 5 });
});
});

async function getContent(imodel: IModelConnection, key: InstanceKey): Promise<Content> {
const keys = new KeySet([key]);
const ruleset: Ruleset = {
id: Guid.createValue(),
rules: [
{
ruleType: RuleTypes.Content,
specifications: [{ specType: ContentSpecificationTypes.SelectedNodeInstances }],
},
],
};

return using(PresentationManager.create(), async (manager) => {
const descriptor = await manager.getContentDescriptor({
imodel,
rulesetOrId: ruleset,
keys,
displayType: DefaultContentDisplayTypes.Grid,
});
expect(descriptor).to.not.be.undefined;
const content = await manager
.getContentIterator({ imodel, rulesetOrId: ruleset, keys, descriptor: descriptor! })
.then(async (x) => x && new Content(x.descriptor, await collect(x.items)));
expect(content).to.not.be.undefined;
return content!;
});
}
35 changes: 35 additions & 0 deletions presentation/common/src/presentation-common/EC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,41 @@ export interface PropertyInfo {
extendedType?: string;
/** Navigation property info if the field is navigation type */
navigationPropertyInfo?: NavigationPropertyInfo;
/** Constraints for values of ECProperty */
constraints?: PropertyValueConstraints;
}

/**
* Constraints for values of ECProperty
* @public
*/
export type PropertyValueConstraints = StringPropertyValueConstraints | ArrayPropertyValueConstraints | NumericPropertyValueConstraints;

/**
* Describes constraints for `string` type ECProperty values
* @public
*/
export interface StringPropertyValueConstraints {
minimumLength?: number;
maximumLength?: number;
}

/**
* Describes constraints for `int` | `double` | `float` type ECProperty values
* @public
*/
export interface NumericPropertyValueConstraints {
minimumValue?: number;
maximumValue?: number;
}

/**
* Describes constraints for `array` type ECProperty values
* @public
*/
export interface ArrayPropertyValueConstraints {
minOccurs?: number;
maxOccurs?: number;
}

/** @public */
Expand Down

0 comments on commit 546a0a4

Please sign in to comment.