@itwin/presentation-components
Version:
React components based on iTwin.js Presentation library
426 lines • 17.9 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module InstancesFilter
*/
import { PropertyValueFormat } from "@itwin/appui-abstract";
import { isPropertyFilterRuleGroup, } from "@itwin/components-react";
import { assert } from "@itwin/core-bentley";
import { GenericInstanceFilter, } from "@itwin/core-common";
import { PresentationError, PresentationStatus, RelationshipPath, } from "@itwin/presentation-common";
import { deserializeUniqueValues, findField, serializeUniqueValues } from "../common/Utils.js";
import { createPropertyInfoFromPropertiesField, getInstanceFilterFieldName } from "./Utils.js";
/** @public */
// eslint-disable-next-line @typescript-eslint/no-redeclare
export var PresentationInstanceFilter;
(function (PresentationInstanceFilter) {
/**
* Converts filter built by [usePropertyFilterBuilder]($components-react) into presentation specific format.
* @throws if presentation data cannot be found for properties used in `filter`.
*/
function fromComponentsPropertyFilter(descriptor, filter) {
if (isPropertyFilterRuleGroup(filter)) {
return createPresentationInstanceFilterConditionGroup(descriptor, filter);
}
return createPresentationInstanceFilterCondition(descriptor, filter);
}
PresentationInstanceFilter.fromComponentsPropertyFilter = fromComponentsPropertyFilter;
/**
* Converts [[PresentationInstanceFilter]] into format used by [usePropertyFilterBuilder]($components-react).
* @throws if fields used in filter cannot be found in `descriptor`.
*/
function toComponentsPropertyFilter(descriptor, filter) {
if (PresentationInstanceFilter.isConditionGroup(filter)) {
return createPropertyFilterRuleGroup(filter, descriptor);
}
return createPropertyFilterRule(filter, descriptor);
}
PresentationInstanceFilter.toComponentsPropertyFilter = toComponentsPropertyFilter;
/**
* Extracts information from [[PresentationInstanceFilter]] and creates a [GenericInstanceFilter]($common) for building queries.
*/
function toGenericInstanceFilter(filter, filteredClasses) {
const context = { relatedInstances: [], propertyClasses: [], usedRelatedAliases: new Map() };
const rules = createGenericInstanceFilter(filter, context);
return {
rules,
relatedInstances: context.relatedInstances.map((instance) => ({ path: toRelationshipStep(instance.path), alias: instance.alias })),
propertyClassNames: context.propertyClasses.map((classInfo) => classInfo.name),
filteredClassNames: filteredClasses?.map((classInfo) => classInfo.name),
};
}
PresentationInstanceFilter.toGenericInstanceFilter = toGenericInstanceFilter;
/**
* Creates [[PresentationInstanceFilter]] from given [GenericInstanceFilter]($common).
* @throws if fields used in `filter` cannot be found in `descriptor`.
*/
function fromGenericInstanceFilter(descriptor, filter) {
return parseGenericFilter(filter, descriptor);
}
PresentationInstanceFilter.fromGenericInstanceFilter = fromGenericInstanceFilter;
/**
* Function that checks if supplied [[PresentationInstanceFilter]] is [[PresentationInstanceFilterConditionGroup]].
*/
function isConditionGroup(filter) {
return filter.conditions !== undefined;
}
PresentationInstanceFilter.isConditionGroup = isConditionGroup;
/**
* Function that creates equality condition based on supplied [[PropertiesField]] and [[PrimitiveValue]] that is compatible
* with `UniquePropertyValuesSelector`.
* If [[PrimitiveValue.value]] is `undefined` created condition uses `is-null` or `is-not-null` operator.
*/
function createPrimitiveValueEqualityCondition(field, operator, value) {
if (!isValidPrimitiveValue(value)) {
return {
field,
operator: operator === "is-equal" ? "is-null" : "is-not-null",
};
}
return {
field,
operator,
value: createUniqueValue(value),
};
}
PresentationInstanceFilter.createPrimitiveValueEqualityCondition = createPrimitiveValueEqualityCondition;
})(PresentationInstanceFilter || (PresentationInstanceFilter = {}));
function createPresentationInstanceFilterConditionGroup(descriptor, group) {
return {
operator: group.operator,
conditions: group.rules.map((rule) => PresentationInstanceFilter.fromComponentsPropertyFilter(descriptor, rule)),
};
}
function createPresentationInstanceFilterCondition(descriptor, condition) {
const field = findField(descriptor, getInstanceFilterFieldName(condition.property));
if (!field || !field.isPropertiesField()) {
throw new PresentationError(PresentationStatus.Error, `Failed to find properties field for property - ${condition.property.name}`);
}
if (condition.value && condition.value.valueFormat !== PropertyValueFormat.Primitive) {
throw new PresentationError(PresentationStatus.Error, `Property '${condition.property.name}' cannot be compared with non primitive value.`);
}
return {
operator: condition.operator,
field,
value: condition.value,
};
}
function createPropertyFilterRule(condition, descriptor) {
const field = descriptor.getFieldByName(condition.field.name, true);
if (!field || !field.isPropertiesField()) {
throw new PresentationError(PresentationStatus.Error, `Failed to find properties field - ${condition.field.name} in descriptor`);
}
return {
property: createPropertyInfoFromPropertiesField(field).propertyDescription,
operator: condition.operator,
value: condition.value,
};
}
function createPropertyFilterRuleGroup(group, descriptor) {
return {
operator: group.operator,
rules: group.conditions.map((condition) => PresentationInstanceFilter.toComponentsPropertyFilter(descriptor, condition)),
};
}
function createGenericInstanceFilter(filter, ctx) {
if (PresentationInstanceFilter.isConditionGroup(filter)) {
return createGenericInstanceFilterRuleGroup(filter, ctx);
}
const result = createGenericInstanceFilterUniqueValueRules(filter, ctx);
if (result !== undefined) {
return result;
}
return createGenericInstanceFilterRule(filter, ctx);
}
function createGenericInstanceFilterUniqueValueRules(filter, ctx) {
// Unique values works only with `IsEqual` and `IsNotEqual` operators.
if (filter.operator !== "is-equal" && filter.operator !== "is-not-equal") {
return undefined;
}
if (typeof filter.value?.value !== "string" || typeof filter.value?.displayValue !== "string") {
return undefined;
}
const result = createUniqueValueConditions(filter, filter.value.displayValue, filter.value.value);
if (result === undefined) {
return undefined;
}
return createGenericInstanceFilterRuleGroup(result, ctx);
}
function createGenericInstanceFilterRuleGroup(group, ctx) {
const convertedConditions = group.conditions.map((condition) => createGenericInstanceFilter(condition, ctx));
return {
operator: group.operator,
rules: convertedConditions,
};
}
function createGenericInstanceFilterRule(condition, ctx) {
const { field, operator, value } = condition;
// we can use first property when creating `GenericInstanceFilterRule` because all properties in this field will have same name.
const property = field.properties[0].property;
const relatedInstance = getRelatedInstanceDescription(field, property.classInfo.name, ctx);
addClassInfoToContext(relatedInstance ? [relatedInstance.path[0].sourceClassInfo] : field.properties.map((prop) => prop.property.classInfo), ctx);
const propertyAlias = relatedInstance?.alias ?? "this";
return {
operator,
value: toGenericInstanceFilterRuleValue(value),
sourceAlias: propertyAlias,
propertyName: property.name,
propertyTypeName: field.type.typeName,
};
}
function createUniqueValueConditions(filter, serializedDisplayValues, serializedGroupedRawValues) {
const { field, operator } = filter;
const uniqueValues = deserializeUniqueValues(serializedDisplayValues, serializedGroupedRawValues);
if (uniqueValues === undefined) {
return undefined;
}
const conditionGroup = {
operator: operator === "is-equal" ? "or" : "and",
conditions: [],
};
for (const { displayValue, groupedRawValues } of uniqueValues) {
for (const value of groupedRawValues) {
conditionGroup.conditions.push({
field,
operator,
value: { valueFormat: PropertyValueFormat.Primitive, displayValue, value },
});
}
}
return conditionGroup;
}
function getRelatedInstanceDescription(field, propClassName, ctx) {
if (!field.parent) {
return undefined;
}
const pathToProperty = RelationshipPath.reverse(getPathToPrimaryClass(field.parent));
const existing = ctx.relatedInstances.find((instance) => RelationshipPath.equals(pathToProperty, instance.path));
if (existing) {
return existing;
}
const baseAlias = `rel_${propClassName.split(":")[1]}`;
const index = getAliasIndex(baseAlias, ctx.usedRelatedAliases);
const newRelated = {
path: pathToProperty,
alias: `${baseAlias}_${index}`,
};
ctx.relatedInstances.push(newRelated);
return newRelated;
}
function addClassInfoToContext(classInfos, ctx) {
for (const classInfo of classInfos) {
if (ctx.propertyClasses.find((existing) => existing.id === classInfo.id)) {
return;
}
ctx.propertyClasses.push(classInfo);
}
}
function getPathToPrimaryClass(field) {
if (field.parent) {
return [...field.pathToPrimaryClass, ...getPathToPrimaryClass(field.parent)];
}
return [...field.pathToPrimaryClass];
}
function getAliasIndex(alias, usedAliases) {
const index = usedAliases.has(alias) ? usedAliases.get(alias) + 1 : 0;
usedAliases.set(alias, index);
return index;
}
function parseGenericFilter(filter, descriptor) {
const ctx = {
findField: (propName, alias) => {
const field = alias === "this" ? findDirectField(descriptor.fields, propName) : findRelatedField(descriptor.fields, propName, alias, filter.relatedInstances);
if (!field) {
throw new PresentationError(PresentationStatus.Error, `Failed to find field for property - ${alias}.${propName}`);
}
return field;
},
};
return parseGenericFilterRules(filter.rules, ctx);
}
function parseGenericFilterRules(rules, ctx) {
if (GenericInstanceFilter.isFilterRuleGroup(rules)) {
return parseGenericFilterRuleGroup(rules, ctx);
}
return parseGenericFilterRule(rules, ctx);
}
function parseGenericFilterRuleGroup(group, ctx) {
if (group.rules.every((rule) => !GenericInstanceFilter.isFilterRuleGroup(rule) && (rule.operator === "is-equal" || rule.operator === "is-not-equal"))) {
const uniqueValueRule = parseUniqueValuesRule(group.rules, ctx);
if (uniqueValueRule) {
return uniqueValueRule;
}
}
return {
operator: group.operator,
conditions: group.rules.map((rule) => parseGenericFilterRules(rule, ctx)),
};
}
function parseGenericFilterRule(rule, ctx) {
const field = ctx.findField(rule.propertyName, rule.sourceAlias);
return {
field,
operator: rule.operator,
value: rule.value
? {
valueFormat: PropertyValueFormat.Primitive,
displayValue: rule.value.displayValue,
value: rule.value.rawValue,
...("roundingError" in rule.value ? { roundingError: rule.value.roundingError } : undefined),
}
: undefined,
};
}
function parseUniqueValuesRule(rules, ctx) {
if (rules.length === 0) {
return undefined;
}
// check if all rules in group use same property and operator
const ruleInfo = { propName: rules[0].propertyName, alias: rules[0].sourceAlias, operator: rules[0].operator };
if (!rules.every((rule) => rule.operator === ruleInfo.operator && rule.propertyName === ruleInfo.propName && rule.sourceAlias === ruleInfo.alias)) {
return undefined;
}
const field = ctx.findField(rules[0].propertyName, rules[0].sourceAlias);
const uniqueValues = [];
for (const rule of rules) {
assert(rule.value?.displayValue !== undefined && rule.value.rawValue !== undefined);
const displayValue = rule.value.displayValue;
const value = rule.value.rawValue;
const currentValue = uniqueValues.find((val) => val.displayValue === displayValue);
if (currentValue) {
currentValue.groupedRawValues.push(value);
}
else {
uniqueValues.push({
displayValue,
groupedRawValues: [value],
});
}
}
const { displayValues, groupedRawValues } = serializeUniqueValues(uniqueValues);
return {
operator: rules[0].operator,
field,
value: { valueFormat: PropertyValueFormat.Primitive, displayValue: displayValues, value: groupedRawValues },
};
}
function findDirectField(fields, propName) {
for (const field of fields) {
if (!field.isPropertiesField()) {
continue;
}
// check only the name of the first property because only properties with the same name can be merged under single field.
if (field.properties[0].property.name === propName) {
return field;
}
}
return undefined;
}
function findRelatedField(fields, propName, alias, relatedInstances) {
const relatedInstance = relatedInstances.find((rel) => alias === rel.alias);
if (!relatedInstance) {
return undefined;
}
const relatedField = findFieldByPath(fields, relatedInstance.path);
return relatedField ? findDirectField(relatedField.nestedFields, propName) : undefined;
}
function findFieldByPath(fields, pathToField) {
for (const field of fields) {
if (!field.isNestedContentField()) {
continue;
}
const pathMatchResult = pathStartsWith(pathToField, RelationshipPath.reverse(field.pathToPrimaryClass));
if (!pathMatchResult.matches) {
continue;
}
if (pathMatchResult.leftOver.length === 0) {
return field;
}
const nestedField = findFieldByPath(field.nestedFields, pathMatchResult.leftOver);
if (nestedField) {
return nestedField;
}
}
return undefined;
}
function pathStartsWith(prefix, path) {
if (prefix.length < path.length) {
return { matches: false };
}
for (let i = 0; i < path.length; ++i) {
const prefixStep = prefix[i];
const pathStep = path[i];
if (prefixStep.sourceClassName !== pathStep.sourceClassInfo.name ||
prefixStep.targetClassName !== pathStep.targetClassInfo.name ||
prefixStep.relationshipClassName !== pathStep.relationshipInfo.name ||
prefixStep.isForwardRelationship !== pathStep.isForwardRelationship) {
return { matches: false };
}
}
const leftOver = prefix.slice(path.length);
return {
matches: true,
leftOver,
};
}
function toRelationshipStep(path) {
return path.map((step) => ({
sourceClassName: step.sourceClassInfo.name,
targetClassName: step.targetClassInfo.name,
relationshipClassName: step.relationshipInfo.name,
isForwardRelationship: step.isForwardRelationship,
}));
}
function toGenericInstanceFilterRuleValue(primitiveValue) {
if (!primitiveValue || primitiveValue.value === undefined || !isGenericPrimitiveValueLike(primitiveValue.value)) {
return undefined;
}
if (typeof primitiveValue.value === "number") {
return {
displayValue: primitiveValue.displayValue ?? "",
rawValue: primitiveValue.value,
...("roundingError" in primitiveValue ? { roundingError: primitiveValue.roundingError } : undefined),
};
}
return {
displayValue: primitiveValue.displayValue ?? "",
rawValue: primitiveValue.value,
};
}
function isGenericPrimitiveValueLike(value) {
switch (typeof value) {
case "string":
case "number":
case "boolean":
return true;
}
return isPoint3dLike(value) || isPoint2dLike(value) || isInstanceKeyLike(value);
}
function isPoint2dLike(value) {
return value.x !== undefined && value.y !== undefined;
}
function isPoint3dLike(value) {
return isPoint2dLike(value) && value.z !== undefined;
}
function isInstanceKeyLike(value) {
return value.id !== undefined && value.className !== undefined;
}
function isValidPrimitiveValue(val) {
return val.value !== undefined && val.displayValue !== undefined;
}
function createUniqueValue(value) {
const { displayValues, groupedRawValues } = serializeUniqueValues([
{
displayValue: value.displayValue,
groupedRawValues: [value.value],
},
]);
return {
displayValue: displayValues,
value: groupedRawValues,
valueFormat: PropertyValueFormat.Primitive,
};
}
//# sourceMappingURL=PresentationInstanceFilter.js.map