@adaptabletools/adaptable 24.0.0-canary.3 → 24.0.0-canary.5

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/aql.d.ts ADDED
@@ -0,0 +1,104 @@
1
+ import type { AdaptableColumnDataType } from './src/AdaptableState/Common/AdaptableColumn';
2
+ import type { AdaptableStandardExpressionModule } from './src/AdaptableState/Common/Types';
3
+ import { type LegacyExpressionKind } from './src/aql/legacyExpressionKinds';
4
+ /**
5
+ * What an Expression returns, and whether it is evaluated per row or across rows:
6
+ * - `boolean` - Standard boolean Expressions: Grid Filter, Column Filter, Format Column,
7
+ * Alert and Flashing Cell rules, Named Queries, Reports; also Relative Change
8
+ * (`ANY_CHANGE()`) and boolean Observable rules (`CHANGE_COUNT{[price], WITHIN(5m)} >= 2`)
9
+ * - `scalar` - Standard Expressions returning a value: Calculated Columns, scalar Named
10
+ * Queries; also Observable values (`MAX{[price], WITHIN(1d)}`)
11
+ * - `aggregatedScalar` - Aggregation Expressions in Calculated Columns (`SUM{[price]}`),
12
+ * including Cumulative, Quantile and Window Expressions (`LAG{[price], 1, OVER([date])}`)
13
+ * - `aggregatedBoolean` - Aggregation rules in Alerts (`SUM{[price]} > 100`)
14
+ *
15
+ * Which of these a Module accepts, and which Functions it offers, is checked
16
+ * when `module` is passed to {@link validateExpression}.
17
+ */
18
+ export type AdaptableQLExpressionKind = LegacyExpressionKind;
19
+ export interface ValidateExpressionOptions {
20
+ kind: AdaptableQLExpressionKind;
21
+ /**
22
+ * Module the Expression belongs to. Applies that Module's rules: Observable
23
+ * Functions only in Alert, Calculated Column, Format Column and Flashing
24
+ * Cell; Relative Change Functions only in Alert and Flashing Cell;
25
+ * Cumulative and Window Functions only in Calculated Columns; aggregated
26
+ * kinds only in the Modules that evaluate them. When omitted, every
27
+ * system Function is accepted.
28
+ */
29
+ module?: AdaptableStandardExpressionModule;
30
+ /**
31
+ * Column Id to AdapTable data type. When provided, references to other
32
+ * Columns are errors and Column types are checked against Function inputs.
33
+ */
34
+ columns?: Record<string, AdaptableColumnDataType | undefined>;
35
+ }
36
+ export interface ExpressionValidationIssue {
37
+ /** Stable identifier, e.g. `AQL_LEGACY_SPELLING`; absent for generic type errors */
38
+ code?: string;
39
+ message: string;
40
+ severity: 'error' | 'warning' | 'info';
41
+ /** Character range in the Expression `[start, end)` */
42
+ range?: [number, number];
43
+ /** Suggested rewrite, when one exists */
44
+ quickFix?: {
45
+ label: string;
46
+ replacement: string;
47
+ range?: [number, number];
48
+ };
49
+ }
50
+ export interface ExpressionValidationResult {
51
+ isValid: boolean;
52
+ issues: ExpressionValidationIssue[];
53
+ }
54
+ export interface MigrateExpressionResult {
55
+ /** Current-syntax Expression, or the input unchanged when it does not parse */
56
+ expression: string;
57
+ changed: boolean;
58
+ /** Set when the input does not parse */
59
+ error?: string;
60
+ /** How a rewritten legacy observable rule differs in meaning from the original */
61
+ notes?: string[];
62
+ }
63
+ export interface ExpressionFunctionDescription {
64
+ name: string;
65
+ /** `paren` is `NAME(...)`, `brace` is `NAME{...}` */
66
+ callForm: 'paren' | 'brace';
67
+ group: 'scalar' | 'aggregation' | 'observable' | 'clause';
68
+ returnType?: string;
69
+ description?: string;
70
+ signatures: string[];
71
+ examples: string[];
72
+ category?: string;
73
+ }
74
+ /**
75
+ * Validates an AdapTableQL Expression without a grid. Mirrors
76
+ * `ExpressionApi.isValidBooleanExpression` / `isValidScalarExpression` /
77
+ * `isValidAggregatedScalarExpression` / `isValidAggregatedBooleanExpression`,
78
+ * but reports every issue (with ranges and quick fixes) instead of the first.
79
+ *
80
+ * Pre-v24 spellings (`CUMUL(...)`, `TIMEFRAME(...)`, paren aggregations) are
81
+ * errors; convert them with {@link migrateExpression} first.
82
+ */
83
+ export declare function validateExpression(expressionInput: string, options: ValidateExpressionOptions): ExpressionValidationResult;
84
+ /**
85
+ * Converts a pre-v24 AdapTableQL Expression to the current syntax: brace
86
+ * aggregations (`SUM{[price]}`), named operands (`CUMULATIVE(...)`,
87
+ * `WITHIN(5m)`), no `CUMUL(...)` / `TIMEFRAME(...)` wrappers. Same logic as
88
+ * `ExpressionApi.migratePreV24Expression`.
89
+ *
90
+ * Pass `kind` for single-argument dual names: `SUM([x])` is an aggregation
91
+ * in the aggregated kinds and a row-wise scalar elsewhere.
92
+ *
93
+ * Legacy observable rules (`ROW_CHANGE(COUNT([price], 3), TIMEFRAME('5m'))`)
94
+ * are rewritten to observable calls (`CHANGE_COUNT{[price], WITHIN(5m)} >= 3`),
95
+ * as the State upgrade does for Alerts.
96
+ */
97
+ export declare function migrateExpression(expression: string, options?: {
98
+ kind?: AdaptableQLExpressionKind;
99
+ }): MigrateExpressionResult;
100
+ /**
101
+ * Every system Expression Function, aggregation, observable function and
102
+ * clause, with its signatures and examples.
103
+ */
104
+ export declare function getExpressionFunctionCatalogue(): ExpressionFunctionDescription[];
package/aql.js ADDED
@@ -0,0 +1,212 @@
1
+ import { CLAUSE_DOCS, SYSTEM_AGGREGATION_CATALOGUE, compileValueExpression, isAST_Function, mapDataTypeToStaticType, migrateExpression as migrateAqlExpression, normalizeAst, parse, validateAqlExpression, validateExpressionTypes, } from "./vendor/aql/index.js";
2
+ import { AGGREGATED_BOOLEAN_MODULES, moduleAllowsObservableCalls, } from './src/aql/expressionFunctionAvailability';
3
+ import { getMissingColumnIssues } from './src/aql/getMissingColumnIssues';
4
+ import { mapLegacyKindToValidationRequest, toMigrateOptions, } from './src/aql/legacyExpressionKinds';
5
+ import { systemAggregatedExpressionFunctions, systemAggregationClauseFunctions, systemObservableExpressionFunctions, systemScalarExpressionFunctions, } from './src/aql/systemExpressionFunctions';
6
+ const scalarFunctions = {
7
+ ...systemObservableExpressionFunctions,
8
+ ...systemScalarExpressionFunctions,
9
+ };
10
+ const isAggregatedKind = (kind) => kind === 'aggregatedScalar' || kind === 'aggregatedBoolean';
11
+ const AGGREGATED_KIND_MODULES = {
12
+ aggregatedScalar: ['CalculatedColumn'],
13
+ aggregatedBoolean: AGGREGATED_BOOLEAN_MODULES,
14
+ };
15
+ const scalarPool = systemScalarExpressionFunctions;
16
+ const aggregationPool = {
17
+ ...systemAggregatedExpressionFunctions,
18
+ ...systemAggregationClauseFunctions,
19
+ };
20
+ function getModuleAvailabilityIssues(ast, module, aggregated) {
21
+ const issues = [];
22
+ const reported = new Set();
23
+ const visit = (node) => {
24
+ if (node == null || typeof node !== 'object') {
25
+ return;
26
+ }
27
+ if (Array.isArray(node)) {
28
+ node.forEach(visit);
29
+ return;
30
+ }
31
+ if (!isAST_Function(node)) {
32
+ Object.values(node).forEach(visit);
33
+ return;
34
+ }
35
+ const fn = node;
36
+ const kind = fn.kind ?? fn.metaInfo;
37
+ if (kind === 'OBSERVABLE_CALL') {
38
+ return;
39
+ }
40
+ const scalar = kind === 'FUNCTION' ? scalarPool[fn.type] : undefined;
41
+ const modules = (scalar ?? (aggregated ? aggregationPool[fn.type] : undefined))?.isAvailable;
42
+ if (Array.isArray(modules) && !modules.includes(module)) {
43
+ if (!reported.has(fn.type)) {
44
+ reported.add(fn.type);
45
+ issues.push({
46
+ code: 'AQL_NOT_AVAILABLE_IN_MODULE',
47
+ message: `${fn.type} is not available in ${module} Expressions (only in ${modules.join(', ')})`,
48
+ severity: scalar ? 'error' : 'warning',
49
+ range: fn.range,
50
+ });
51
+ }
52
+ return;
53
+ }
54
+ fn.args.forEach(visit);
55
+ };
56
+ visit(ast);
57
+ return issues;
58
+ }
59
+ const error = (message, code) => ({
60
+ code,
61
+ message,
62
+ severity: 'error',
63
+ });
64
+ const toResult = (issues) => {
65
+ const byKey = new Map();
66
+ for (const issue of issues) {
67
+ const key = `${issue.message}::${issue.range?.join(',') ?? ''}`;
68
+ if (!byKey.get(key)?.code) {
69
+ byKey.set(key, issue);
70
+ }
71
+ }
72
+ const unique = [...byKey.values()];
73
+ return {
74
+ isValid: !unique.some((issue) => issue.severity === 'error'),
75
+ issues: unique,
76
+ };
77
+ };
78
+ export function validateExpression(expressionInput, options) {
79
+ const expression = (expressionInput ?? '').trim();
80
+ if (expression === '') {
81
+ return toResult([error(`empty ${options.kind} expression`, 'AQL_EMPTY')]);
82
+ }
83
+ let ast;
84
+ try {
85
+ ast = parse(expression).ast;
86
+ }
87
+ catch (e) {
88
+ return toResult([error(e instanceof Error ? e.message : String(e), 'AQL_PARSE_ERROR')]);
89
+ }
90
+ const columns = options.columns;
91
+ const getColumnType = columns
92
+ ? (columnId) => mapDataTypeToStaticType(columns[columnId])
93
+ : undefined;
94
+ const issues = [];
95
+ if (!isAggregatedKind(options.kind)) {
96
+ const rootFn = ast[ast.length - 1];
97
+ if (rootFn?.type === undefined || scalarFunctions[rootFn.type] === undefined) {
98
+ return toResult([
99
+ error(`provided function ${rootFn?.type} cannot be handled`, 'AQL_UNKNOWN_FUNCTION'),
100
+ ]);
101
+ }
102
+ const typeCheck = validateExpressionTypes(ast, scalarFunctions, {
103
+ getBraceCallSpec: SYSTEM_AGGREGATION_CATALOGUE.getBraceCallSpec,
104
+ getColumnType,
105
+ });
106
+ issues.push(...typeCheck.issues);
107
+ }
108
+ const { module } = options;
109
+ let kindInModule = true;
110
+ if (module && isAggregatedKind(options.kind)) {
111
+ const modules = AGGREGATED_KIND_MODULES[options.kind];
112
+ kindInModule = modules.includes(module);
113
+ if (!kindInModule) {
114
+ issues.push(error(`${module} does not evaluate ${options.kind} Expressions (only ${modules.join(', ')} does)`, 'AQL_KIND_NOT_IN_MODULE'));
115
+ }
116
+ }
117
+ const structure = validateAqlExpression(ast, {
118
+ request: {
119
+ ...mapLegacyKindToValidationRequest(options.kind),
120
+ requiresAggCalls: false,
121
+ allowsObservableCalls: module ? moduleAllowsObservableCalls(module) : true,
122
+ },
123
+ scalarFunctions,
124
+ catalogue: SYSTEM_AGGREGATION_CATALOGUE,
125
+ getColumnType,
126
+ });
127
+ issues.push(...structure.issues);
128
+ if (module && kindInModule) {
129
+ issues.push(...getModuleAvailabilityIssues(structure.normalizedAst, module, isAggregatedKind(options.kind)));
130
+ }
131
+ if (columns) {
132
+ issues.push(...getMissingColumnIssues(ast, (columnId) => columnId in columns).map((issue) => ({
133
+ ...issue,
134
+ code: 'AQL_UNKNOWN_COLUMN',
135
+ })));
136
+ }
137
+ if (isAggregatedKind(options.kind) && !issues.some((issue) => issue.severity === 'error')) {
138
+ const mismatch = error(`provided ${options.kind} expression does not evaluate to a supported aggregation`, 'AQL_AGG_REQUIRED');
139
+ try {
140
+ const compiled = compileValueExpression(normalizeAst(ast), {
141
+ registry: SYSTEM_AGGREGATION_CATALOGUE,
142
+ getColumnType,
143
+ scalarFunctions: systemScalarExpressionFunctions,
144
+ });
145
+ const isBoolean = compiled.outputType === 'boolean';
146
+ if (compiled.aggregations.length === 0 ||
147
+ (options.kind === 'aggregatedBoolean' && !isBoolean) ||
148
+ (options.kind === 'aggregatedScalar' && isBoolean)) {
149
+ issues.push(mismatch);
150
+ }
151
+ }
152
+ catch (e) {
153
+ issues.push(error(e instanceof Error ? e.message : String(e)));
154
+ }
155
+ }
156
+ return toResult(issues);
157
+ }
158
+ export function migrateExpression(expression, options) {
159
+ const result = migrateAqlExpression((expression ?? '').trim(), {
160
+ ...toMigrateOptions(options?.kind),
161
+ rewriteLegacyObservables: true,
162
+ });
163
+ return {
164
+ expression: result.expression,
165
+ changed: result.changed,
166
+ ...(result.error !== undefined ? { error: result.error } : {}),
167
+ ...(result.notes?.length ? { notes: result.notes.map((note) => note.message) } : {}),
168
+ };
169
+ }
170
+ export function getExpressionFunctionCatalogue() {
171
+ const scalar = Object.entries(systemScalarExpressionFunctions).map(([name, fn]) => ({
172
+ name,
173
+ callForm: 'paren',
174
+ group: 'scalar',
175
+ returnType: fn.returnType,
176
+ description: fn.description,
177
+ signatures: fn.signatures ?? [],
178
+ examples: fn.examples ?? [],
179
+ category: fn.category,
180
+ }));
181
+ const aggregations = SYSTEM_AGGREGATION_CATALOGUE.callables()
182
+ .filter((callable) => callable.system)
183
+ .map((callable) => ({
184
+ name: callable.name,
185
+ callForm: callable.callForm,
186
+ group: 'aggregation',
187
+ description: callable.docs.description,
188
+ signatures: callable.docs.signatures ?? [],
189
+ examples: callable.docs.examples ?? [],
190
+ category: callable.docs.category,
191
+ }));
192
+ const observables = Object.entries(systemObservableExpressionFunctions).map(([name, fn]) => ({
193
+ name,
194
+ callForm: 'brace',
195
+ group: 'observable',
196
+ returnType: fn.returnType,
197
+ description: fn.description,
198
+ signatures: fn.signatures ?? [],
199
+ examples: fn.examples ?? [],
200
+ category: fn.category,
201
+ }));
202
+ const clauses = Object.entries(CLAUSE_DOCS).map(([name, docs]) => ({
203
+ name,
204
+ callForm: 'paren',
205
+ group: 'clause',
206
+ description: docs.description,
207
+ signatures: docs.signatures,
208
+ examples: docs.examples,
209
+ category: docs.category,
210
+ }));
211
+ return [...scalar, ...aggregations, ...observables, ...clauses];
212
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptabletools/adaptable",
3
- "version": "24.0.0-canary.3",
3
+ "version": "24.0.0-canary.5",
4
4
  "description": "Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements",
5
5
  "keywords": [
6
6
  "web-components",
@@ -21,8 +21,18 @@ export interface AdaptableGenericAlert extends AdaptableAlertBase<'generic'> {
21
21
  export interface AdaptableCellChangedAlert<TData = any> extends AdaptableAlertBase<'cellChanged'> {
22
22
  /**
23
23
  * Cell DataChange which triggered Alert
24
+ *
25
+ * For a consolidated batch Alert this is the first affected cell (jump-to-cell lands here).
24
26
  */
25
27
  cellDataChangedInfo: CellDataChangedInfo<TData>;
28
+ /**
29
+ * All cell changes represented by this Alert when several changes from one batch
30
+ * action were consolidated (`occurrenceCount` > 1). The first entry matches
31
+ * {@link AdaptableCellChangedAlert.cellDataChangedInfo}.
32
+ *
33
+ * Used so cell / row highlighting still applies to every affected cell.
34
+ */
35
+ cellDataChangedInfos?: CellDataChangedInfo<TData>[];
26
36
  }
27
37
  /**
28
38
  * Defines an Alert fired by AdapTable when a row is changed
@@ -64,3 +74,8 @@ export interface AdaptableAlertBase<T> extends Identifiable {
64
74
  }
65
75
  export declare const isAdaptableCellChangedAlert: (alert: AdaptableAlert) => alert is AdaptableCellChangedAlert;
66
76
  export declare const isAdaptableRowChangedAlert: (alert: AdaptableAlert) => alert is AdaptableRowChangedAlert;
77
+ /**
78
+ * Cell changes to highlight (or clear) for an Alert: every cell in a
79
+ * consolidated batch, otherwise the single {@link AdaptableCellChangedAlert.cellDataChangedInfo}.
80
+ */
81
+ export declare const getAlertCellDataChangedInfos: (alert: AdaptableAlert) => CellDataChangedInfo[];
@@ -4,3 +4,12 @@ export const isAdaptableCellChangedAlert = (alert) => {
4
4
  export const isAdaptableRowChangedAlert = (alert) => {
5
5
  return alert.alertType === 'rowChanged';
6
6
  };
7
+ export const getAlertCellDataChangedInfos = (alert) => {
8
+ if (!isAdaptableCellChangedAlert(alert)) {
9
+ return [];
10
+ }
11
+ if (alert.cellDataChangedInfos?.length) {
12
+ return alert.cellDataChangedInfos;
13
+ }
14
+ return alert.cellDataChangedInfo ? [alert.cellDataChangedInfo] : [];
15
+ };
@@ -17,11 +17,13 @@ export interface LayoutChangedInfo extends BaseContext {
17
17
  */
18
18
  newLayoutState: LayoutState;
19
19
  /**
20
- * Facets of the **current** Layout that changed.
20
+ * Which parts of the **current** Layout changed.
21
+ *
22
+ * Use this when you care *what* changed, rather than which `actionName` fired:
21
23
  *
22
24
  * - `LAYOUT_SAVE`: diffs non-filter facets (e.g. `ColumnSorting`, `RowGrouping`)
23
- * - Column Filter actions: `['ColumnFilter']`
24
- * - Grid Filter actions: `['GridFilter']`
25
+ * - Column Filter actions: always `['ColumnFilter']`
26
+ * - Grid Filter actions: always `['GridFilter']`
25
27
  *
26
28
  * Omitted for layout identity changes (`LAYOUT_SELECT`, `LAYOUT_ADD`, `LAYOUT_DELETE`, `LAYOUT_READY`).
27
29
  */
@@ -38,10 +38,9 @@ export declare class AlertApiImpl extends ApiBase implements AlertApi {
38
38
  private applyPerChangeAlertEffects;
39
39
  /**
40
40
  * Effects that should only happen once for a given Alert: adding the Alert to the store (so it
41
- * shows in the toolbar/status bar), the toast notification, jumping to the cell/row and
42
- * highlighting the cell/row (which reads the highlight from the just-stored Alert). When Alerts
43
- * are consolidated these run a single time for the consolidated Alert, jumping to / highlighting
44
- * the first affected cell/row.
41
+ * shows in the toolbar/status bar), the toast notification and jumping to the cell/row.
42
+ * Highlighting reads the just-stored Alert and is applied to every cell in
43
+ * `cellDataChangedInfos` when the Alert is a consolidated batch.
45
44
  */
46
45
  private applyOnceAlertEffects;
47
46
  /**
@@ -69,7 +68,8 @@ export declare class AlertApiImpl extends ApiBase implements AlertApi {
69
68
  /**
70
69
  * Builds a single consolidated Alert from a group of Alerts fired by the same Alert Definition.
71
70
  * The representative (first) Alert's context is retained (so jump-to-cell lands on the first
72
- * affected cell) and an occurrence count / summary message is applied.
71
+ * affected cell) and an occurrence count / summary message is applied. Every cell change is
72
+ * kept on `cellDataChangedInfos` so highlighting still covers the whole batch.
73
73
  *
74
74
  * When every Alert in the group shares the same message (typical for a Bulk Update that sets the
75
75
  * same value across cells), we keep that message and append the "N cells triggered this Alert"
@@ -4,7 +4,7 @@ import * as AlertRedux from '../../Redux/ActionsReducers/AlertRedux';
4
4
  import { ApiBase } from './ApiBase';
5
5
  import * as ModuleConstants from '../../Utilities/Constants/ModuleConstants';
6
6
  import ObjectFactory from '../../Utilities/ObjectFactory';
7
- import { isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
7
+ import { getAlertCellDataChangedInfos, isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
8
8
  import StringExtensions from '../../Utilities/Extensions/StringExtensions';
9
9
  import { AlertInternalApi } from '../Internal/AlertInternalApi';
10
10
  import { resolveContainerElement } from '../../Utilities/resolveContainerElement';
@@ -100,26 +100,29 @@ export class AlertApiImpl extends ApiBase {
100
100
  this.getGridApi().jumpToRow(firstRowNode);
101
101
  }
102
102
  }
103
- if (isAdaptableCellChangedAlert(alertToShow) &&
104
- alertToShow.cellDataChangedInfo &&
103
+ const highlightCellChanges = getAlertCellDataChangedInfos(alertToShow);
104
+ if (highlightCellChanges.length &&
105
105
  (alertProperties.HighlightCell || alertProperties.HighlightRow)) {
106
- let alertNode = alertToShow.cellDataChangedInfo.rowNode;
107
- if (!alertNode) {
108
- alertNode = this.getGridApi().getRowNodeForPrimaryKey(alertToShow.cellDataChangedInfo.primaryKeyValue);
109
- }
110
- if (alertNode) {
111
- if (alertProperties.HighlightRow) {
112
- this.getGridApi().refreshRowNodes([alertNode]);
113
- setTimeout(() => {
114
- this.dispatchAction(InternalRedux.AdaptableAlertRemoveRowHighlight(alertToShow));
115
- }, this.getAlertOptions().rowHighlightDuration);
116
- }
117
- if (alertProperties.HighlightCell && alertToShow.cellDataChangedInfo.column) {
118
- this.getGridApi().refreshCell(alertNode, alertToShow.cellDataChangedInfo.column.columnId);
119
- setTimeout(() => {
120
- this.dispatchAction(InternalRedux.AdaptableAlertRemoveCellHighlight(alertToShow));
121
- }, this.getAlertOptions().cellHighlightDuration);
106
+ const highlightNodes = [];
107
+ highlightCellChanges.forEach((cellChange) => {
108
+ const alertNode = cellChange.rowNode ?? this.getGridApi().getRowNodeForPrimaryKey(cellChange.primaryKeyValue);
109
+ if (alertNode) {
110
+ highlightNodes.push(alertNode);
111
+ if (alertProperties.HighlightCell && cellChange.column) {
112
+ this.getGridApi().refreshCell(alertNode, cellChange.column.columnId);
113
+ }
122
114
  }
115
+ });
116
+ if (alertProperties.HighlightRow && highlightNodes.length) {
117
+ this.getGridApi().refreshRowNodes(highlightNodes);
118
+ setTimeout(() => {
119
+ this.dispatchAction(InternalRedux.AdaptableAlertRemoveRowHighlight(alertToShow));
120
+ }, this.getAlertOptions().rowHighlightDuration);
121
+ }
122
+ if (alertProperties.HighlightCell) {
123
+ setTimeout(() => {
124
+ this.dispatchAction(InternalRedux.AdaptableAlertRemoveCellHighlight(alertToShow));
125
+ }, this.getAlertOptions().cellHighlightDuration);
123
126
  }
124
127
  }
125
128
  else if (alertProperties.HighlightRow &&
@@ -188,10 +191,14 @@ export class AlertApiImpl extends ApiBase {
188
191
  const message = allSameMessage && StringExtensions.IsNotNullOrEmpty(representative.message)
189
192
  ? `${representative.message}\n${summaryMessage}`
190
193
  : summaryMessage;
194
+ const cellDataChangedInfos = group.flatMap((alert) => getAlertCellDataChangedInfos(alert));
191
195
  return {
192
196
  ...representative,
193
197
  occurrenceCount: count,
194
198
  message,
199
+ ...(isAdaptableCellChangedAlert(representative) && cellDataChangedInfos.length
200
+ ? { cellDataChangedInfos }
201
+ : {}),
195
202
  };
196
203
  }
197
204
  displayAdaptableAlertNotification(alert) {
@@ -1,6 +1,6 @@
1
1
  import { ApiBase } from '../Implementation/ApiBase';
2
2
  import { expressionKindForAlertType, isRowChangeAlertDefinition, } from '../../AdaptableState/AlertState';
3
- import { isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
3
+ import { getAlertCellDataChangedInfos, isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
4
4
  import ArrayExtensions from '../../Utilities/Extensions/ArrayExtensions';
5
5
  import { AlertModuleId } from '../../Utilities/Constants/ModuleConstants';
6
6
  import ObjectFactory from '../../Utilities/ObjectFactory';
@@ -476,9 +476,16 @@ export class AlertInternalApi extends ApiBase {
476
476
  return this.getAdaptableAlerts().filter(isAdaptableRowChangedAlert);
477
477
  }
478
478
  getAdaptableAlertWithHighlightCell(columnId, rowNode) {
479
- return this.getAdaptableCellChangedAlerts().find((alert) => getRuleAlertProperties(alert.alertDefinition)?.HighlightCell &&
480
- alert.cellDataChangedInfo?.column?.columnId === columnId &&
481
- alert.cellDataChangedInfo?.rowNode === rowNode);
479
+ const primaryKeyValue = this.getGridApi().getPrimaryKeyValueForRowNode(rowNode);
480
+ return this.getAdaptableCellChangedAlerts().find((alert) => {
481
+ if (!getRuleAlertProperties(alert.alertDefinition)?.HighlightCell) {
482
+ return false;
483
+ }
484
+ return getAlertCellDataChangedInfos(alert).some((cellChange) => cellChange.column?.columnId === columnId &&
485
+ (cellChange.rowNode === rowNode ||
486
+ cellChange.rowNode?.id === rowNode.id ||
487
+ (primaryKeyValue != null && cellChange.primaryKeyValue === primaryKeyValue)));
488
+ });
482
489
  }
483
490
  getAdaptableAlertWithHighlightRow(rowNode) {
484
491
  let alertWithRowHighlight;
@@ -494,9 +501,7 @@ export class AlertInternalApi extends ApiBase {
494
501
  }
495
502
  alertWithRowHighlight = this.getAdaptableCellChangedAlerts().find((alert) => {
496
503
  if (getRuleAlertProperties(alert.alertDefinition)?.HighlightRow) {
497
- if (alert.cellDataChangedInfo?.rowNode.id == rowNode.id) {
498
- return alert;
499
- }
504
+ return getAlertCellDataChangedInfos(alert).some((cellChange) => cellChange.rowNode?.id == rowNode.id);
500
505
  }
501
506
  });
502
507
  return alertWithRowHighlight;
@@ -4,7 +4,7 @@ import { LayoutState, Layout } from '../../AdaptableState/LayoutState';
4
4
  import { ColumnFilter } from '../../types';
5
5
  import { LayoutExtensionInfo, LayoutExtensionModule } from '../../AdaptableState/Common/ExtendedLayout';
6
6
  /**
7
- * @ReduxAction Layout Module is ready
7
+ * @ReduxAction Layout Module is ready — fired once after AdapTable starts, for the Layout that is already current
8
8
  */
9
9
  export declare const LAYOUT_READY = "LAYOUT_READY";
10
10
  /**
@@ -20,11 +20,11 @@ export declare const LAYOUT_EDIT = "LAYOUT_EDIT";
20
20
  */
21
21
  export declare const LAYOUT_DELETE = "LAYOUT_DELETE";
22
22
  /**
23
- * @ReduxAction A Layout has been selected
23
+ * @ReduxAction A different Layout has been selected
24
24
  */
25
25
  export declare const LAYOUT_SELECT = "LAYOUT_SELECT";
26
26
  /**
27
- * @ReduxAction A Layout has been (auto)saved
27
+ * @ReduxAction A Layout has been saved, including auto-save after grid edits (sort, grouping, columns, and so on)
28
28
  */
29
29
  export declare const LAYOUT_SAVE = "LAYOUT_SAVE";
30
30
  /**
@@ -5,7 +5,7 @@ import { PROGRESS_INDICATOR_HIDE, PROGRESS_INDICATOR_SHOW } from '../ActionsRedu
5
5
  import { createEngine as createEngineLocal } from './AdaptableReduxLocalStorageEngine';
6
6
  import { mergeReducer } from './AdaptableReduxMerger';
7
7
  import { isRowChangeAlertDefinition } from '../../AdaptableState/AlertState';
8
- import { isAdaptableCellChangedAlert, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
8
+ import { getAlertCellDataChangedInfos, isAdaptableRowChangedAlert, } from '../../AdaptableState/Common/AdaptableAlert';
9
9
  import { closeAgChartDefinitionIfOpen, closeExternalChartDefinitionIfOpen, } from '../../Utilities/Helpers/ChartHelper';
10
10
  import { refreshScheduledAlertJobs, refreshScheduledReportJobs, } from '../../Utilities/Helpers/Scheduling/ScheduledJobsMiddlewareHelper';
11
11
  import { getRuleAlertProperties, isScheduledAlertDefinition, } from '../../Utilities/Helpers/Scheduling/ScheduledAlertHelper';
@@ -712,11 +712,12 @@ const adaptableMiddleware = (adaptable) => (function(middlewareAPI) {
712
712
  const actionTyped = action;
713
713
  let ret = next(action);
714
714
  const adaptableAlert = actionTyped.alert;
715
- if (getRuleAlertProperties(adaptableAlert.alertDefinition)?.HighlightCell &&
716
- isAdaptableCellChangedAlert(adaptableAlert) &&
717
- adaptableAlert.cellDataChangedInfo) {
718
- const rowNode = adaptableAlert.cellDataChangedInfo.rowNode;
719
- adaptable.api.gridApi.refreshCell(rowNode, adaptableAlert.cellDataChangedInfo.column.columnId, true);
715
+ if (getRuleAlertProperties(adaptableAlert.alertDefinition)?.HighlightCell) {
716
+ getAlertCellDataChangedInfos(adaptableAlert).forEach((cellChange) => {
717
+ if (cellChange.rowNode && cellChange.column) {
718
+ adaptable.api.gridApi.refreshCell(cellChange.rowNode, cellChange.column.columnId, true);
719
+ }
720
+ });
720
721
  }
721
722
  if (getRuleAlertProperties(adaptableAlert.alertDefinition)?.HighlightRow &&
722
723
  isAdaptableRowChangedAlert(adaptableAlert) &&
@@ -730,11 +731,12 @@ const adaptableMiddleware = (adaptable) => (function(middlewareAPI) {
730
731
  let ret = next(action);
731
732
  let alerts = actionTyped.alerts;
732
733
  alerts.forEach((alert) => {
733
- if (getRuleAlertProperties(alert.alertDefinition)?.HighlightCell &&
734
- isAdaptableCellChangedAlert(alert) &&
735
- alert.cellDataChangedInfo) {
736
- let rowNode = alert.cellDataChangedInfo.rowNode;
737
- adaptable.api.gridApi.refreshCell(rowNode, alert.cellDataChangedInfo.column.columnId, true);
734
+ if (getRuleAlertProperties(alert.alertDefinition)?.HighlightCell) {
735
+ getAlertCellDataChangedInfos(alert).forEach((cellChange) => {
736
+ if (cellChange.rowNode && cellChange.column) {
737
+ adaptable.api.gridApi.refreshCell(cellChange.rowNode, cellChange.column.columnId, true);
738
+ }
739
+ });
738
740
  }
739
741
  if (getRuleAlertProperties(alert.alertDefinition)?.HighlightRow &&
740
742
  isAdaptableRowChangedAlert(alert) &&
@@ -749,11 +751,11 @@ const adaptableMiddleware = (adaptable) => (function(middlewareAPI) {
749
751
  const actionTyped = action;
750
752
  const adaptableAlert = actionTyped.alert;
751
753
  if (getRuleAlertProperties(adaptableAlert.alertDefinition)?.HighlightCell) {
752
- if (isAdaptableCellChangedAlert(adaptableAlert) &&
753
- adaptableAlert.cellDataChangedInfo) {
754
- const rowNode = adaptableAlert.cellDataChangedInfo.rowNode;
755
- adaptable.api.gridApi.refreshCell(rowNode, adaptableAlert.cellDataChangedInfo.column.columnId, true);
756
- }
754
+ getAlertCellDataChangedInfos(adaptableAlert).forEach((cellChange) => {
755
+ if (cellChange.rowNode && cellChange.column) {
756
+ adaptable.api.gridApi.refreshCell(cellChange.rowNode, cellChange.column.columnId, true);
757
+ }
758
+ });
757
759
  }
758
760
  return ret;
759
761
  }
@@ -762,9 +764,11 @@ const adaptableMiddleware = (adaptable) => (function(middlewareAPI) {
762
764
  const actionTyped = action;
763
765
  const adaptableAlert = actionTyped.alert;
764
766
  if (getRuleAlertProperties(adaptableAlert.alertDefinition)?.HighlightRow) {
765
- if (isAdaptableCellChangedAlert(adaptableAlert) &&
766
- adaptableAlert.cellDataChangedInfo) {
767
- adaptable.api.gridApi.refreshRowNodes([adaptableAlert.cellDataChangedInfo.rowNode]);
767
+ const highlightRowNodes = getAlertCellDataChangedInfos(adaptableAlert)
768
+ .map((cellChange) => cellChange.rowNode)
769
+ .filter((rowNode) => !!rowNode);
770
+ if (highlightRowNodes.length) {
771
+ adaptable.api.gridApi.refreshRowNodes(highlightRowNodes);
768
772
  }
769
773
  if (isAdaptableRowChangedAlert(adaptableAlert) && adaptableAlert.rowDataChangedInfo) {
770
774
  adaptable.api.gridApi.refreshRowNodes(adaptableAlert.rowDataChangedInfo.rowNodes);
@@ -928,6 +928,9 @@ export class AgGridColumnAdapter {
928
928
  return this.adaptableFloatingFilterComponent;
929
929
  }
930
930
  setupColumnFloatingFilter({ col, colDef }) {
931
+ if (!this.adaptableOptions.filterOptions.useAdaptableFiltering) {
932
+ return;
933
+ }
931
934
  const userProvidedFilterProp = this.getUserColDefProperty(col.getColId(), 'filter');
932
935
  const hasInvalidFilterProp = typeof userProvidedFilterProp !== 'boolean' &&
933
936
  typeof userProvidedFilterProp?.handler !== 'function' &&
@@ -938,7 +941,6 @@ export class AgGridColumnAdapter {
938
941
  }
939
942
  const isFloatingFilterDisabled = !colDef.filter ||
940
943
  !colDef.floatingFilter ||
941
- !this.adaptableOptions.filterOptions.useAdaptableFiltering ||
942
944
  !this.adaptableApi.filterApi.columnFilterApi.isQuickFilterVisible();
943
945
  if (this.adaptableApi.columnApi.isAutoRowGroupColumnForMulti(col.getColId())) {
944
946
  this.setColDefProperty(col, 'floatingFilter', (original_floatingFilter) => {