@adaptabletools/adaptable 22.0.10 → 22.1.0-canary.0

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.
Files changed (71) hide show
  1. package/index.css +33 -1
  2. package/index.css.map +1 -1
  3. package/package.json +3 -4
  4. package/src/AdaptableState/Common/AdaptablePredicate.js +1 -1
  5. package/src/AdaptableState/InitialState.d.ts +2 -2
  6. package/src/AdaptableState/LayoutState.d.ts +47 -0
  7. package/src/Api/CalendarApi.d.ts +15 -0
  8. package/src/Api/ColumnScopeApi.d.ts +5 -0
  9. package/src/Api/DataChangeHistoryApi.d.ts +7 -2
  10. package/src/Api/Implementation/CalendarApiImpl.d.ts +3 -0
  11. package/src/Api/Implementation/CalendarApiImpl.js +10 -0
  12. package/src/Api/Implementation/ColumnScopeApiImpl.d.ts +1 -0
  13. package/src/Api/Implementation/ColumnScopeApiImpl.js +12 -0
  14. package/src/Api/Implementation/DataChangeHistoryApiImpl.d.ts +2 -1
  15. package/src/Api/Implementation/DataChangeHistoryApiImpl.js +7 -0
  16. package/src/Api/Implementation/LayoutHelpers.js +12 -0
  17. package/src/Api/Internal/AlertInternalApi.js +4 -1
  18. package/src/Api/Internal/FormatColumnInternalApi.js +3 -3
  19. package/src/Strategy/FlashingCellModule.js +1 -0
  20. package/src/Strategy/PlusMinusModule.js +3 -3
  21. package/src/Utilities/Constants/GeneralConstants.js +2 -1
  22. package/src/Utilities/ExpressionFunctions/booleanExpressionFunctions.d.ts +1 -1
  23. package/src/Utilities/ExpressionFunctions/booleanExpressionFunctions.js +41 -2
  24. package/src/Utilities/ExpressionFunctions/scalarExpressionFunctions.d.ts +1 -1
  25. package/src/Utilities/ExpressionFunctions/scalarExpressionFunctions.js +31 -2
  26. package/src/Utilities/Helpers/AdaptableHelper.js +30 -4
  27. package/src/Utilities/Services/Interface/IQueryLanguageService.d.ts +1 -0
  28. package/src/Utilities/Services/MetamodelService.js +18 -18
  29. package/src/Utilities/Services/QueryLanguageService.d.ts +2 -0
  30. package/src/Utilities/Services/QueryLanguageService.js +20 -8
  31. package/src/Utilities/Services/ValidationService.js +3 -1
  32. package/src/View/Components/EntityRulesEditor/index.js +1 -1
  33. package/src/View/Components/ModuleValueSelector/index.js +9 -1
  34. package/src/View/Components/ReorderDraggable/index.js +22 -36
  35. package/src/View/Components/ValueSelector/index.js +46 -50
  36. package/src/View/Dashboard/PinnedToolbarsSelector.js +1 -1
  37. package/src/View/Layout/Wizard/LayoutWizard.js +16 -1
  38. package/src/View/Layout/Wizard/sections/RowSelectionSection.d.ts +8 -0
  39. package/src/View/Layout/Wizard/sections/RowSelectionSection.js +141 -0
  40. package/src/View/NamedQuery/Wizard/NamedQuerySettingsWizardSection.js +0 -1
  41. package/src/agGrid/AdaptableAgGrid.js +10 -0
  42. package/src/components/Dashboard/Dashboard.js +1 -1
  43. package/src/components/DragAndDropContext/ModuleManager.d.ts +1 -0
  44. package/src/components/DragAndDropContext/ModuleManager.js +12 -37
  45. package/src/components/DragAndDropContext/TabList.d.ts +11 -6
  46. package/src/components/DragAndDropContext/TabList.js +78 -36
  47. package/src/components/DragAndDropContext/UnusedPanel.js +10 -21
  48. package/src/components/ExpressionEditor/BaseEditorInput.d.ts +2 -0
  49. package/src/components/ExpressionEditor/BaseEditorInput.js +4 -0
  50. package/src/components/ExpressionEditor/EditorInput.d.ts +3 -1
  51. package/src/components/ExpressionEditor/EditorInput.js +20 -9
  52. package/src/components/ExpressionEditor/QueryBuilder/QueryBuilder.d.ts +2 -1
  53. package/src/components/ExpressionEditor/QueryBuilder/QueryBuilder.js +3 -12
  54. package/src/components/ExpressionEditor/QueryBuilder/QueryPredicateBuilder.js +17 -19
  55. package/src/components/ExpressionEditor/index.d.ts +2 -1
  56. package/src/components/ExpressionEditor/index.js +1 -1
  57. package/src/components/Tree/TreeDropdown/index.js +38 -27
  58. package/src/components/dnd/index.d.ts +3 -13
  59. package/src/components/dnd/index.js +4 -55
  60. package/src/env.js +2 -2
  61. package/src/layout-manager/src/LayoutManagerModel.d.ts +2 -1
  62. package/src/layout-manager/src/index.d.ts +9 -0
  63. package/src/layout-manager/src/index.js +97 -1
  64. package/src/layout-manager/src/normalizeLayoutModel.js +8 -0
  65. package/src/layout-manager/src/simplifyLayoutModel.js +6 -0
  66. package/src/metamodel/adaptable-metamodel-model.d.ts +22 -13
  67. package/src/metamodel/adaptable.metamodel.d.ts +3773 -5143
  68. package/src/metamodel/adaptable.metamodel.js +1 -1
  69. package/src/parser/src/parser.js +55 -1218
  70. package/src/parser/src/types.d.ts +5 -0
  71. package/tsconfig.esm.tsbuildinfo +1 -1
