@finos/legend-query-builder 4.14.26 → 4.14.28
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts +4 -0
- package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts.map +1 -1
- package/lib/components/result/tds/QueryBuilderTDSGridResult.js +126 -37
- package/lib/components/result/tds/QueryBuilderTDSGridResult.js.map +1 -1
- package/lib/index.css +17 -1
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/QueryBuilderResultState.d.ts +12 -0
- package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderResultState.js +68 -4
- package/lib/stores/QueryBuilderResultState.js.map +1 -1
- package/lib/stores/QueryBuilderState.d.ts +2 -1
- package/lib/stores/QueryBuilderState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderState.js +10 -2
- package/lib/stores/QueryBuilderState.js.map +1 -1
- package/package.json +3 -3
- package/src/components/result/tds/QueryBuilderTDSGridResult.tsx +188 -11
- package/src/stores/QueryBuilderResultState.ts +82 -4
- package/src/stores/QueryBuilderState.ts +21 -0
@@ -14,11 +14,10 @@
|
|
14
14
|
* limitations under the License.
|
15
15
|
*/
|
16
16
|
|
17
|
-
import { clsx } from '@finos/legend-art';
|
18
17
|
import { observer } from 'mobx-react-lite';
|
19
18
|
import type { QueryBuilderState } from '../../../stores/QueryBuilderState.js';
|
20
19
|
import { PRIMITIVE_TYPE, type TDSExecutionResult } from '@finos/legend-graph';
|
21
|
-
import { useState, useCallback } from 'react';
|
20
|
+
import { useState, useCallback, useEffect } from 'react';
|
22
21
|
import {
|
23
22
|
DataGrid,
|
24
23
|
type DataGridApi,
|
@@ -28,23 +27,45 @@ import {
|
|
28
27
|
type DataGridGetContextMenuItemsParams,
|
29
28
|
type DataGridIRowNode,
|
30
29
|
type DataGridMenuItemDef,
|
30
|
+
type DataGridIAggFuncParams,
|
31
31
|
} from '@finos/legend-lego/data-grid';
|
32
32
|
import {
|
33
33
|
getRowDataFromExecutionResult,
|
34
34
|
type IQueryRendererParamsWithGridType,
|
35
35
|
filterByOrOutValues,
|
36
36
|
} from './QueryBuilderTDSResultShared.js';
|
37
|
-
import
|
38
|
-
QueryBuilderResultState,
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
import {
|
38
|
+
type QueryBuilderResultState,
|
39
|
+
QueryBuilderResultWavgAggregationState,
|
40
|
+
type QueryBuilderTDSResultCellData,
|
41
|
+
type QueryBuilderTDSResultCellDataType,
|
42
|
+
type QueryBuilderTDSRowDataType,
|
42
43
|
} from '../../../stores/QueryBuilderResultState.js';
|
43
44
|
import { QueryBuilderTDSState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
|
44
45
|
import { DEFAULT_LOCALE } from '../../../graph-manager/QueryBuilderConst.js';
|
45
|
-
import {
|
46
|
+
import {
|
47
|
+
assertErrorThrown,
|
48
|
+
isNumber,
|
49
|
+
isString,
|
50
|
+
isValidURL,
|
51
|
+
} from '@finos/legend-shared';
|
46
52
|
import { useApplicationStore } from '@finos/legend-application';
|
47
53
|
import { QUERY_BUILDER_TEST_ID } from '../../../__lib__/QueryBuilderTesting.js';
|
54
|
+
import {
|
55
|
+
clsx,
|
56
|
+
Modal,
|
57
|
+
ModalBody,
|
58
|
+
ModalHeader,
|
59
|
+
ModalFooter,
|
60
|
+
ModalFooterButton,
|
61
|
+
Dialog,
|
62
|
+
CustomSelectorInput,
|
63
|
+
} from '@finos/legend-art';
|
64
|
+
|
65
|
+
export const enum QueryBuilderDataGridCustomAggregationFunction {
|
66
|
+
wavg = 'wavg',
|
67
|
+
WAVG = 'WAVG',
|
68
|
+
}
|
48
69
|
|
49
70
|
const getAggregationTDSColumnCustomizations = (
|
50
71
|
result: TDSExecutionResult,
|
@@ -71,7 +92,7 @@ const getAggregationTDSColumnCustomizations = (
|
|
71
92
|
case PRIMITIVE_TYPE.FLOAT:
|
72
93
|
return {
|
73
94
|
filter: 'agNumberColumnFilter',
|
74
|
-
allowedAggFuncs: ['count', 'sum', 'max', 'min', 'avg'],
|
95
|
+
allowedAggFuncs: ['count', 'sum', 'max', 'min', 'avg', 'wavg'],
|
75
96
|
};
|
76
97
|
default:
|
77
98
|
return {
|
@@ -222,16 +243,19 @@ export const QueryBuilderTDSGridResult = observer(
|
|
222
243
|
}) => {
|
223
244
|
const { executionResult, queryBuilderState } = props;
|
224
245
|
const applicationStore = useApplicationStore();
|
246
|
+
const darkMode =
|
247
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
|
225
248
|
const [columnAPi, setColumnApi] = useState<DataGridColumnApi | undefined>(
|
226
249
|
undefined,
|
227
250
|
);
|
251
|
+
const [aggFuncParams, setAggFuncParams] = useState<
|
252
|
+
DataGridIAggFuncParams | undefined
|
253
|
+
>(undefined);
|
228
254
|
const resultState = queryBuilderState.resultState;
|
229
255
|
const isLocalModeEnabled = queryBuilderState.isLocalModeEnabled;
|
230
256
|
const colDefs = isLocalModeEnabled
|
231
257
|
? getLocalColDefs(executionResult, resultState)
|
232
258
|
: getColDefs(executionResult, resultState);
|
233
|
-
const darkMode =
|
234
|
-
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
|
235
259
|
|
236
260
|
const onSaveGridColumnState = (): void => {
|
237
261
|
if (!columnAPi) {
|
@@ -242,6 +266,10 @@ export const QueryBuilderTDSGridResult = observer(
|
|
242
266
|
isPivotModeEnabled: columnAPi.isPivotMode(),
|
243
267
|
isLocalModeEnabled: true,
|
244
268
|
previewLimit: resultState.previewLimit,
|
269
|
+
...(resultState.wavgAggregationState?.weightedColumnIdPairs && {
|
270
|
+
weightedColumnPairs:
|
271
|
+
resultState.wavgAggregationState.weightedColumnIdPairs,
|
272
|
+
}),
|
245
273
|
});
|
246
274
|
};
|
247
275
|
|
@@ -336,6 +364,103 @@ export const QueryBuilderTDSGridResult = observer(
|
|
336
364
|
],
|
337
365
|
);
|
338
366
|
|
367
|
+
const weightedColumnOptions = columnAPi
|
368
|
+
?.getColumns()
|
369
|
+
?.filter((c) => c.getColDef().cellDataType === 'number')
|
370
|
+
.map((col) => ({
|
371
|
+
label: col.getColId(),
|
372
|
+
value: col.getColId(),
|
373
|
+
}));
|
374
|
+
|
375
|
+
const selectedWeightedColumn =
|
376
|
+
aggFuncParams?.colDef.field &&
|
377
|
+
resultState.wavgAggregationState?.weightedColumnIdPairs.get(
|
378
|
+
aggFuncParams.colDef.field,
|
379
|
+
)
|
380
|
+
? {
|
381
|
+
label: resultState.wavgAggregationState.weightedColumnIdPairs.get(
|
382
|
+
aggFuncParams.colDef.field,
|
383
|
+
),
|
384
|
+
value: resultState.wavgAggregationState.weightedColumnIdPairs.get(
|
385
|
+
aggFuncParams.colDef.field,
|
386
|
+
),
|
387
|
+
}
|
388
|
+
: null;
|
389
|
+
|
390
|
+
const onWeightedColumnOptionChange = async (
|
391
|
+
option: { label: string; value: string } | null,
|
392
|
+
): Promise<void> => {
|
393
|
+
if (aggFuncParams?.colDef.field && option?.value) {
|
394
|
+
resultState.wavgAggregationState?.addWeightedColumnIdPair(
|
395
|
+
aggFuncParams.colDef.field,
|
396
|
+
option.value,
|
397
|
+
);
|
398
|
+
}
|
399
|
+
};
|
400
|
+
|
401
|
+
const weightedAverage = (param: DataGridIAggFuncParams): void => {
|
402
|
+
if (param.colDef.field) {
|
403
|
+
if (!resultState.wavgAggregationState) {
|
404
|
+
resultState.setWavgAggregationState(
|
405
|
+
new QueryBuilderResultWavgAggregationState(),
|
406
|
+
);
|
407
|
+
}
|
408
|
+
resultState.wavgAggregationState?.addWeightedColumnIdPair(
|
409
|
+
param.colDef.field,
|
410
|
+
param.colDef.field,
|
411
|
+
);
|
412
|
+
resultState.wavgAggregationState?.setIsApplyingWavg(true);
|
413
|
+
setAggFuncParams(param);
|
414
|
+
} else {
|
415
|
+
applicationStore.notificationService.notifyError(
|
416
|
+
'The id of this column can`t be retrieved to perform weighted average',
|
417
|
+
);
|
418
|
+
}
|
419
|
+
};
|
420
|
+
|
421
|
+
const weightedAverageHelper = (param: DataGridIAggFuncParams): number => {
|
422
|
+
try {
|
423
|
+
const column = param.colDef.field;
|
424
|
+
if (column) {
|
425
|
+
const weightedColumnId =
|
426
|
+
resultState.wavgAggregationState?.weightedColumnIdPairs.get(column);
|
427
|
+
if (weightedColumnId) {
|
428
|
+
const weightedColumnSum = param.rowNode.allLeafChildren
|
429
|
+
.map((node) => node.data[weightedColumnId])
|
430
|
+
.reduce((a, b) => a + b) as number;
|
431
|
+
const weightedColumnMultiply = param.rowNode.allLeafChildren
|
432
|
+
.map((node) => node.data[weightedColumnId] * node.data[column])
|
433
|
+
.reduce((a, b) => a + b);
|
434
|
+
if (weightedColumnSum !== 0) {
|
435
|
+
onSaveGridColumnState();
|
436
|
+
return weightedColumnMultiply / weightedColumnSum;
|
437
|
+
} else {
|
438
|
+
applicationStore.notificationService.notifyError(
|
439
|
+
'The weighted column sum is 0',
|
440
|
+
);
|
441
|
+
}
|
442
|
+
} else {
|
443
|
+
applicationStore.notificationService.notifyError(
|
444
|
+
'The weighted column Id is not defined',
|
445
|
+
);
|
446
|
+
}
|
447
|
+
}
|
448
|
+
} catch (error) {
|
449
|
+
assertErrorThrown(error);
|
450
|
+
applicationStore.notificationService.notifyError(error);
|
451
|
+
}
|
452
|
+
return -1;
|
453
|
+
};
|
454
|
+
|
455
|
+
useEffect(() => {
|
456
|
+
if (aggFuncParams) {
|
457
|
+
aggFuncParams.columnApi.setColumnAggFunc(
|
458
|
+
aggFuncParams.colDef.field!,
|
459
|
+
QueryBuilderDataGridCustomAggregationFunction.WAVG,
|
460
|
+
);
|
461
|
+
}
|
462
|
+
}, [resultState.wavgAggregationState, aggFuncParams]);
|
463
|
+
|
339
464
|
return (
|
340
465
|
<div
|
341
466
|
data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_VALUES_TDS}
|
@@ -372,6 +497,10 @@ export const QueryBuilderTDSGridResult = observer(
|
|
372
497
|
suppressFieldDotNotation={true}
|
373
498
|
suppressContextMenu={false}
|
374
499
|
columnDefs={colDefs}
|
500
|
+
aggFuncs={{
|
501
|
+
wavg: weightedAverage,
|
502
|
+
WAVG: weightedAverageHelper,
|
503
|
+
}}
|
375
504
|
sideBar={['columns', 'filters']}
|
376
505
|
onColumnVisible={onSaveGridColumnState}
|
377
506
|
onColumnPinned={onSaveGridColumnState}
|
@@ -411,6 +540,54 @@ export const QueryBuilderTDSGridResult = observer(
|
|
411
540
|
}
|
412
541
|
/>
|
413
542
|
)}
|
543
|
+
{resultState.wavgAggregationState?.isApplyingWavg && (
|
544
|
+
<Dialog
|
545
|
+
open={resultState.wavgAggregationState.isApplyingWavg}
|
546
|
+
onClose={() =>
|
547
|
+
resultState.wavgAggregationState?.setIsApplyingWavg(false)
|
548
|
+
}
|
549
|
+
classes={{
|
550
|
+
root: 'editor-modal__root-container',
|
551
|
+
container: 'editor-modal__container',
|
552
|
+
paper: 'editor-modal__content',
|
553
|
+
}}
|
554
|
+
>
|
555
|
+
<Modal
|
556
|
+
darkMode={
|
557
|
+
!applicationStore.layoutService
|
558
|
+
.TEMPORARY__isLightColorThemeEnabled
|
559
|
+
}
|
560
|
+
className="query-editor__blocking-alert"
|
561
|
+
>
|
562
|
+
<ModalHeader title="Applying Weighted Average" />
|
563
|
+
<ModalBody>
|
564
|
+
<div className="query-builder__result__tds-grid__text">
|
565
|
+
choose a weighted column from dropdown
|
566
|
+
</div>
|
567
|
+
<CustomSelectorInput
|
568
|
+
options={weightedColumnOptions}
|
569
|
+
onChange={onWeightedColumnOptionChange}
|
570
|
+
value={selectedWeightedColumn}
|
571
|
+
placeholder={'Choose a weighted column'}
|
572
|
+
darkMode={
|
573
|
+
!applicationStore.layoutService
|
574
|
+
.TEMPORARY__isLightColorThemeEnabled
|
575
|
+
}
|
576
|
+
/>
|
577
|
+
</ModalBody>
|
578
|
+
<ModalFooter>
|
579
|
+
<ModalFooterButton
|
580
|
+
onClick={() => {
|
581
|
+
resultState.wavgAggregationState?.setIsApplyingWavg(
|
582
|
+
false,
|
583
|
+
);
|
584
|
+
}}
|
585
|
+
text="Apply"
|
586
|
+
/>
|
587
|
+
</ModalFooter>
|
588
|
+
</Modal>
|
589
|
+
</Dialog>
|
590
|
+
)}
|
414
591
|
</div>
|
415
592
|
</div>
|
416
593
|
);
|
@@ -48,6 +48,7 @@ import { QUERY_BUILDER_EVENT } from '../__lib__/QueryBuilderEvent.js';
|
|
48
48
|
import { ExecutionPlanState } from './execution-plan/ExecutionPlanState.js';
|
49
49
|
import type { DataGridColumnState } from '@finos/legend-lego/data-grid';
|
50
50
|
import { downloadStream } from '@finos/legend-application';
|
51
|
+
import { QueryBuilderDataGridCustomAggregationFunction } from '../components/result/tds/QueryBuilderTDSGridResult.js';
|
51
52
|
|
52
53
|
export const DEFAULT_LIMIT = 1000;
|
53
54
|
|
@@ -83,8 +84,37 @@ type QueryBuilderDataGridConfig = {
|
|
83
84
|
isPivotModeEnabled: boolean | undefined;
|
84
85
|
isLocalModeEnabled: boolean | undefined;
|
85
86
|
previewLimit?: number | undefined;
|
87
|
+
weightedColumnPairs?: Map<string, string> | undefined;
|
86
88
|
};
|
87
89
|
|
90
|
+
export class QueryBuilderResultWavgAggregationState {
|
91
|
+
isApplyingWavg = false;
|
92
|
+
weightedColumnIdPairs: Map<string, string>;
|
93
|
+
|
94
|
+
constructor() {
|
95
|
+
makeObservable(this, {
|
96
|
+
isApplyingWavg: observable,
|
97
|
+
weightedColumnIdPairs: observable,
|
98
|
+
setIsApplyingWavg: action,
|
99
|
+
addWeightedColumnIdPair: action,
|
100
|
+
removeWeightedColumnIdPair: action,
|
101
|
+
});
|
102
|
+
this.weightedColumnIdPairs = new Map<string, string>();
|
103
|
+
}
|
104
|
+
|
105
|
+
setIsApplyingWavg(val: boolean): void {
|
106
|
+
this.isApplyingWavg = val;
|
107
|
+
}
|
108
|
+
|
109
|
+
addWeightedColumnIdPair(col: string, weightedColumnId: string): void {
|
110
|
+
this.weightedColumnIdPairs.set(col, weightedColumnId);
|
111
|
+
}
|
112
|
+
|
113
|
+
removeWeightedColumnIdPair(col: string): void {
|
114
|
+
this.weightedColumnIdPairs.delete(col);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
88
118
|
export class QueryBuilderResultState {
|
89
119
|
readonly queryBuilderState: QueryBuilderState;
|
90
120
|
readonly executionPlanState: ExecutionPlanState;
|
@@ -105,6 +135,7 @@ export class QueryBuilderResultState {
|
|
105
135
|
isSelectingCells: boolean;
|
106
136
|
|
107
137
|
gridConfig: QueryBuilderDataGridConfig | undefined;
|
138
|
+
wavgAggregationState: QueryBuilderResultWavgAggregationState | undefined;
|
108
139
|
|
109
140
|
constructor(queryBuilderState: QueryBuilderState) {
|
110
141
|
makeObservable(this, {
|
@@ -120,7 +151,9 @@ export class QueryBuilderResultState {
|
|
120
151
|
isSelectingCells: observable,
|
121
152
|
isQueryUsageViewerOpened: observable,
|
122
153
|
gridConfig: observable,
|
154
|
+
wavgAggregationState: observable,
|
123
155
|
setGridConfig: action,
|
156
|
+
setWavgAggregationState: action,
|
124
157
|
setIsSelectingCells: action,
|
125
158
|
setIsRunningQuery: action,
|
126
159
|
setExecutionResult: action,
|
@@ -153,6 +186,12 @@ export class QueryBuilderResultState {
|
|
153
186
|
this.gridConfig = val;
|
154
187
|
}
|
155
188
|
|
189
|
+
setWavgAggregationState(
|
190
|
+
val: QueryBuilderResultWavgAggregationState | undefined,
|
191
|
+
): void {
|
192
|
+
this.wavgAggregationState = val;
|
193
|
+
}
|
194
|
+
|
156
195
|
setIsSelectingCells(val: boolean): void {
|
157
196
|
this.isSelectingCells = val;
|
158
197
|
}
|
@@ -199,11 +238,50 @@ export class QueryBuilderResultState {
|
|
199
238
|
}
|
200
239
|
}
|
201
240
|
|
241
|
+
processWeightedColumnPairsMap(
|
242
|
+
config: QueryGridConfig,
|
243
|
+
): Map<string, string> | undefined {
|
244
|
+
if (config.weightedColumnPairs) {
|
245
|
+
const wavgColumns = config.columns
|
246
|
+
.filter(
|
247
|
+
(col) =>
|
248
|
+
(col as DataGridColumnState).aggFunc ===
|
249
|
+
QueryBuilderDataGridCustomAggregationFunction.WAVG,
|
250
|
+
)
|
251
|
+
.map((col) => (col as DataGridColumnState).colId);
|
252
|
+
const weightedColumnPairsMap = new Map<string, string>();
|
253
|
+
config.weightedColumnPairs.forEach((wc) => {
|
254
|
+
if (wc[0] && wc[1]) {
|
255
|
+
weightedColumnPairsMap.set(wc[0], wc[1]);
|
256
|
+
}
|
257
|
+
});
|
258
|
+
for (const wavgCol of weightedColumnPairsMap.keys()) {
|
259
|
+
if (!wavgColumns.includes(wavgCol)) {
|
260
|
+
weightedColumnPairsMap.delete(wavgCol);
|
261
|
+
}
|
262
|
+
}
|
263
|
+
return weightedColumnPairsMap;
|
264
|
+
}
|
265
|
+
return undefined;
|
266
|
+
}
|
267
|
+
|
202
268
|
handlePreConfiguredGridConfig(config: QueryGridConfig): void {
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
269
|
+
let newConfig;
|
270
|
+
const weightedColumnPairsMap = this.processWeightedColumnPairsMap(config);
|
271
|
+
if (weightedColumnPairsMap) {
|
272
|
+
this.wavgAggregationState = new QueryBuilderResultWavgAggregationState();
|
273
|
+
this.wavgAggregationState.weightedColumnIdPairs = weightedColumnPairsMap;
|
274
|
+
newConfig = {
|
275
|
+
...config,
|
276
|
+
weightedColumnPairs: weightedColumnPairsMap,
|
277
|
+
columns: config.columns as DataGridColumnState[],
|
278
|
+
};
|
279
|
+
} else {
|
280
|
+
newConfig = {
|
281
|
+
...config,
|
282
|
+
columns: config.columns as DataGridColumnState[],
|
283
|
+
};
|
284
|
+
}
|
207
285
|
if (config.previewLimit) {
|
208
286
|
this.setPreviewLimit(config.previewLimit);
|
209
287
|
}
|
@@ -32,6 +32,7 @@ import {
|
|
32
32
|
ActionState,
|
33
33
|
hashArray,
|
34
34
|
assertTrue,
|
35
|
+
assertNonNullable,
|
35
36
|
} from '@finos/legend-shared';
|
36
37
|
import { QueryBuilderFilterState } from './filter/QueryBuilderFilterState.js';
|
37
38
|
import { QueryBuilderFetchStructureState } from './fetch-structure/QueryBuilderFetchStructureState.js';
|
@@ -54,6 +55,7 @@ import {
|
|
54
55
|
type ValueSpecification,
|
55
56
|
type Type,
|
56
57
|
type QueryGridConfig,
|
58
|
+
type QueryExecutionContext,
|
57
59
|
GRAPH_MANAGER_EVENT,
|
58
60
|
CompilationError,
|
59
61
|
extractSourceInformationCoordinates,
|
@@ -73,6 +75,7 @@ import {
|
|
73
75
|
InstanceValue,
|
74
76
|
Multiplicity,
|
75
77
|
RuntimePointer,
|
78
|
+
QueryExplicitExecutionContext,
|
76
79
|
} from '@finos/legend-graph';
|
77
80
|
import { buildLambdaFunction } from './QueryBuilderValueSpecificationBuilder.js';
|
78
81
|
import type {
|
@@ -287,6 +290,24 @@ export abstract class QueryBuilderState implements CommandRegistrar {
|
|
287
290
|
return this.allVariables.map((e) => e.name);
|
288
291
|
}
|
289
292
|
|
293
|
+
getQueryExecutionContext(): QueryExecutionContext {
|
294
|
+
const queryExeContext = new QueryExplicitExecutionContext();
|
295
|
+
const runtimeValue = guaranteeType(
|
296
|
+
this.executionContextState.runtimeValue,
|
297
|
+
RuntimePointer,
|
298
|
+
'Query runtime must be of type runtime pointer',
|
299
|
+
);
|
300
|
+
assertNonNullable(
|
301
|
+
this.executionContextState.mapping,
|
302
|
+
'Query required mapping to update',
|
303
|
+
);
|
304
|
+
queryExeContext.mapping = PackageableElementExplicitReference.create(
|
305
|
+
this.executionContextState.mapping,
|
306
|
+
);
|
307
|
+
queryExeContext.runtime = runtimeValue.packageableRuntime;
|
308
|
+
return queryExeContext;
|
309
|
+
}
|
310
|
+
|
290
311
|
/**
|
291
312
|
* Gets information about the current queryBuilderState.
|
292
313
|
* This information can be used as a part of analytics
|