@finos/legend-query-builder 4.14.26 → 4.14.28

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.
@@ -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 type {
38
- QueryBuilderResultState,
39
- QueryBuilderTDSResultCellData,
40
- QueryBuilderTDSResultCellDataType,
41
- QueryBuilderTDSRowDataType,
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 { isNumber, isString, isValidURL } from '@finos/legend-shared';
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
- const newConfig = {
204
- ...config,
205
- columns: config.columns as DataGridColumnState[],
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