@adaptabletools/adaptable 22.0.11 → 22.1.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.css +19 -1
- package/index.css.map +1 -1
- package/package.json +3 -4
- package/src/AdaptableOptions/DashboardOptions.d.ts +2 -2
- package/src/AdaptableState/Common/AdaptablePredicate.js +1 -1
- package/src/AdaptableState/InitialState.d.ts +2 -2
- package/src/AdaptableState/LayoutState.d.ts +44 -0
- package/src/Api/CalendarApi.d.ts +15 -0
- package/src/Api/ColumnScopeApi.d.ts +5 -0
- package/src/Api/Implementation/CalendarApiImpl.d.ts +3 -0
- package/src/Api/Implementation/CalendarApiImpl.js +10 -0
- package/src/Api/Implementation/ColumnApiImpl.d.ts +1 -0
- package/src/Api/Implementation/ColumnApiImpl.js +14 -0
- package/src/Api/Implementation/ColumnScopeApiImpl.d.ts +1 -0
- package/src/Api/Implementation/ColumnScopeApiImpl.js +12 -0
- package/src/Api/Implementation/LayoutHelpers.js +12 -0
- package/src/Api/Internal/AlertInternalApi.js +4 -1
- package/src/Api/Internal/FormatColumnInternalApi.js +3 -3
- package/src/Strategy/FlashingCellModule.js +1 -0
- package/src/Strategy/PlusMinusModule.js +3 -3
- package/src/Strategy/SmartEditModule.js +1 -1
- package/src/Utilities/Constants/GeneralConstants.js +2 -1
- package/src/Utilities/ExpressionFunctions/booleanExpressionFunctions.d.ts +1 -1
- package/src/Utilities/ExpressionFunctions/booleanExpressionFunctions.js +41 -2
- package/src/Utilities/ExpressionFunctions/scalarExpressionFunctions.d.ts +1 -1
- package/src/Utilities/ExpressionFunctions/scalarExpressionFunctions.js +31 -2
- package/src/Utilities/Helpers/AdaptableHelper.js +30 -4
- package/src/Utilities/Services/Interface/IQueryLanguageService.d.ts +1 -0
- package/src/Utilities/Services/MetamodelService.js +18 -18
- package/src/Utilities/Services/QueryLanguageService.d.ts +2 -0
- package/src/Utilities/Services/QueryLanguageService.js +20 -8
- package/src/Utilities/Services/ValidationService.js +3 -1
- package/src/View/Components/EntityRulesEditor/index.js +1 -1
- package/src/View/Components/ModuleValueSelector/index.js +9 -1
- package/src/View/Components/ReorderDraggable/index.js +22 -36
- package/src/View/Components/ValueSelector/index.js +46 -50
- package/src/View/Dashboard/PinnedToolbarsSelector.js +1 -1
- package/src/View/Layout/Wizard/LayoutWizard.js +16 -1
- package/src/View/Layout/Wizard/sections/ColumnsSection.js +31 -19
- package/src/View/Layout/Wizard/sections/RowGroupingSection.js +1 -1
- package/src/View/Layout/Wizard/sections/RowSelectionSection.d.ts +8 -0
- package/src/View/Layout/Wizard/sections/RowSelectionSection.js +140 -0
- package/src/View/NamedQuery/Wizard/NamedQuerySettingsWizardSection.js +0 -1
- package/src/agGrid/AdaptableAgGrid.js +10 -0
- package/src/components/Dashboard/Dashboard.js +1 -1
- package/src/components/DragAndDropContext/ModuleManager.d.ts +1 -0
- package/src/components/DragAndDropContext/ModuleManager.js +12 -37
- package/src/components/DragAndDropContext/TabList.d.ts +11 -6
- package/src/components/DragAndDropContext/TabList.js +78 -36
- package/src/components/DragAndDropContext/UnusedPanel.js +10 -21
- package/src/components/ExpressionEditor/BaseEditorInput.d.ts +2 -0
- package/src/components/ExpressionEditor/BaseEditorInput.js +4 -0
- package/src/components/ExpressionEditor/EditorInput.d.ts +3 -1
- package/src/components/ExpressionEditor/EditorInput.js +20 -9
- package/src/components/ExpressionEditor/QueryBuilder/QueryBuilder.d.ts +2 -1
- package/src/components/ExpressionEditor/QueryBuilder/QueryBuilder.js +3 -12
- package/src/components/ExpressionEditor/QueryBuilder/QueryPredicateBuilder.js +17 -19
- package/src/components/ExpressionEditor/index.d.ts +2 -1
- package/src/components/ExpressionEditor/index.js +1 -1
- package/src/components/Tree/TreeDropdown/index.js +38 -27
- package/src/components/dnd/index.d.ts +3 -13
- package/src/components/dnd/index.js +4 -55
- package/src/env.js +2 -2
- package/src/layout-manager/src/LayoutManagerModel.d.ts +2 -1
- package/src/layout-manager/src/index.d.ts +10 -0
- package/src/layout-manager/src/index.js +156 -4
- package/src/layout-manager/src/normalizeLayoutModel.js +8 -0
- package/src/layout-manager/src/simplifyLayoutModel.js +6 -0
- package/src/metamodel/adaptable-metamodel-model.d.ts +22 -13
- package/src/metamodel/adaptable.metamodel.d.ts +3792 -5143
- package/src/metamodel/adaptable.metamodel.js +1 -1
- package/src/parser/src/parser.js +55 -1218
- package/src/parser/src/types.d.ts +5 -0
- package/src/types.d.ts +1 -1
- package/tsconfig.esm.tsbuildinfo +1 -1
|
@@ -71,6 +71,17 @@ export function generateAutoRowGroupColumnForColumn(column) {
|
|
|
71
71
|
dataType: column.dataType,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
const SELECTION_COLUMN_FRIENDLY_NAME = '[Selection Column]';
|
|
75
|
+
export function generateSelectionColumn() {
|
|
76
|
+
return {
|
|
77
|
+
...ROW_GROUP_COLUMN_DEFAULTS,
|
|
78
|
+
columnId: AG_GRID_SELECTION_COLUMN,
|
|
79
|
+
friendlyName: SELECTION_COLUMN_FRIENDLY_NAME,
|
|
80
|
+
isGrouped: false,
|
|
81
|
+
isGeneratedRowGroupColumn: false,
|
|
82
|
+
isGeneratedSelectionColumn: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
74
85
|
export function getFriendlyNameForPivotResultColumn(columnId, agGridApi) {
|
|
75
86
|
if (isPivotGrandTotal(columnId)) {
|
|
76
87
|
return `[Grand Total] ${columnId
|
|
@@ -304,6 +315,9 @@ export class ColumnApiImpl extends ApiBase {
|
|
|
304
315
|
if (foundColumn?.isTreeColumn && isRowGroupColumn) {
|
|
305
316
|
return foundColumn.friendlyName;
|
|
306
317
|
}
|
|
318
|
+
if (foundColumn?.isGeneratedSelectionColumn) {
|
|
319
|
+
return foundColumn.friendlyName || SELECTION_COLUMN_FRIENDLY_NAME;
|
|
320
|
+
}
|
|
307
321
|
let result = columnId + GeneralConstants.MISSING_COLUMN;
|
|
308
322
|
if (this.isAutoRowGroupColumn(columnId)) {
|
|
309
323
|
if (this.getGridApi().isTreeDataGrid()) {
|
|
@@ -29,5 +29,6 @@ export declare class ColumnScopeApiImpl extends ApiBase implements ColumnScopeAp
|
|
|
29
29
|
isColumnInDateScope(column: AdaptableColumn, scope: ColumnScope): boolean;
|
|
30
30
|
isScopeInScope(a: ColumnScope, b: ColumnScope): boolean;
|
|
31
31
|
createCellColorRangesForScope(scope: ColumnScope): CellColorRange[];
|
|
32
|
+
getAnyColumnIdForScope(scope: ColumnScope): string | undefined;
|
|
32
33
|
private distinctColumnValuesAreEmpty;
|
|
33
34
|
}
|
|
@@ -306,6 +306,18 @@ export class ColumnScopeApiImpl extends ApiBase {
|
|
|
306
306
|
}
|
|
307
307
|
return ranges;
|
|
308
308
|
}
|
|
309
|
+
getAnyColumnIdForScope(scope) {
|
|
310
|
+
if (!scope) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const allAdaptableColumns = this.getColumnApi()
|
|
314
|
+
.getColumns()
|
|
315
|
+
.filter((col) => !col.isUIHiddenColumn &&
|
|
316
|
+
!col.isGeneratedRowGroupColumn &&
|
|
317
|
+
!col.isGeneratedSelectionColumn &&
|
|
318
|
+
!col.isPivotTotalColumn);
|
|
319
|
+
return allAdaptableColumns.find((col) => this.isColumnInScope(col, scope))?.columnId;
|
|
320
|
+
}
|
|
309
321
|
distinctColumnValuesAreEmpty(distinctColumnsValues) {
|
|
310
322
|
if (ArrayExtensions.IsNullOrEmpty(distinctColumnsValues)) {
|
|
311
323
|
return true;
|
|
@@ -212,6 +212,9 @@ export const tableLayoutToTableLayoutModel = (tableLayout) => {
|
|
|
212
212
|
TableAggregationColumns: TableAggregationColumns,
|
|
213
213
|
GrandTotalRow: tableLayout.GrandTotalRow,
|
|
214
214
|
});
|
|
215
|
+
if (tableLayout.RowSelection != null) {
|
|
216
|
+
result.RowSelection = tableLayout.RowSelection;
|
|
217
|
+
}
|
|
215
218
|
if (tableLayout.RowGroupDisplayType) {
|
|
216
219
|
result.RowGroupDisplayType = tableLayout.RowGroupDisplayType;
|
|
217
220
|
}
|
|
@@ -288,6 +291,9 @@ export const pivotLayoutToPivotLayoutModel = (pivotLayout) => {
|
|
|
288
291
|
}
|
|
289
292
|
: undefined,
|
|
290
293
|
});
|
|
294
|
+
if (pivotLayout.RowSelection != null) {
|
|
295
|
+
result.RowSelection = pivotLayout.RowSelection;
|
|
296
|
+
}
|
|
291
297
|
if (pivotLayout.RowGroupDisplayType) {
|
|
292
298
|
result.RowGroupDisplayType = pivotLayout.RowGroupDisplayType;
|
|
293
299
|
}
|
|
@@ -422,6 +428,9 @@ export const tableLayoutModelToTableLayout = (layoutModel) => {
|
|
|
422
428
|
else {
|
|
423
429
|
delete tableLayout.GrandTotalRow;
|
|
424
430
|
}
|
|
431
|
+
if (layoutModel.RowSelection != null) {
|
|
432
|
+
tableLayout.RowSelection = layoutModel.RowSelection;
|
|
433
|
+
}
|
|
425
434
|
cleanupAdaptableObjectPrimitives(tableLayout);
|
|
426
435
|
return tableLayout;
|
|
427
436
|
};
|
|
@@ -538,6 +547,9 @@ export const pivotLayoutModelToPivotLayout = (layoutModel) => {
|
|
|
538
547
|
// if (layoutModel.RowGroupDisplayType) {
|
|
539
548
|
pivotLayout.RowGroupDisplayType = layoutModel.RowGroupDisplayType ?? 'single';
|
|
540
549
|
// }
|
|
550
|
+
if (layoutModel.RowSelection != null) {
|
|
551
|
+
pivotLayout.RowSelection = layoutModel.RowSelection;
|
|
552
|
+
}
|
|
541
553
|
return pivotLayout;
|
|
542
554
|
};
|
|
543
555
|
export const isPivotLayout = (layout) => Array.isArray(layout.PivotColumns);
|
|
@@ -525,7 +525,10 @@ export class AlertInternalApi extends ApiBase {
|
|
|
525
525
|
isValidExpression &&
|
|
526
526
|
this.getAdaptableApi()
|
|
527
527
|
.internalApi.getQueryLanguageService()
|
|
528
|
-
.evaluateBooleanExpression(expression, 'Alert', rowNode, {
|
|
528
|
+
.evaluateBooleanExpression(expression, 'Alert', rowNode, {
|
|
529
|
+
dataChangedEvent,
|
|
530
|
+
columnScope: dataChangedEvent.column?.columnId,
|
|
531
|
+
});
|
|
529
532
|
}
|
|
530
533
|
catch (error) {
|
|
531
534
|
isSatisfiedExpression = false;
|
|
@@ -253,7 +253,7 @@ export class FormatColumnInternalApi extends ApiBase {
|
|
|
253
253
|
return this.evaluatePredicate(formatColumn, predicateDefHandlerContext);
|
|
254
254
|
} // then run the Expression
|
|
255
255
|
else if (formatColumn.Rule.BooleanExpression) {
|
|
256
|
-
return this.evaluateExpression(formatColumn, rowNode, pivotResultColumn);
|
|
256
|
+
return this.evaluateExpression(formatColumn, rowNode, column, pivotResultColumn);
|
|
257
257
|
}
|
|
258
258
|
// nothing has passed then return false
|
|
259
259
|
return false;
|
|
@@ -277,13 +277,13 @@ export class FormatColumnInternalApi extends ApiBase {
|
|
|
277
277
|
evaluatePredicate(formatColumn, predicateDefHandlerContext) {
|
|
278
278
|
return this.getPredicateApi().handleColumnPredicates(formatColumn.Rule?.Predicates, predicateDefHandlerContext, false);
|
|
279
279
|
}
|
|
280
|
-
evaluateExpression(formatColumn, node, pivotResultColumn) {
|
|
280
|
+
evaluateExpression(formatColumn, node, columnScope, pivotResultColumn) {
|
|
281
281
|
const isValidExpression = this.getExpressionApi().isValidBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, `Invalid format column rule '${formatColumn.Rule.BooleanExpression}'`);
|
|
282
282
|
try {
|
|
283
283
|
return (isValidExpression &&
|
|
284
284
|
this.getAdaptableApi()
|
|
285
285
|
.internalApi.getQueryLanguageService()
|
|
286
|
-
.evaluateBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, node, { pivotResultColumn }));
|
|
286
|
+
.evaluateBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, node, { pivotResultColumn, columnScope: columnScope?.columnId }));
|
|
287
287
|
}
|
|
288
288
|
catch (error) {
|
|
289
289
|
errorOnce(error.message);
|
|
@@ -88,6 +88,7 @@ export class FlashingCellModule extends AdaptableModuleBase {
|
|
|
88
88
|
.getQueryLanguageService()
|
|
89
89
|
.evaluateBooleanExpression(expression, this.moduleInfo.ModuleName, rowNode, {
|
|
90
90
|
dataChangedEvent: cellDataChangedInfo,
|
|
91
|
+
columnScope: cellDataChangedInfo.column?.columnId,
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
94
|
catch (error) {
|
|
@@ -177,15 +177,15 @@ export class PlusMinusModule extends AdaptableModuleBase {
|
|
|
177
177
|
}
|
|
178
178
|
isPlusMinusNudgeApplied(plusMinusNudge, gridCell) {
|
|
179
179
|
if (plusMinusNudge.Rule?.BooleanExpression) {
|
|
180
|
-
return this.evaluateExpression(plusMinusNudge, gridCell.rowNode);
|
|
180
|
+
return this.evaluateExpression(plusMinusNudge, gridCell.rowNode, gridCell.column);
|
|
181
181
|
}
|
|
182
182
|
return true;
|
|
183
183
|
}
|
|
184
|
-
evaluateExpression(plusMinusNudge, node) {
|
|
184
|
+
evaluateExpression(plusMinusNudge, node, column) {
|
|
185
185
|
try {
|
|
186
186
|
return this.api.internalApi
|
|
187
187
|
.getQueryLanguageService()
|
|
188
|
-
.evaluateBooleanExpression(plusMinusNudge.Rule?.BooleanExpression, PlusMinusModuleId, node);
|
|
188
|
+
.evaluateBooleanExpression(plusMinusNudge.Rule?.BooleanExpression, PlusMinusModuleId, node, { columnScope: column?.columnId });
|
|
189
189
|
}
|
|
190
190
|
catch (error) {
|
|
191
191
|
errorOnce(error.message);
|
|
@@ -100,7 +100,7 @@ export class SmartEditModule extends AdaptableModuleBase {
|
|
|
100
100
|
let previewResults = [];
|
|
101
101
|
let column;
|
|
102
102
|
if (!this.api.layoutApi.isCurrentLayoutPivot()) {
|
|
103
|
-
if (
|
|
103
|
+
if (selectedCellInfo != null && selectedCellInfo.columns.length > 0) {
|
|
104
104
|
column = this.api.columnApi.getColumnWithColumnId(selectedCellInfo.columns[0].columnId);
|
|
105
105
|
if (column) {
|
|
106
106
|
selectedCellInfo.gridCells.forEach((selectedCell) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AUTO_GROUP_COLUMN_ID__SINGLE } from '../../layout-manager/src/normalizeLayoutModel';
|
|
2
|
+
import { LayoutManager } from '../../layout-manager/src';
|
|
2
3
|
export const AUTOGENERATED_PK_COLUMN = '__ADAPTABLE_PK__';
|
|
3
4
|
export const MISSING_COLUMN = ' [MISSING]';
|
|
4
5
|
export const LIGHT_THEME = 'light';
|
|
@@ -22,7 +23,7 @@ export const HALF_SECOND = 500;
|
|
|
22
23
|
export const QUARTER_SECOND = 250;
|
|
23
24
|
export const GROUP_PATH_SEPARATOR = '/';
|
|
24
25
|
export const AG_GRID_GROUPED_COLUMN = AUTO_GROUP_COLUMN_ID__SINGLE;
|
|
25
|
-
export const AG_GRID_SELECTION_COLUMN =
|
|
26
|
+
export const AG_GRID_SELECTION_COLUMN = LayoutManager.SELECTION_COLUMN_ID;
|
|
26
27
|
export const AG_GRID_PIVOT_COLUMN = 'pivot_';
|
|
27
28
|
export const AG_GRID_PIVOT_GRAND_TOTAL_COLUMN = 'PivotRowTotal_pivot_';
|
|
28
29
|
export const AG_GRID_CHART_WINDOW = 'AG Grid Window';
|
|
@@ -2,7 +2,7 @@ import { ExpressionFunction } from '../../parser/src/types';
|
|
|
2
2
|
/**
|
|
3
3
|
* List of all the Boolean Functions available in AdaptableQL
|
|
4
4
|
*/
|
|
5
|
-
export type BooleanFunctionName = 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'AND' | 'OR' | 'NOT' | 'BETWEEN' | 'IN' | 'CONTAINS' | 'STARTS_WITH' | 'ENDS_WITH' | 'ANY_CONTAINS' | 'IS_NUMERIC' | 'REGEX' | 'IS_HOLIDAY' | 'IS_WORKDAY' | 'TRUE' | 'FALSE';
|
|
5
|
+
export type BooleanFunctionName = 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'AND' | 'OR' | 'NOT' | 'BETWEEN' | 'IN' | 'CONTAINS' | 'STARTS_WITH' | 'ENDS_WITH' | 'ANY_CONTAINS' | 'IS_NUMERIC' | 'REGEX' | 'IS_TODAY' | 'IS_YESTERDAY' | 'IS_TOMORROW' | 'IS_HOLIDAY' | 'IS_WORKDAY' | 'TRUE' | 'FALSE';
|
|
6
6
|
export declare const booleanExpressionFunctions: Record<BooleanFunctionName, ExpressionFunction>;
|
|
7
7
|
export declare const booleanExpressionFunctionsNames: BooleanFunctionName[];
|
|
8
8
|
export declare const isBooleanAdaptableQlFunction: (functionName: string) => functionName is BooleanFunctionName;
|
|
@@ -254,8 +254,8 @@ export const booleanExpressionFunctions = {
|
|
|
254
254
|
},
|
|
255
255
|
category: 'strings',
|
|
256
256
|
description: 'Returns true if 1st input contains 2nd input',
|
|
257
|
-
signatures: ['CONTAINS(a: string, b: string)'],
|
|
258
|
-
examples: ['CONTAINS([col1], "S")'],
|
|
257
|
+
signatures: ['value1 CONTAINS value2', 'CONTAINS(a: string, b: string)'],
|
|
258
|
+
examples: ['[col1] CONTAINS "S"', 'CONTAINS([col1], "S")'],
|
|
259
259
|
returnType: 'boolean',
|
|
260
260
|
inputs: ['text', 'text'],
|
|
261
261
|
},
|
|
@@ -325,6 +325,45 @@ export const booleanExpressionFunctions = {
|
|
|
325
325
|
category: 'strings',
|
|
326
326
|
returnType: 'boolean',
|
|
327
327
|
},
|
|
328
|
+
IS_TODAY: {
|
|
329
|
+
handler(args, context) {
|
|
330
|
+
const dateToCheck = normalizeDateParam(args[0]);
|
|
331
|
+
return context.adaptableApi.calendarApi.isToday(dateToCheck);
|
|
332
|
+
},
|
|
333
|
+
isHiddenFromMenu: false,
|
|
334
|
+
description: 'Returns true if input date is today',
|
|
335
|
+
signatures: ['IS_TODAY(a: date)'],
|
|
336
|
+
examples: ['IS_TODAY([columnName])'],
|
|
337
|
+
category: 'dates',
|
|
338
|
+
returnType: 'boolean',
|
|
339
|
+
inputs: ['date'],
|
|
340
|
+
},
|
|
341
|
+
IS_YESTERDAY: {
|
|
342
|
+
handler(args, context) {
|
|
343
|
+
const dateToCheck = normalizeDateParam(args[0]);
|
|
344
|
+
return context.adaptableApi.calendarApi.isYesterday(dateToCheck);
|
|
345
|
+
},
|
|
346
|
+
isHiddenFromMenu: false,
|
|
347
|
+
description: 'Returns true if input date is yesterday',
|
|
348
|
+
signatures: ['IS_YESTERDAY(a: date)'],
|
|
349
|
+
examples: ['IS_YESTERDAY([columnName])'],
|
|
350
|
+
category: 'dates',
|
|
351
|
+
returnType: 'boolean',
|
|
352
|
+
inputs: ['date'],
|
|
353
|
+
},
|
|
354
|
+
IS_TOMORROW: {
|
|
355
|
+
handler(args, context) {
|
|
356
|
+
const dateToCheck = normalizeDateParam(args[0]);
|
|
357
|
+
return context.adaptableApi.calendarApi.isTomorrow(dateToCheck);
|
|
358
|
+
},
|
|
359
|
+
isHiddenFromMenu: false,
|
|
360
|
+
description: 'Returns true if input date is tomorrow',
|
|
361
|
+
signatures: ['IS_TOMORROW(a: date)'],
|
|
362
|
+
examples: ['IS_TOMORROW([columnName])'],
|
|
363
|
+
category: 'dates',
|
|
364
|
+
returnType: 'boolean',
|
|
365
|
+
inputs: ['date'],
|
|
366
|
+
},
|
|
328
367
|
IS_HOLIDAY: {
|
|
329
368
|
handler(args, context) {
|
|
330
369
|
const dateToCheck = normalizeDateParam(args[0]);
|
|
@@ -2,6 +2,6 @@ import { ExpressionFunction } from '../../parser/src/types';
|
|
|
2
2
|
/**
|
|
3
3
|
* List of all the Scalar Functions available in AdaptableQL
|
|
4
4
|
*/
|
|
5
|
-
export type ScalarFunctionName = 'ADD' | 'SUB' | 'MUL' | 'DIV' | 'MOD' | 'POW' | 'ABS' | 'CEILING' | 'FLOOR' | 'ROUND' | 'MIN' | 'MAX' | 'AVG' | 'DATE' | 'NOW' | 'CURRENT_DAY' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR' | 'ADD_DAYS' | 'ADD_WEEKS' | 'ADD_MONTHS' | 'ADD_YEARS' | 'DIFF_DAYS' | 'DIFF_WEEKS' | 'DIFF_MONTHS' | 'DIFF_YEARS' | 'SUB_STRING' | 'REPLACE' | 'COALESCE' | 'NULL' | 'LEN' | 'UPPER' | 'LOWER' | 'CONCAT' | 'IF' | 'CASE' | 'COL' | 'FIELD' | 'VAR' | 'TO_ARRAY' | 'QUERY' | 'PERCENT_CHANGE' | 'ABSOLUTE_CHANGE' | 'ANY_CHANGE' | 'IS_BLANK' | 'IS_NOT_BLANK';
|
|
5
|
+
export type ScalarFunctionName = 'ADD' | 'SUB' | 'MUL' | 'DIV' | 'MOD' | 'POW' | 'ABS' | 'CEILING' | 'FLOOR' | 'ROUND' | 'MIN' | 'MAX' | 'AVG' | 'DATE' | 'NOW' | 'CURRENT_DAY' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR' | 'ADD_DAYS' | 'ADD_WEEKS' | 'ADD_MONTHS' | 'ADD_YEARS' | 'DIFF_DAYS' | 'DIFF_WEEKS' | 'DIFF_MONTHS' | 'DIFF_YEARS' | 'SUB_STRING' | 'REPLACE' | 'COALESCE' | 'NULL' | 'LEN' | 'UPPER' | 'LOWER' | 'CONCAT' | 'IF' | 'CASE' | 'COL' | 'FIELD' | '$SCOPE' | 'VAR' | 'TO_ARRAY' | 'QUERY' | 'PERCENT_CHANGE' | 'ABSOLUTE_CHANGE' | 'ANY_CHANGE' | 'IS_BLANK' | 'IS_NOT_BLANK';
|
|
6
6
|
export declare const scalarExpressionFunctions: Record<ScalarFunctionName, ExpressionFunction>;
|
|
7
7
|
export declare const scalarExpressionFunctionNames: ScalarFunctionName[];
|
|
@@ -90,6 +90,35 @@ export const scalarExpressionFunctions = {
|
|
|
90
90
|
category: 'special',
|
|
91
91
|
returnType: 'any',
|
|
92
92
|
},
|
|
93
|
+
$SCOPE: {
|
|
94
|
+
handler(args, context) {
|
|
95
|
+
const scopeColumnId = context.columnScope;
|
|
96
|
+
if (!scopeColumnId) {
|
|
97
|
+
throw new ExpressionEvaluationError('$SCOPE', `$SCOPE is not available in this context`);
|
|
98
|
+
}
|
|
99
|
+
const column = context.adaptableApi?.columnApi.getColumnWithColumnId(scopeColumnId);
|
|
100
|
+
if (!column) {
|
|
101
|
+
throw new ExpressionEvaluationError('$SCOPE', `Column name "${scopeColumnId}" is not found`);
|
|
102
|
+
}
|
|
103
|
+
if (!column.queryable) {
|
|
104
|
+
throw new ExpressionEvaluationError('$SCOPE', `Column name "${scopeColumnId}" is not queryable`);
|
|
105
|
+
}
|
|
106
|
+
// see #derived_pivot_cell_style
|
|
107
|
+
if (context.pivotResultColumn) {
|
|
108
|
+
const baseColumnId = context.pivotResultColumn.getColDef()?.pivotValueColumn?.getColId();
|
|
109
|
+
if (baseColumnId === scopeColumnId) {
|
|
110
|
+
// we evaluate the pivot result column instead of the base column
|
|
111
|
+
return context.adaptableApi?.gridApi.getNormalisedValueFromRowNode(context.node, context.pivotResultColumn.getColId());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return context.adaptableApi?.gridApi.getNormalisedValueFromRowNode(context.node, scopeColumnId);
|
|
115
|
+
},
|
|
116
|
+
description: 'Returns the value of the column in the current scope',
|
|
117
|
+
signatures: ['$SCOPE()'],
|
|
118
|
+
examples: ['$SCOPE() > 10'],
|
|
119
|
+
category: 'special',
|
|
120
|
+
returnType: 'any',
|
|
121
|
+
},
|
|
93
122
|
QUERY: {
|
|
94
123
|
handler(args, context) {
|
|
95
124
|
const namedQueryName = args[0];
|
|
@@ -709,8 +738,8 @@ export const scalarExpressionFunctions = {
|
|
|
709
738
|
return context.dataChangedEvent.oldValue !== context.dataChangedEvent.newValue;
|
|
710
739
|
}
|
|
711
740
|
// only COL argument is supported
|
|
712
|
-
if (columnArg.type !== 'COL') {
|
|
713
|
-
throw new ExpressionEvaluationError('ANY_CHANGE', 'accepts only a column reference as an argument');
|
|
741
|
+
if (columnArg.type !== 'COL' && columnArg.type !== '$SCOPE') {
|
|
742
|
+
throw new ExpressionEvaluationError('ANY_CHANGE', 'accepts only a column reference or scope as an argument');
|
|
714
743
|
}
|
|
715
744
|
const currentColumnValue = evaluateNode(columnArg, context);
|
|
716
745
|
const previousValue = context.dataChangedEvent.oldValue;
|
|
@@ -9,11 +9,37 @@ const GET_CURRENT_VERSION = () => {
|
|
|
9
9
|
: packageJson.version) ?? packageJson.version);
|
|
10
10
|
};
|
|
11
11
|
export function addUuidsToInitialState(initialState) {
|
|
12
|
+
// Collect only the objects that should receive metadata:
|
|
13
|
+
// module state objects and their direct array items (root-level AdaptableObjects).
|
|
14
|
+
// Deeply nested sub-objects (e.g. CellRanges, ColumnComparison) are excluded.
|
|
15
|
+
const objectsToEnrich = new Set();
|
|
16
|
+
for (const moduleKey in initialState) {
|
|
17
|
+
const moduleState = initialState[moduleKey];
|
|
18
|
+
if (isPlainObject(moduleState)) {
|
|
19
|
+
objectsToEnrich.add(moduleState);
|
|
20
|
+
for (const key in moduleState) {
|
|
21
|
+
const value = moduleState[key];
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
for (const item of value) {
|
|
24
|
+
if (isPlainObject(item)) {
|
|
25
|
+
objectsToEnrich.add(item);
|
|
26
|
+
// ColumnFilters are nested inside Layouts but need Uuid
|
|
27
|
+
// for identification (suspend/unsuspend, edit vs add, etc.)
|
|
28
|
+
if (Array.isArray(item.ColumnFilters)) {
|
|
29
|
+
for (const filter of item.ColumnFilters) {
|
|
30
|
+
if (isPlainObject(filter)) {
|
|
31
|
+
objectsToEnrich.add(filter);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
12
41
|
const customizer = (value) => {
|
|
13
|
-
|
|
14
|
-
// we add a Uuid property
|
|
15
|
-
// jw added 9/8/20: unless there is on there already
|
|
16
|
-
if (isPlainObject(value) && value != initialState) {
|
|
42
|
+
if (isPlainObject(value) && objectsToEnrich.has(value)) {
|
|
17
43
|
if (value.Uuid == null || value.Uuid == undefined) {
|
|
18
44
|
value.Uuid = createUuid();
|
|
19
45
|
}
|
|
@@ -13,6 +13,7 @@ import { ExpressionFunctionMap } from '../../../parser/src/types';
|
|
|
13
13
|
export interface IQueryLanguageService extends IAdaptableService {
|
|
14
14
|
evaluateBooleanExpression(expression: string, module: AdaptableModule, rowNode: IRowNode, evalContext?: {
|
|
15
15
|
dataChangedEvent?: CellDataChangedInfo;
|
|
16
|
+
columnScope?: string;
|
|
16
17
|
pivotResultColumn?: Column;
|
|
17
18
|
}): boolean;
|
|
18
19
|
evaluateScalarExpression(expression: string, module: AdaptableModule, rowNode: IRowNode): any;
|
|
@@ -20,7 +20,7 @@ export class MetamodelService {
|
|
|
20
20
|
getGridInfoNoCodeOptions() {
|
|
21
21
|
return this.buildGridInfoOptions({
|
|
22
22
|
filterItemProperty: (itemProperty) => {
|
|
23
|
-
return itemProperty.
|
|
23
|
+
return itemProperty.nC === 'item';
|
|
24
24
|
},
|
|
25
25
|
filterEmptyAdaptableOptions: false,
|
|
26
26
|
});
|
|
@@ -50,7 +50,7 @@ export class MetamodelService {
|
|
|
50
50
|
if (optionKey === 'fdc3Options') {
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
const optionMetamodel = optionsObjectMetamodel?.
|
|
53
|
+
const optionMetamodel = optionsObjectMetamodel?.p?.find((metamodelProperty) => metamodelProperty.n === optionKey);
|
|
54
54
|
// Return if property not found - log if not 'Revision' or 'Uuid' or 'AdaptableVersion'
|
|
55
55
|
if (!optionMetamodel) {
|
|
56
56
|
if (optionKey === 'Revision' || optionKey === 'Uuid' || optionKey === 'AdaptableVersion') {
|
|
@@ -69,10 +69,10 @@ export class MetamodelService {
|
|
|
69
69
|
}
|
|
70
70
|
// if it's a REFERENCE, we try to go (recursively) deeper
|
|
71
71
|
if (expectedOptionsValueType === 'R') {
|
|
72
|
-
const referenceObjectName = optionMetamodel.
|
|
72
|
+
const referenceObjectName = optionMetamodel.r;
|
|
73
73
|
const referenceObject = optionsObject[optionKey];
|
|
74
74
|
const referenceObjectMetamodel = this.getAdaptableMetamodel()[referenceObjectName];
|
|
75
|
-
if (referenceObject && referenceObjectMetamodel?.
|
|
75
|
+
if (referenceObject && referenceObjectMetamodel?.k === 'I') {
|
|
76
76
|
this.validateOptionsObject(validationErrors, referenceObjectName, referenceObject, referenceObjectMetamodel, optionsObjectDefaultValues?.[optionKey]);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -83,7 +83,7 @@ export class MetamodelService {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
getExpectedOptionsValueType(metamodelProperty) {
|
|
86
|
-
const metamodelPropertyKind = metamodelProperty.
|
|
86
|
+
const metamodelPropertyKind = metamodelProperty.k;
|
|
87
87
|
if (supportedMetamodelTypes.includes(metamodelPropertyKind)) {
|
|
88
88
|
return metamodelPropertyKind;
|
|
89
89
|
}
|
|
@@ -106,11 +106,11 @@ export class MetamodelService {
|
|
|
106
106
|
items: baseOptionsItems,
|
|
107
107
|
});
|
|
108
108
|
// map containers
|
|
109
|
-
adaptableOptionsMetamodel.
|
|
110
|
-
.filter((optionItem) => optionItem.
|
|
109
|
+
adaptableOptionsMetamodel.p
|
|
110
|
+
.filter((optionItem) => optionItem.g === 'container')
|
|
111
111
|
.forEach((containerOptionItem) => {
|
|
112
|
-
const containerMetamodelName = containerOptionItem.
|
|
113
|
-
const adaptableOptionsName = containerOptionItem.
|
|
112
|
+
const containerMetamodelName = containerOptionItem.r;
|
|
113
|
+
const adaptableOptionsName = containerOptionItem.n;
|
|
114
114
|
const containerOptionsMetamodel = adaptableMetamodel[containerMetamodelName];
|
|
115
115
|
// @ts-ignore
|
|
116
116
|
const containerOptionsValues = adaptableOptionsValues[adaptableOptionsName];
|
|
@@ -123,21 +123,21 @@ export class MetamodelService {
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
const optionItems = this.mapGridInfoContainerItems(containerOptionsMetamodel, containerOptionsValues, containerOptionsDefaultValues, filterItemProperty);
|
|
126
|
-
gridInfoOptions.set(containerOptionItem.
|
|
126
|
+
gridInfoOptions.set(containerOptionItem.n, {
|
|
127
127
|
containerLabel: this.extractUiLabel(containerOptionItem),
|
|
128
128
|
items: optionItems,
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
return gridInfoOptions;
|
|
132
132
|
}
|
|
133
|
-
mapGridInfoContainerItems(optionItemContainer, adaptableOptionsValues, defaultAdaptableOptionsValues, filter = (itemProperty) => itemProperty.
|
|
134
|
-
return optionItemContainer.
|
|
133
|
+
mapGridInfoContainerItems(optionItemContainer, adaptableOptionsValues, defaultAdaptableOptionsValues, filter = (itemProperty) => itemProperty.g === 'item') {
|
|
134
|
+
return optionItemContainer.p.filter(filter).map((itemProperty) => {
|
|
135
135
|
return {
|
|
136
|
-
name: itemProperty.
|
|
137
|
-
value: adaptableOptionsValues?.[itemProperty.
|
|
138
|
-
defaultValue: defaultAdaptableOptionsValues[itemProperty.
|
|
139
|
-
kind: itemProperty.
|
|
140
|
-
description: StringExtensions.UnescapeHtmlEntities(itemProperty.
|
|
136
|
+
name: itemProperty.n,
|
|
137
|
+
value: adaptableOptionsValues?.[itemProperty.n],
|
|
138
|
+
defaultValue: defaultAdaptableOptionsValues[itemProperty.n],
|
|
139
|
+
kind: itemProperty.k,
|
|
140
|
+
description: StringExtensions.UnescapeHtmlEntities(itemProperty.d ?? ''),
|
|
141
141
|
uiLabel: this.extractUiLabel(itemProperty),
|
|
142
142
|
};
|
|
143
143
|
});
|
|
@@ -152,7 +152,7 @@ export class MetamodelService {
|
|
|
152
152
|
return this.getAdaptableMetamodel()['AdaptableOptions'];
|
|
153
153
|
}
|
|
154
154
|
extractUiLabel(item) {
|
|
155
|
-
return item.
|
|
155
|
+
return item.l || this.formatCamelCaseToHumanText(item.n);
|
|
156
156
|
}
|
|
157
157
|
formatCamelCaseToHumanText(camelCase) {
|
|
158
158
|
if (!camelCase || camelCase == null) {
|
|
@@ -19,6 +19,7 @@ export declare class QueryLanguageService implements IQueryLanguageService {
|
|
|
19
19
|
evaluateBooleanExpression(expression: string, module: AdaptableModule, rowNode: any, evalContext?: {
|
|
20
20
|
dataChangedEvent?: CellDataChangedInfo;
|
|
21
21
|
pivotResultColumn?: Column;
|
|
22
|
+
columnScope?: string;
|
|
22
23
|
}): any;
|
|
23
24
|
evaluateScalarExpression(expression: string, module: AdaptableModule, rowNode: IRowNode): any;
|
|
24
25
|
evaluateAggregatedScalarExpression(expression: string, module: AdaptableModule, getRowNodes?: () => IRowNode[]): ScalarAggregationParameter;
|
|
@@ -54,4 +55,5 @@ export declare class QueryLanguageService implements IQueryLanguageService {
|
|
|
54
55
|
private getBooleanAndScalarFunctions;
|
|
55
56
|
private getExpressionCacheKey;
|
|
56
57
|
getNodesFromExpression(input: string, nodeType: string): string[];
|
|
58
|
+
validateCircularQueryReferences(expression: string, module: AdaptableModule): void;
|
|
57
59
|
}
|
|
@@ -32,6 +32,7 @@ export class QueryLanguageService {
|
|
|
32
32
|
evaluateCustomQueryVariable: this.evaluateCustomQueryVariable,
|
|
33
33
|
dataChangedEvent: evalContext?.dataChangedEvent,
|
|
34
34
|
pivotResultColumn: evalContext?.pivotResultColumn,
|
|
35
|
+
columnScope: evalContext?.columnScope,
|
|
35
36
|
...this.adaptableApi.internalApi.buildBaseContext(),
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -116,14 +117,7 @@ export class QueryLanguageService {
|
|
|
116
117
|
this.cacheBooleanValidation.set(cacheKey, result);
|
|
117
118
|
return result;
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
const firstRowNode = this.adaptableApi.gridApi.getFirstRowNode();
|
|
121
|
-
// Mock datachangedevent for change-based functions e.g. PERCENT_CHANGE
|
|
122
|
-
const dataChangedEvent = {
|
|
123
|
-
newValue: 100,
|
|
124
|
-
oldValue: 150,
|
|
125
|
-
};
|
|
126
|
-
this.evaluateBooleanExpression(expression, module, firstRowNode, { dataChangedEvent });
|
|
120
|
+
this.validateCircularQueryReferences(expression, module);
|
|
127
121
|
const result = {
|
|
128
122
|
isValid: true,
|
|
129
123
|
errorMessage: '',
|
|
@@ -274,6 +268,8 @@ export class QueryLanguageService {
|
|
|
274
268
|
const columnFriendlyName = this.adaptableApi.columnApi.getFriendlyNameForColumnId(columnId);
|
|
275
269
|
result = result.split(columnId).join(columnFriendlyName);
|
|
276
270
|
});
|
|
271
|
+
// replace $SCOPE() with friendly name as well
|
|
272
|
+
result = result.split(`$SCOPE()`).join(`"<Column Scope>"`);
|
|
277
273
|
return result;
|
|
278
274
|
}
|
|
279
275
|
// Returns the ExpressionFunctions available for the given Module as specified in the `QueryLanguageOptions.moduleExpressionFunctions`
|
|
@@ -543,4 +539,20 @@ export class QueryLanguageService {
|
|
|
543
539
|
return [];
|
|
544
540
|
}
|
|
545
541
|
}
|
|
542
|
+
validateCircularQueryReferences(expression, module) {
|
|
543
|
+
if (!expression || expression.trim() === '') {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const queryNodes = this.getNodesFromExpression(expression, 'QUERY');
|
|
547
|
+
if (!queryNodes.length) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
// circular QUERY references can only be detected by actually evaluating the expression
|
|
551
|
+
// we just evaluate against the first row, as we don't care about the result, just about
|
|
552
|
+
// whether an exception is thrown
|
|
553
|
+
const firstRowNode = this.adaptableApi.gridApi.getFirstRowNode();
|
|
554
|
+
queryNodes.forEach((queryNode) => {
|
|
555
|
+
this.evaluateBooleanExpression(`QUERY("${queryNode}")`, module, firstRowNode);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
546
558
|
}
|
|
@@ -28,7 +28,9 @@ export class ValidationService {
|
|
|
28
28
|
cellDataChangedInfo.rowNode != null &&
|
|
29
29
|
this.adaptableApi.internalApi
|
|
30
30
|
.getQueryLanguageService()
|
|
31
|
-
.evaluateBooleanExpression(alertDefinition.Rule.BooleanExpression, AlertModuleId, cellDataChangedInfo.rowNode
|
|
31
|
+
.evaluateBooleanExpression(alertDefinition.Rule.BooleanExpression, AlertModuleId, cellDataChangedInfo.rowNode, {
|
|
32
|
+
columnScope: cellDataChangedInfo.column?.columnId,
|
|
33
|
+
});
|
|
32
34
|
}
|
|
33
35
|
catch (error) {
|
|
34
36
|
isSatisfiedExpression = false;
|
|
@@ -147,7 +147,7 @@ export const EntityRulesEditor = (props) => {
|
|
|
147
147
|
React.createElement(EntityRulePredicatesEditor, { enablePredicateColumnId: props.enablePredicateColumnId, data: data, descriptions: descriptions, predicateDefs: filteredPredicateDefs, getPredicateDefsForColId: props.getPredicateDefsForColId, onChange: props.onChange }))) : null,
|
|
148
148
|
showBoolean ? (React.createElement(QueryTab, { showRadio: showRadioButtons, value: "BooleanExpression", type: type, label: "Boolean" })) : null,
|
|
149
149
|
showBoolean ? (React.createElement(Tabs.Content, { "data-name": "BooleanExpression", value: 'BooleanExpression' }, (() => {
|
|
150
|
-
const editor = (React.createElement(ExpressionEditor, { type: 'boolean', module: module, value: data.Rule?.BooleanExpression, onChange: setBooleanExpression, initialData: initialData, columns: api.columnApi.internalApi.getQueryableColumnsForUIEditor(), fields: api.expressionApi.internalApi.getAvailableFields(), namedQueries: api.namedQueryApi.getNamedQueries(), api: api, showQueryBuilder: props.showQueryBuilder }));
|
|
150
|
+
const editor = (React.createElement(ExpressionEditor, { type: 'boolean', module: module, value: data.Rule?.BooleanExpression, onChange: setBooleanExpression, initialData: initialData, columns: api.columnApi.internalApi.getQueryableColumnsForUIEditor(), fields: api.expressionApi.internalApi.getAvailableFields(), namedQueries: api.namedQueryApi.getNamedQueries(), api: api, showQueryBuilder: props.showQueryBuilder, columnScope: data.Scope }));
|
|
151
151
|
return props.showQueryBuilder ? React.createElement(Panel, null, editor) : editor;
|
|
152
152
|
})())) : null,
|
|
153
153
|
showObservable ? (React.createElement(QueryTab, { showRadio: showRadioButtons, value: "ObservableExpression", type: type, label: "Observable" })) : null,
|
|
@@ -26,5 +26,13 @@ export const ModuleValueSelector = (props) => {
|
|
|
26
26
|
const sortedOptions = useMemo(() => {
|
|
27
27
|
return ArrayExtensions.sortArrayWithOrder(options, value, { sortUnorderedItems: true });
|
|
28
28
|
}, [allowReorder, options, value]);
|
|
29
|
-
|
|
29
|
+
const moduleFilter = useCallback((module, searchText) => {
|
|
30
|
+
// the module name is camelCased so I want to split it
|
|
31
|
+
// so we can also use spaces in the search text
|
|
32
|
+
const parts = module.split(/(?=[A-Z])/);
|
|
33
|
+
const withSpaces = parts.join(' ');
|
|
34
|
+
return (module.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
35
|
+
withSpaces.toLowerCase().includes(searchText.toLowerCase()));
|
|
36
|
+
}, []);
|
|
37
|
+
return (React.createElement(ValueSelector, { ...valueSelectorProps, options: sortedOptions, value: value, onChange: onChange, showFilterInput: true, filter: moduleFilter, allowReorder: allowReorder, toIdentifier: (module) => module, toLabel: (module) => moduleLabelMap.get(module), isOptionDisabled: props.isOptionDisabled, disabled: props.disabled }));
|
|
30
38
|
};
|