Skip to content

Commit 1d5c183

Browse files
Otixagedinakovaigdmdimitrov
authored
feat(query-builder): added rehydration to expression trees (#14887)
* feat(query-builder): Added rehydration for filtering expressions and trees * feat(query-builder): Added tests for sub-tree rehydration * chore(*): Improved rehydration checks for grids --------- Co-authored-by: Galina Edinakova <[email protected]> Co-authored-by: igdmdimitrov <[email protected]>
1 parent ecbfae4 commit 1d5c183

16 files changed

+760
-148
lines changed

projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.spec.ts

Lines changed: 480 additions & 0 deletions
Large diffs are not rendered by default.

projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import { DateTimeUtil } from '../date-common/util/date-time.util';
2+
import { EntityType, FieldType } from '../grids/common/grid.interface';
3+
import { GridColumnDataType } from './data-util';
4+
import { IFilteringOperation, IgxBooleanFilteringOperand, IgxDateFilteringOperand, IgxDateTimeFilteringOperand, IgxFilteringOperand, IgxNumberFilteringOperand, IgxStringFilteringOperand, IgxTimeFilteringOperand } from './filtering-condition';
15
import { IFilteringExpression } from './filtering-expression.interface';
2-
import { IFilteringExpressionsTree } from './filtering-expressions-tree';
6+
import { IExpressionTree, IFilteringExpressionsTree } from './filtering-expressions-tree';
37

48
export class ExpressionsTreeUtil {
59
/**
@@ -54,3 +58,163 @@ export class ExpressionsTreeUtil {
5458
return false;
5559
}
5660
}
61+
62+
/**
63+
* Recreates the search value for a given expression.
64+
* @param searchValue The search value to recreate.
65+
* @param dataType The data type of the field.
66+
* @returns The recreated search value.
67+
*/
68+
function recreateSearchValue(searchValue: any, dataType: string): any {
69+
if (!dataType) {
70+
return searchValue;
71+
}
72+
// In ESF, values are stored as a Set.
73+
// Those values are converted to an array before returning string in the stringifyCallback
74+
// now we need to convert those back to Set
75+
if (Array.isArray(searchValue)) {
76+
return new Set(searchValue);
77+
} else if ((dataType.toLowerCase().includes('date') || dataType.toLowerCase().includes('time')) && !(searchValue instanceof Date)) {
78+
return DateTimeUtil.parseIsoDate(searchValue) ?? searchValue;
79+
}
80+
81+
return searchValue;
82+
}
83+
84+
/**
85+
* Returns the filtering logic function for a given dataType and condition (contains, greaterThan, etc.)
86+
* @param dataType The data type of the field.
87+
* @param name The name of the filtering condition.
88+
* @returns The filtering logic function.
89+
*/
90+
function getFilteringCondition(dataType: string, name: string): IFilteringOperation {
91+
let filters: IgxFilteringOperand;
92+
switch (dataType) {
93+
case GridColumnDataType.Boolean:
94+
filters = IgxBooleanFilteringOperand.instance();
95+
break;
96+
case GridColumnDataType.Number:
97+
case GridColumnDataType.Currency:
98+
case GridColumnDataType.Percent:
99+
filters = IgxNumberFilteringOperand.instance();
100+
break;
101+
case GridColumnDataType.Date:
102+
filters = IgxDateFilteringOperand.instance();
103+
break;
104+
case GridColumnDataType.Time:
105+
filters = IgxTimeFilteringOperand.instance();
106+
break;
107+
case GridColumnDataType.DateTime:
108+
filters = IgxDateTimeFilteringOperand.instance();
109+
break;
110+
case GridColumnDataType.String:
111+
default:
112+
filters = IgxStringFilteringOperand.instance();
113+
break;
114+
}
115+
return filters.condition(name);
116+
}
117+
118+
/**
119+
* Recreates the IFilteringOperation for a given expression.
120+
* If the `logic` is already populated - it will return the original IFilteringOperation
121+
* of the expression.
122+
* @param expression The expression for which to resolve the IFilteringOperation.
123+
* @param dataType The data type of the field.
124+
* @returns The IFilteringOperation for the given expression.
125+
*/
126+
function recreateOperatorFromDataType(expression: IFilteringExpression, dataType: string): IFilteringOperation {
127+
if (!expression.condition?.logic) {
128+
return getFilteringCondition(dataType, expression.conditionName || expression.condition?.name);
129+
}
130+
131+
return expression.condition;
132+
}
133+
134+
/**
135+
* Recreates an expression from the given fields by applying the correct operands
136+
* and adjusting the search value to be the correct type.
137+
* @param expression The expression to recreate.
138+
* @param fields An array of fields to use for recreating the expression.
139+
* @returns The recreated expression.
140+
*/
141+
function recreateExpression(expression: IFilteringExpression, fields: FieldType[]): IFilteringExpression {
142+
const field = fields?.find(f => f.field === expression.fieldName);
143+
144+
if (field && !expression.condition?.logic) {
145+
if (!field.filters) {
146+
expression.condition = recreateOperatorFromDataType(expression, field.dataType);
147+
} else {
148+
expression.condition = field.filters.condition(expression.conditionName || expression.condition?.name);
149+
}
150+
}
151+
152+
if (!expression.condition) {
153+
throw Error('Wrong `conditionName`, `condition` or `field` provided0!');
154+
}
155+
156+
if (!expression.conditionName) {
157+
expression.conditionName = expression.condition?.name;
158+
}
159+
160+
expression.searchVal = recreateSearchValue(expression.searchVal, field?.dataType);
161+
162+
return expression;
163+
}
164+
165+
/**
166+
* Checks if the given entry is an IExpressionTree.
167+
* @param entry The entry to check.
168+
* @returns True if the entry is an IExpressionTree, false otherwise.
169+
*/
170+
export function isTree(entry: IExpressionTree | IFilteringExpression): entry is IExpressionTree {
171+
return 'operator' in entry;
172+
}
173+
174+
/**
175+
* Recreates the tree from a given array of entities by applying the correct operands
176+
* for each expression and adjusting the search values to be the correct type.
177+
* @param tree The expression tree to recreate.
178+
* @param entities An array of entities to use for recreating the tree.
179+
* @returns The recreated expression tree.
180+
*/
181+
export function recreateTree(tree: IExpressionTree, entities: EntityType[]): IExpressionTree {
182+
const entity = entities.find(e => e.name === tree.entity);
183+
184+
for (let i = 0; i < tree.filteringOperands.length; i++) {
185+
const operand = tree.filteringOperands[i];
186+
if (isTree(operand)) {
187+
tree.filteringOperands[i] = recreateTree(operand, entities);
188+
} else {
189+
if (operand.searchTree) {
190+
operand.searchTree = recreateTree(operand.searchTree, entities);
191+
}
192+
tree.filteringOperands[i] = recreateExpression(operand, entity?.fields);
193+
}
194+
}
195+
196+
return tree;
197+
}
198+
199+
/**
200+
* Recreates the tree from a given array of fields by applying the correct operands.
201+
* It is recommended to use `recreateTree` if there will be multiple entities in the tree
202+
* with potentially colliding field names.
203+
* @param tree The expression tree to recreate.
204+
* @param fields An array of fields to use for recreating the tree.
205+
*/
206+
export function recreateTreeFromFields(tree: IExpressionTree, fields: FieldType[]): IExpressionTree {
207+
for (let i = 0; i < tree.filteringOperands.length; i++) {
208+
const operand = tree.filteringOperands[i];
209+
if (isTree(operand)) {
210+
tree.filteringOperands[i] = recreateTreeFromFields(operand, fields);
211+
} else {
212+
if (operand.searchTree) {
213+
operand.searchTree = recreateTreeFromFields(operand.searchTree, fields);
214+
}
215+
tree.filteringOperands[i] = recreateExpression(operand, fields);
216+
}
217+
}
218+
219+
return tree;
220+
}

projects/igniteui-angular/src/lib/data-operations/filtering-expressions-tree.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ export class FilteringExpressionsTree implements IFilteringExpressionsTree {
134134
this.fieldName = fieldName;
135135
}
136136

137-
138137
/**
139138
* Checks if filtering expressions tree is empty.
140139
*

projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { GridColumnDataType } from './data-util';
66
import { SortingDirection } from './sorting-strategy';
77
import { formatNumber, formatPercent, getLocaleCurrencyCode } from '@angular/common';
88
import { IFilteringState } from './filtering-state.interface';
9+
import { isTree } from './expressions-tree-util';
910

1011
const DateType = 'date';
1112
const DateTimeType = 'dateTime';
@@ -46,8 +47,8 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
4647
// protected
4748
public matchRecord(rec: any, expressions: IFilteringExpressionsTree | IFilteringExpression, grid?: GridType): boolean {
4849
if (expressions) {
49-
if (expressions instanceof FilteringExpressionsTree) {
50-
const expressionsTree = expressions as IFilteringExpressionsTree;
50+
if (isTree(expressions)) {
51+
const expressionsTree = expressions;
5152
const operator = expressionsTree.operator as FilteringLogic;
5253
let matchOperand;
5354

@@ -71,7 +72,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
7172

7273
return true;
7374
} else {
74-
const expression = expressions as IFilteringExpression;
75+
const expression = expressions;
7576
const column = grid && grid.getColumnByName(expression.fieldName);
7677
const isDate = column ? column.dataType === DateType || column.dataType === DateTimeType : false;
7778
const isTime = column ? column.dataType === TimeType : false;

projects/igniteui-angular/src/lib/grids/filtering/excel-style/common.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { isTree } from '../../../data-operations/expressions-tree-util';
12
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
2-
import { FilteringExpressionsTree, IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree';
3+
import { IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree';
34

45
/**
56
* @hidden @internal
@@ -55,17 +56,16 @@ function generateExpressionsListRecursive(expressions: IFilteringExpressionsTree
5556
return;
5657
}
5758

58-
if (expressions instanceof FilteringExpressionsTree) {
59-
const expressionsTree = expressions as FilteringExpressionsTree;
60-
for (const operand of expressionsTree.filteringOperands) {
61-
generateExpressionsListRecursive(operand, expressionsTree.operator, expressionsUIs);
59+
if (isTree(expressions)) {
60+
for (const operand of expressions.filteringOperands) {
61+
generateExpressionsListRecursive(operand, expressions.operator, expressionsUIs);
6262
}
6363
if (expressionsUIs.length) {
6464
expressionsUIs[expressionsUIs.length - 1].afterOperator = operator;
6565
}
6666
} else {
6767
const exprUI = new ExpressionUI();
68-
exprUI.expression = expressions as IFilteringExpression;
68+
exprUI.expression = expressions;
6969
exprUI.afterOperator = operator;
7070

7171
const prevExprUI = expressionsUIs[expressionsUIs.length - 1];

projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-filtering.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { IgxExcelStylePinningComponent } from './excel-style-pinning.component';
4040
import { IgxExcelStyleMovingComponent } from './excel-style-moving.component';
4141
import { IgxExcelStyleSortingComponent } from './excel-style-sorting.component';
4242
import { IgxExcelStyleHeaderComponent } from './excel-style-header.component';
43+
import { isTree } from '../../../data-operations/expressions-tree-util';
4344

4445
@Directive({
4546
selector: 'igx-excel-style-column-operations,[igxExcelStyleColumnOperations]',
@@ -584,7 +585,7 @@ export class IgxGridExcelStyleFilteringComponent extends BaseFilteringComponent
584585
const expressionsTree = new FilteringExpressionsTree(gridExpressionsTree.operator, gridExpressionsTree.fieldName);
585586

586587
for (const operand of gridExpressionsTree.filteringOperands) {
587-
if (operand instanceof FilteringExpressionsTree) {
588+
if (isTree(operand)) {
588589
const columnExprTree = operand as FilteringExpressionsTree;
589590
if (columnExprTree.fieldName === this.column.field) {
590591
continue;

projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ColumnType, GridType } from '../common/grid.interface';
2020
import { formatDate } from '../../core/utils';
2121
import { ExcelStylePositionStrategy } from './excel-style/excel-style-position-strategy';
2222
import { fadeIn } from 'igniteui-angular/animations';
23-
import { ExpressionsTreeUtil } from '../../data-operations/expressions-tree-util';
23+
import { ExpressionsTreeUtil, isTree } from '../../data-operations/expressions-tree-util';
2424

2525
/**
2626
* @hidden
@@ -146,7 +146,7 @@ export class IgxFilteringService implements OnDestroy {
146146
this.isFiltering = true;
147147

148148
let expressionsTree;
149-
if (expressions instanceof FilteringExpressionsTree) {
149+
if (expressions && 'operator' in expressions) {
150150
expressionsTree = expressions;
151151
} else {
152152
expressionsTree = this.createSimpleFilteringTree(field, expressions);
@@ -197,11 +197,10 @@ export class IgxFilteringService implements OnDestroy {
197197
const expressionsTreeForColumn = ExpressionsTreeUtil.find(this.grid.filteringExpressionsTree, field);
198198
if (!expressionsTreeForColumn) {
199199
throw new Error('Invalid condition or Expression Tree!');
200-
} else if (expressionsTreeForColumn instanceof FilteringExpressionsTree) {
200+
} else if (isTree(expressionsTreeForColumn)) {
201201
this.filter_internal(field, value, expressionsTreeForColumn, filteringIgnoreCase);
202202
} else {
203-
const expressionForColumn = expressionsTreeForColumn as IFilteringExpression;
204-
this.filter_internal(field, value, expressionForColumn.condition, filteringIgnoreCase);
203+
this.filter_internal(field, value, expressionsTreeForColumn.condition, filteringIgnoreCase);
205204
}
206205
}
207206
const doneEventArgs = ExpressionsTreeUtil.find(this.grid.filteringExpressionsTree, field) as FilteringExpressionsTree;
@@ -496,9 +495,8 @@ export class IgxFilteringService implements OnDestroy {
496495
}
497496

498497
for (const expr of expressionTree.filteringOperands) {
499-
if ((expr instanceof FilteringExpressionsTree)) {
500-
const exprTree = expr as FilteringExpressionsTree;
501-
if (exprTree.filteringOperands && exprTree.filteringOperands.length) {
498+
if (isTree(expr)) {
499+
if (expr.filteringOperands && expr.filteringOperands.length) {
502500
return false;
503501
}
504502
} else {
@@ -532,9 +530,9 @@ export class IgxFilteringService implements OnDestroy {
532530
insertAtIndex = -1,
533531
createNewTree = false): FilteringExpressionsTree {
534532

535-
let expressionsTree = conditionOrExpressionsTree instanceof FilteringExpressionsTree ?
536-
conditionOrExpressionsTree as IFilteringExpressionsTree : null;
537-
const condition = conditionOrExpressionsTree instanceof FilteringExpressionsTree ?
533+
let expressionsTree = 'operator' in conditionOrExpressionsTree ?
534+
conditionOrExpressionsTree : null;
535+
const condition = 'operator' in conditionOrExpressionsTree ?
538536
null : conditionOrExpressionsTree as IFilteringOperation;
539537

540538
let newExpressionsTree = filteringState as FilteringExpressionsTree;
@@ -567,17 +565,16 @@ export class IgxFilteringService implements OnDestroy {
567565
return false;
568566
}
569567

570-
if (expressions instanceof FilteringExpressionsTree) {
571-
const expressionsTree = expressions as FilteringExpressionsTree;
572-
if (expressionsTree.operator === FilteringLogic.Or) {
573-
const andOperatorsCount = this.getChildAndOperatorsCount(expressionsTree);
568+
if (isTree(expressions)) {
569+
if (expressions.operator === FilteringLogic.Or) {
570+
const andOperatorsCount = this.getChildAndOperatorsCount(expressions);
574571

575572
// having more than one 'And' operator in the sub-tree means that the filter could not be represented without parentheses.
576573
return andOperatorsCount > 1;
577574
}
578575

579576
let isComplex = false;
580-
for (const operand of expressionsTree.filteringOperands) {
577+
for (const operand of expressions.filteringOperands) {
581578
isComplex = isComplex || this.isFilteringTreeComplex(operand);
582579
}
583580

@@ -592,12 +589,12 @@ export class IgxFilteringService implements OnDestroy {
592589
let operand;
593590
for (let i = 0; i < expressions.filteringOperands.length; i++) {
594591
operand = expressions[i];
595-
if (operand instanceof FilteringExpressionsTree) {
592+
if (operand && isTree(operand)) {
596593
if (operand.operator === FilteringLogic.And) {
597594
count++;
598595
}
599596

600-
count = count + this.getChildAndOperatorsCount(operand);
597+
count = count + this.getChildAndOperatorsCount(operand as IFilteringExpressionsTree);
601598
}
602599
}
603600

0 commit comments

Comments
 (0)