@@ -1,4 +1,5 @@
1
1
  import { ApiBase } from './ApiBase';
2
+ import { isToday, isTomorrow, isYesterday } from 'date-fns';
2
3
  export class CalendarApiImpl extends ApiBase {
3
4
  getNextWorkingDay() {
4
5
  let counterDate = new Date();
@@ -49,4 +50,13 @@ export class CalendarApiImpl extends ApiBase {
49
50
  }
50
51
  }
51
52
  }
53
+ isToday(dateToCheck) {
54
+ return isToday(dateToCheck);
55
+ }
56
+ isYesterday(dateToCheck) {
57
+ return isYesterday(dateToCheck);
58
+ }
59
+ isTomorrow(dateToCheck) {
60
+ return isTomorrow(dateToCheck);
61
+ }
52
62
  }
@@ -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;
@@ -10,7 +10,8 @@ export declare class DataChangeHistoryApiImpl extends ApiBase implements DataCha
10
10
  getDataChangeHistoryLog(): CellDataChangedInfo[];
11
11
  getDataChangeForGridCell(gridCell: GridCell): CellDataChangedInfo | undefined;
12
12
  addDataChangeHistoryEntry(dataChangeInfo: CellDataChangedInfo): void;
13
- undoDataChangeHistoryEntry(dataChangeInfo: CellDataChangedInfo): void;
13
+ undoDataChangeHistoryEntry(dataChangeInfo: CellDataChangedInfo): GridCell | undefined;
14
+ undoAllDataChangeHistoryEntries(): GridCell[];
14
15
  clearDataChangeHistoryEntry(dataChangeInfo: CellDataChangedInfo): void;
15
16
  openDataChangeHistorySettingsPanel(): void;
16
17
  private getDataChangeHistoryKey;
@@ -44,6 +44,13 @@ export class DataChangeHistoryApiImpl extends ApiBase {
44
44
  undoDataChangeHistoryEntry(dataChangeInfo) {
45
45
  const uniqueKey = this.getDataChangeHistoryKey(dataChangeInfo);
46
46
  this.dispatchAction(DataChangeHistoryUndo(dataChangeInfo, uniqueKey));
47
+ return this.getGridApi().getGridCellFromRowNode(dataChangeInfo.rowNode, dataChangeInfo.column.columnId);
48
+ }
49
+ undoAllDataChangeHistoryEntries() {
50
+ const allChanges = this.getDataChangeHistoryLog();
51
+ return allChanges
52
+ .map((dataChange) => this.undoDataChangeHistoryEntry(dataChange))
53
+ .filter((gridCell) => gridCell != null);
47
54
  }
48
55
  clearDataChangeHistoryEntry(dataChangeInfo) {
49
56
  const uniqueKey = this.getDataChangeHistoryKey(dataChangeInfo);
@@ -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, { dataChangedEvent });
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);
@@ -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 = 'ag-Grid-SelectionColumn';
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
- // so whenever we clone a plain object,
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.noCode === 'item';
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?.props?.find((metamodelProperty) => metamodelProperty.name === optionKey);
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.ref;
72
+ const referenceObjectName = optionMetamodel.r;
73
73
  const referenceObject = optionsObject[optionKey];
74
74
  const referenceObjectMetamodel = this.getAdaptableMetamodel()[referenceObjectName];
75
- if (referenceObject && referenceObjectMetamodel?.kind === 'I') {
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.kind;
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.props
110
- .filter((optionItem) => optionItem.gridInfo === 'container')
109
+ adaptableOptionsMetamodel.p
110
+ .filter((optionItem) => optionItem.g === 'container')
111
111
  .forEach((containerOptionItem) => {
112
- const containerMetamodelName = containerOptionItem.ref;
113
- const adaptableOptionsName = containerOptionItem.name;
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.name, {
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.gridInfo === 'item') {
134
- return optionItemContainer.props.filter(filter).map((itemProperty) => {
133
+ mapGridInfoContainerItems(optionItemContainer, adaptableOptionsValues, defaultAdaptableOptionsValues, filter = (itemProperty) => itemProperty.g === 'item') {
134
+ return optionItemContainer.p.filter(filter).map((itemProperty) => {
135
135
  return {
136
- name: itemProperty.name,
137
- value: adaptableOptionsValues?.[itemProperty.name],
138
- defaultValue: defaultAdaptableOptionsValues[itemProperty.name],
139
- kind: itemProperty.kind,
140
- description: StringExtensions.UnescapeHtmlEntities(itemProperty.desc),
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.uiLabel || this.formatCamelCaseToHumanText(item.name);
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
- // evaluating the expression is the only way to catch circular named query references
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
- return (React.createElement(ValueSelector, { ...valueSelectorProps, options: sortedOptions, value: value, onChange: onChange, allowReorder: allowReorder, toIdentifier: (module) => module, toLabel: (module) => moduleLabelMap.get(module), isOptionDisabled: props.isOptionDisabled, disabled: props.disabled }));
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
  };