@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 +104 -0
- package/aql.js +212 -0
- package/package.json +1 -1
- package/src/AdaptableState/Common/AdaptableAlert.d.ts +15 -0
- package/src/AdaptableState/Common/AdaptableAlert.js +9 -0
- package/src/Api/Events/LayoutChanged.d.ts +5 -3
- package/src/Api/Implementation/AlertApiImpl.d.ts +5 -5
- package/src/Api/Implementation/AlertApiImpl.js +26 -19
- package/src/Api/Internal/AlertInternalApi.js +12 -7
- package/src/Redux/ActionsReducers/LayoutRedux.d.ts +3 -3
- package/src/Redux/Store/AdaptableStore.js +23 -19
- package/src/agGrid/AgGridColumnAdapter.js +3 -1
- package/src/components/aql/QueryBuilder/QueryBuilderInputs.js +11 -1
- package/src/env.js +2 -2
- package/tsconfig.esm.tsbuildinfo +1 -1
- package/upgrade.d.ts +1 -0
- package/upgrade.js +1 -0
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
|
+
"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
|
-
*
|
|
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
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
-
|
|
104
|
-
|
|
103
|
+
const highlightCellChanges = getAlertCellDataChangedInfos(alertToShow);
|
|
104
|
+
if (highlightCellChanges.length &&
|
|
105
105
|
(alertProperties.HighlightCell || alertProperties.HighlightRow)) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
alertNode = this.getGridApi().getRowNodeForPrimaryKey(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
alert.
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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) => {
|