@finos/legend-query-builder 4.11.5 → 4.11.6

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.
@@ -17,43 +17,42 @@
17
17
  import {
18
18
  MenuContent,
19
19
  MenuContentItem,
20
- ContextMenu,
21
- clsx,
22
20
  MenuContentDivider,
23
21
  } from '@finos/legend-art';
24
22
  import { observer } from 'mobx-react-lite';
25
23
  import { flowResult } from 'mobx';
26
24
  import {
25
+ type TDSExecutionResult,
27
26
  type Enumeration,
28
27
  InstanceValue,
29
- TDSExecutionResult,
30
28
  EnumValueInstanceValue,
31
29
  EnumValueExplicitReference,
32
- getTDSRowRankByColumnInAsc,
33
30
  PRIMITIVE_TYPE,
34
31
  type ExecutionResult,
35
32
  RelationalExecutionActivities,
36
33
  } from '@finos/legend-graph';
37
34
  import { format as formatSQL } from 'sql-formatter';
38
- import { useApplicationStore } from '@finos/legend-application';
35
+ import {
36
+ type ApplicationStore,
37
+ type LegendApplicationConfig,
38
+ type LegendApplicationPlugin,
39
+ type LegendApplicationPluginManager,
40
+ useApplicationStore,
41
+ } from '@finos/legend-application';
39
42
  import {
40
43
  assertErrorThrown,
41
44
  guaranteeNonNullable,
42
- isValidURL,
43
- isString,
44
- isNumber,
45
45
  filterByType,
46
46
  isBoolean,
47
- type PlainObject,
48
47
  } from '@finos/legend-shared';
49
48
  import { forwardRef } from 'react';
50
-
51
49
  import {
52
50
  QueryBuilderDerivationProjectionColumnState,
53
51
  QueryBuilderProjectionColumnState,
54
52
  } from '../../../stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
55
53
  import {
56
54
  type QueryBuilderPostFilterTreeNodeData,
55
+ type QueryBuilderPostFilterState,
57
56
  PostFilterConditionState,
58
57
  QueryBuilderPostFilterTreeConditionNodeData,
59
58
  PostFilterValueSpecConditionValueState,
@@ -67,28 +66,23 @@ import {
67
66
  QueryBuilderPostFilterOperator_NotIn,
68
67
  } from '../../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.js';
69
68
  import type { QueryBuilderPostFilterOperator } from '../../../stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.js';
70
- import { QueryBuilderTDSState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
69
+ import type { QueryBuilderTDSState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
71
70
  import {
72
71
  instanceValue_setValue,
73
72
  instanceValue_setValues,
74
73
  } from '../../../stores/shared/ValueSpecificationModifierHelper.js';
75
- import {
76
- isEnterpriseModeEnabled,
77
- type DataGridApi,
78
- type DataGridCellRendererParams,
79
- } from '@finos/legend-lego/data-grid';
74
+ import { type DataGridCellRendererParams } from '@finos/legend-lego/data-grid';
80
75
  import type {
81
- QueryBuilderTDSResultCellCoordinate,
82
76
  QueryBuilderResultState,
83
77
  QueryBuilderTDSResultCellData,
84
78
  QueryBuilderTDSRowDataType,
85
- QueryBuilderTDSResultCellDataType,
86
79
  } from '../../../stores/QueryBuilderResultState.js';
87
80
  import {
88
81
  QueryBuilderPostFilterOperator_IsEmpty,
89
82
  QueryBuilderPostFilterOperator_IsNotEmpty,
90
83
  } from '../../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js';
91
- import { DEFAULT_LOCALE } from '../../../graph-manager/QueryBuilderConst.js';
84
+ import { getTDSColumnState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSHelper.js';
85
+ import type { QueryBuilderTDSColumnState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSColumnState.js';
92
86
 
93
87
  export const tryToFormatSql = (sql: string): string => {
94
88
  try {
@@ -161,9 +155,9 @@ export const getAggregationTDSColumnCustomizations = (
161
155
 
162
156
  export const getRowDataFromExecutionResult = (
163
157
  executionResult: TDSExecutionResult,
164
- ): PlainObject<QueryBuilderTDSRowDataType>[] => {
158
+ ): QueryBuilderTDSRowDataType[] => {
165
159
  const rowData = executionResult.result.rows.map((_row, rowIdx) => {
166
- const row: PlainObject = {};
160
+ const row: QueryBuilderTDSRowDataType = {};
167
161
  const cols = executionResult.result.columns;
168
162
  _row.values.forEach((value, colIdx) => {
169
163
  // `ag-grid` shows `false` value as empty string so we have
@@ -183,684 +177,326 @@ export type IQueryRendererParamsWithGridType = DataGridCellRendererParams & {
183
177
  tdsExecutionResult: TDSExecutionResult;
184
178
  };
185
179
 
186
- export const QueryBuilderGridResultContextMenu = observer(
187
- forwardRef<
188
- HTMLDivElement,
189
- {
190
- data: QueryBuilderTDSResultCellData | null;
191
- tdsState: QueryBuilderTDSState;
192
- dataGridApi: DataGridApi<QueryBuilderTDSRowDataType>;
193
- }
194
- >(function QueryBuilderResultContextMenu(props, ref) {
195
- const { data, tdsState, dataGridApi } = props;
196
-
197
- const applicationStore = useApplicationStore();
198
- const postFilterEqualOperator = new QueryBuilderPostFilterOperator_Equal();
199
- const postFilterInOperator = new QueryBuilderPostFilterOperator_In();
200
- const postFilterEmptyOperator =
201
- new QueryBuilderPostFilterOperator_IsEmpty();
202
- const postFilterNotEmptyOperator =
203
- new QueryBuilderPostFilterOperator_IsNotEmpty();
204
-
205
- const postFilterNotEqualOperator =
206
- new QueryBuilderPostFilterOperator_NotEqual();
207
- const postFilterNotInOperator = new QueryBuilderPostFilterOperator_NotIn();
208
- const postFilterState = tdsState.postFilterState;
180
+ const postFilterEqualOperator = new QueryBuilderPostFilterOperator_Equal();
181
+ const postFilterInOperator = new QueryBuilderPostFilterOperator_In();
182
+ const postFilterEmptyOperator = new QueryBuilderPostFilterOperator_IsEmpty();
183
+ const postFilterNotEmptyOperator =
184
+ new QueryBuilderPostFilterOperator_IsNotEmpty();
185
+ const postFilterNotEqualOperator =
186
+ new QueryBuilderPostFilterOperator_NotEqual();
187
+ const postFilterNotInOperator = new QueryBuilderPostFilterOperator_NotIn();
188
+
189
+ const getExistingPostFilterNode = (
190
+ operators: QueryBuilderPostFilterOperator[],
191
+ projectionColumnName: string | undefined,
192
+ postFilterState: QueryBuilderPostFilterState,
193
+ tdsColState: QueryBuilderTDSColumnState | undefined,
194
+ ): QueryBuilderPostFilterTreeNodeData | undefined =>
195
+ Array.from(postFilterState.nodes.values())
196
+ .filter(filterByType(QueryBuilderPostFilterTreeConditionNodeData))
197
+ .filter(
198
+ (node) =>
199
+ node.condition.leftConditionValue instanceof
200
+ QueryBuilderProjectionColumnState,
201
+ )
202
+ .filter(
203
+ (node) =>
204
+ node.condition.leftConditionValue.columnName ===
205
+ (projectionColumnName ?? tdsColState?.columnName) &&
206
+ operators
207
+ .map((op) => op.getLabel())
208
+ .includes(node.condition.operator.getLabel()),
209
+ )[0];
210
+
211
+ const updateFilterConditionValue = (
212
+ conditionValue: InstanceValue,
213
+ _cellData: QueryBuilderTDSResultCellData,
214
+ tdsState: QueryBuilderTDSState,
215
+ ): void => {
216
+ if (_cellData.value) {
217
+ instanceValue_setValue(
218
+ conditionValue,
219
+ conditionValue instanceof EnumValueInstanceValue
220
+ ? EnumValueExplicitReference.create(
221
+ guaranteeNonNullable(
222
+ (
223
+ conditionValue.genericType?.ownerReference.value as Enumeration
224
+ ).values.filter((v) => v.name === _cellData.value)[0],
225
+ ),
226
+ )
227
+ : _cellData.value,
228
+ 0,
229
+ tdsState.queryBuilderState.observerContext,
230
+ );
231
+ }
232
+ };
209
233
 
210
- const projectionColumnState = tdsState.projectionColumns
211
- .filter((c) => c.columnName === data?.columnName)
212
- .concat(
213
- tdsState.aggregationState.columns
214
- .filter((c) => c.columnName === data?.columnName)
215
- .map((ag) => ag.projectionColumnState),
216
- )[0];
217
- const getExistingPostFilterNode = (
218
- operators: QueryBuilderPostFilterOperator[],
219
- projectionColumnName: string | undefined,
220
- ): QueryBuilderPostFilterTreeNodeData | undefined =>
221
- Array.from(postFilterState.nodes.values())
222
- .filter(
223
- (v) =>
224
- v instanceof QueryBuilderPostFilterTreeConditionNodeData &&
225
- v.condition.leftConditionValue instanceof
226
- QueryBuilderProjectionColumnState,
227
- )
228
- .filter(
229
- (n) =>
230
- (n as QueryBuilderPostFilterTreeConditionNodeData).condition
231
- .leftConditionValue.columnName ===
232
- (projectionColumnName ?? projectionColumnState?.columnName) &&
233
- operators
234
- .map((op) => op.getLabel())
235
- .includes(
236
- (
237
- n as QueryBuilderPostFilterTreeConditionNodeData
238
- ).condition.operator.getLabel(),
239
- ),
240
- )[0];
234
+ const generateNewPostFilterConditionNodeData = async (
235
+ applicationStore: ApplicationStore<
236
+ LegendApplicationConfig,
237
+ LegendApplicationPluginManager<LegendApplicationPlugin>
238
+ >,
239
+ operator: QueryBuilderPostFilterOperator,
240
+ _cellData: QueryBuilderTDSResultCellData,
241
+ tdsState: QueryBuilderTDSState,
242
+ tdsColState: QueryBuilderTDSColumnState | undefined,
243
+ ): Promise<void> => {
244
+ let postFilterConditionState: PostFilterConditionState;
245
+ try {
246
+ const possibleProjectionColumnState = _cellData.columnName
247
+ ? tdsState.projectionColumns
248
+ .filter((c) => c.columnName === _cellData.columnName)
249
+ .concat(
250
+ tdsState.aggregationState.columns
251
+ .filter((c) => c.columnName === _cellData.columnName)
252
+ .map((ag) => ag.projectionColumnState),
253
+ )[0]
254
+ : tdsColState;
255
+
256
+ if (possibleProjectionColumnState) {
257
+ postFilterConditionState = new PostFilterConditionState(
258
+ tdsState.postFilterState,
259
+ possibleProjectionColumnState,
260
+ operator,
261
+ );
241
262
 
242
- const updateFilterConditionValue = (
243
- conditionValue: InstanceValue,
244
- cellData: QueryBuilderTDSResultCellData,
245
- ): void => {
246
- if (cellData.value) {
247
- instanceValue_setValue(
248
- conditionValue,
249
- conditionValue instanceof EnumValueInstanceValue
250
- ? EnumValueExplicitReference.create(
251
- guaranteeNonNullable(
252
- (
253
- conditionValue.genericType?.ownerReference
254
- .value as Enumeration
255
- ).values.filter((v) => v.name === cellData.value)[0],
256
- ),
257
- )
258
- : cellData.value,
259
- 0,
260
- tdsState.queryBuilderState.observerContext,
261
- );
263
+ if (tdsColState instanceof QueryBuilderDerivationProjectionColumnState) {
264
+ await flowResult(tdsColState.fetchDerivationLambdaReturnType());
262
265
  }
263
- };
264
-
265
- const generateNewPostFilterConditionNodeData = async (
266
- operator: QueryBuilderPostFilterOperator,
267
- cellData: QueryBuilderTDSResultCellData,
268
- ): Promise<void> => {
269
- let postFilterConditionState: PostFilterConditionState;
270
- try {
271
- const possibleProjectionColumnState = cellData.columnName
272
- ? tdsState.projectionColumns
273
- .filter((c) => c.columnName === cellData.columnName)
274
- .concat(
275
- tdsState.aggregationState.columns
276
- .filter((c) => c.columnName === cellData.columnName)
277
- .map((ag) => ag.projectionColumnState),
278
- )[0]
279
- : projectionColumnState;
280
-
281
- if (possibleProjectionColumnState) {
282
- postFilterConditionState = new PostFilterConditionState(
283
- postFilterState,
284
- possibleProjectionColumnState,
285
- operator,
286
- );
287
-
288
- if (
289
- projectionColumnState instanceof
290
- QueryBuilderDerivationProjectionColumnState
291
- ) {
292
- await flowResult(
293
- projectionColumnState.fetchDerivationLambdaReturnType(),
294
- );
295
- }
296
266
 
297
- const defaultFilterConditionValue =
298
- postFilterConditionState.operator.getDefaultFilterConditionValue(
299
- postFilterConditionState,
300
- );
267
+ const defaultFilterConditionValue =
268
+ postFilterConditionState.operator.getDefaultFilterConditionValue(
269
+ postFilterConditionState,
270
+ );
301
271
 
302
- postFilterConditionState.buildFromValueSpec(
303
- defaultFilterConditionValue,
304
- );
305
- updateFilterConditionValue(
306
- defaultFilterConditionValue as InstanceValue,
307
- cellData,
308
- );
309
- postFilterState.addNodeFromNode(
310
- new QueryBuilderPostFilterTreeConditionNodeData(
311
- undefined,
312
- postFilterConditionState,
313
- ),
314
- undefined,
315
- );
316
- }
317
- } catch (error) {
318
- assertErrorThrown(error);
319
- applicationStore.notificationService.notifyWarning(error.message);
320
- return;
321
- }
322
- };
272
+ postFilterConditionState.buildFromValueSpec(defaultFilterConditionValue);
273
+ updateFilterConditionValue(
274
+ defaultFilterConditionValue as InstanceValue,
275
+ _cellData,
276
+ tdsState,
277
+ );
278
+ tdsState.postFilterState.addNodeFromNode(
279
+ new QueryBuilderPostFilterTreeConditionNodeData(
280
+ undefined,
281
+ postFilterConditionState,
282
+ ),
283
+ undefined,
284
+ );
285
+ }
286
+ } catch (error) {
287
+ assertErrorThrown(error);
288
+ applicationStore.notificationService.notifyWarning(error.message);
289
+ return;
290
+ }
291
+ };
323
292
 
324
- const updateExistingPostFilterConditionNodeData = (
325
- existingPostFilterNode: QueryBuilderPostFilterTreeNodeData,
326
- isFilterBy: boolean,
327
- cellData: QueryBuilderTDSResultCellData,
328
- operator: QueryBuilderPostFilterOperator,
329
- ): void => {
330
- if (
331
- operator === postFilterEmptyOperator ||
332
- operator === postFilterNotEmptyOperator
333
- ) {
334
- const conditionState = (
335
- existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
336
- ).condition;
337
- if (conditionState.operator.getLabel() !== operator.getLabel()) {
338
- conditionState.changeOperator(
339
- isFilterBy ? postFilterEmptyOperator : postFilterNotEmptyOperator,
293
+ const updateExistingPostFilterConditionNodeData = (
294
+ existingPostFilterNode: QueryBuilderPostFilterTreeNodeData,
295
+ isFilterBy: boolean,
296
+ _cellData: QueryBuilderTDSResultCellData,
297
+ operator: QueryBuilderPostFilterOperator,
298
+ data: QueryBuilderTDSResultCellData | null,
299
+ tdsState: QueryBuilderTDSState,
300
+ ): void => {
301
+ if (
302
+ operator === postFilterEmptyOperator ||
303
+ operator === postFilterNotEmptyOperator
304
+ ) {
305
+ const conditionState = (
306
+ existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
307
+ ).condition;
308
+ if (conditionState.operator.getLabel() !== operator.getLabel()) {
309
+ conditionState.changeOperator(
310
+ isFilterBy ? postFilterEmptyOperator : postFilterNotEmptyOperator,
311
+ );
312
+ }
313
+ return;
314
+ }
315
+ const conditionState = (
316
+ existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
317
+ ).condition;
318
+
319
+ const rightSide = conditionState.rightConditionValue;
320
+ if (rightSide instanceof PostFilterValueSpecConditionValueState) {
321
+ if (conditionState.operator.getLabel() === operator.getLabel()) {
322
+ const doesValueAlreadyExist =
323
+ rightSide.value instanceof InstanceValue &&
324
+ (rightSide.value instanceof EnumValueInstanceValue
325
+ ? rightSide.value.values.map((ef) => ef.value.name)
326
+ : rightSide.value.values
327
+ ).includes(_cellData.value);
328
+
329
+ if (!doesValueAlreadyExist) {
330
+ const currentValueSpecificaton = rightSide.value;
331
+ const newValueSpecification =
332
+ conditionState.operator.getDefaultFilterConditionValue(
333
+ conditionState,
340
334
  );
341
- }
342
- return;
343
- }
344
- const conditionState = (
345
- existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
346
- ).condition;
347
-
348
- const rightSide = conditionState.rightConditionValue;
349
- if (rightSide instanceof PostFilterValueSpecConditionValueState) {
350
- if (conditionState.operator.getLabel() === operator.getLabel()) {
351
- const doesValueAlreadyExist =
352
- rightSide.value instanceof InstanceValue &&
353
- (rightSide.value instanceof EnumValueInstanceValue
354
- ? rightSide.value.values.map((ef) => ef.value.name)
355
- : rightSide.value.values
356
- ).includes(cellData.value);
357
-
358
- if (!doesValueAlreadyExist) {
359
- const currentValueSpecificaton = rightSide.value;
360
- const newValueSpecification =
361
- conditionState.operator.getDefaultFilterConditionValue(
362
- conditionState,
363
- );
364
- updateFilterConditionValue(
365
- newValueSpecification as InstanceValue,
366
- cellData,
367
- );
368
- conditionState.changeOperator(
369
- isFilterBy ? postFilterInOperator : postFilterNotInOperator,
370
- );
371
- instanceValue_setValues(
372
- rightSide.value as InstanceValue,
373
- [currentValueSpecificaton, newValueSpecification],
374
- tdsState.queryBuilderState.observerContext,
375
- );
376
- }
377
- } else {
378
- const doesValueAlreadyExist =
379
- rightSide.value instanceof InstanceValue &&
380
- rightSide.value.values
381
- .filter((v) => v instanceof InstanceValue)
382
- .map((v) =>
383
- v instanceof EnumValueInstanceValue
384
- ? v.values.map((ef) => ef.value.name)
385
- : (v as InstanceValue).values,
386
- )
387
- .flat()
388
- .includes(cellData.value ?? data?.value);
389
-
390
- if (!doesValueAlreadyExist) {
391
- const newValueSpecification = (
392
- isFilterBy ? postFilterEqualOperator : postFilterNotEqualOperator
393
- ).getDefaultFilterConditionValue(conditionState);
394
- updateFilterConditionValue(
395
- newValueSpecification as InstanceValue,
396
- cellData,
397
- );
398
- instanceValue_setValues(
399
- rightSide.value as InstanceValue,
400
- [
401
- ...(rightSide.value as InstanceValue).values,
402
- newValueSpecification,
403
- ],
404
- tdsState.queryBuilderState.observerContext,
405
- );
406
- }
407
- }
335
+ updateFilterConditionValue(
336
+ newValueSpecification as InstanceValue,
337
+ _cellData,
338
+ tdsState,
339
+ );
340
+ conditionState.changeOperator(
341
+ isFilterBy ? postFilterInOperator : postFilterNotInOperator,
342
+ );
343
+ instanceValue_setValues(
344
+ rightSide.value as InstanceValue,
345
+ [currentValueSpecificaton, newValueSpecification],
346
+ tdsState.queryBuilderState.observerContext,
347
+ );
408
348
  }
409
- };
410
-
411
- const getFilterOperator = (
412
- isFilterBy: boolean,
413
- cellData: QueryBuilderTDSResultCellData,
414
- ): QueryBuilderPostFilterOperator => {
415
- if (isFilterBy === true) {
416
- if (cellData.value === null) {
417
- return postFilterEmptyOperator;
418
- } else {
419
- return postFilterEqualOperator;
420
- }
421
- } else {
422
- if (cellData.value === null) {
423
- return postFilterNotEmptyOperator;
424
- } else {
425
- return postFilterNotEqualOperator;
426
- }
349
+ } else {
350
+ const doesValueAlreadyExist =
351
+ rightSide.value instanceof InstanceValue &&
352
+ rightSide.value.values
353
+ .filter((v) => v instanceof InstanceValue)
354
+ .map((v) =>
355
+ v instanceof EnumValueInstanceValue
356
+ ? v.values.map((ef) => ef.value.name)
357
+ : (v as InstanceValue).values,
358
+ )
359
+ .flat()
360
+ .includes(_cellData.value ?? data?.value);
361
+
362
+ if (!doesValueAlreadyExist) {
363
+ const newValueSpecification = (
364
+ isFilterBy ? postFilterEqualOperator : postFilterNotEqualOperator
365
+ ).getDefaultFilterConditionValue(conditionState);
366
+ updateFilterConditionValue(
367
+ newValueSpecification as InstanceValue,
368
+ _cellData,
369
+ tdsState,
370
+ );
371
+ instanceValue_setValues(
372
+ rightSide.value as InstanceValue,
373
+ [...(rightSide.value as InstanceValue).values, newValueSpecification],
374
+ tdsState.queryBuilderState.observerContext,
375
+ );
427
376
  }
428
- };
429
-
430
- const filterByOrOutValue = (
431
- isFilterBy: boolean,
432
- cellData: QueryBuilderTDSResultCellData,
433
- ): void => {
434
- tdsState.setShowPostFilterPanel(true);
435
-
436
- const operator = getFilterOperator(isFilterBy, cellData);
437
-
438
- const existingPostFilterNode = getExistingPostFilterNode(
439
- cellData.value === null
440
- ? [postFilterEmptyOperator, postFilterNotEmptyOperator]
441
- : isFilterBy
442
- ? [postFilterEqualOperator, postFilterInOperator]
443
- : [postFilterNotEqualOperator, postFilterNotInOperator],
444
- cellData.columnName,
445
- );
377
+ }
378
+ }
379
+ };
446
380
 
447
- existingPostFilterNode === undefined
448
- ? generateNewPostFilterConditionNodeData(operator, cellData).catch(
449
- applicationStore.alertUnhandledError,
450
- )
451
- : updateExistingPostFilterConditionNodeData(
452
- existingPostFilterNode,
453
- isFilterBy,
454
- cellData,
455
- operator,
456
- );
457
- };
381
+ const getFilterOperator = (
382
+ isFilterBy: boolean,
383
+ _cellData: QueryBuilderTDSResultCellData,
384
+ ): QueryBuilderPostFilterOperator => {
385
+ if (isFilterBy) {
386
+ if (_cellData.value === null) {
387
+ return postFilterEmptyOperator;
388
+ } else {
389
+ return postFilterEqualOperator;
390
+ }
391
+ } else {
392
+ if (_cellData.value === null) {
393
+ return postFilterNotEmptyOperator;
394
+ } else {
395
+ return postFilterNotEqualOperator;
396
+ }
397
+ }
398
+ };
458
399
 
459
- const filterByOrOutValues = (isFilterBy: boolean): void => {
460
- tdsState.queryBuilderState.resultState.selectedCells.forEach(
461
- (cellData) => {
462
- filterByOrOutValue(isFilterBy, cellData);
463
- },
400
+ const filterByOrOutValue = (
401
+ applicationStore: ApplicationStore<
402
+ LegendApplicationConfig,
403
+ LegendApplicationPluginManager<LegendApplicationPlugin>
404
+ >,
405
+ isFilterBy: boolean,
406
+ _cellData: QueryBuilderTDSResultCellData,
407
+ data: QueryBuilderTDSResultCellData | null,
408
+ tdsState: QueryBuilderTDSState,
409
+ ): void => {
410
+ tdsState.setShowPostFilterPanel(true);
411
+ const operator = getFilterOperator(isFilterBy, _cellData);
412
+ const tdsColState = data?.columnName
413
+ ? getTDSColumnState(tdsState, data.columnName)
414
+ : undefined;
415
+ const existingPostFilterNode = getExistingPostFilterNode(
416
+ _cellData.value === null
417
+ ? [postFilterEmptyOperator, postFilterNotEmptyOperator]
418
+ : isFilterBy
419
+ ? [postFilterEqualOperator, postFilterInOperator]
420
+ : [postFilterNotEqualOperator, postFilterNotInOperator],
421
+ _cellData.columnName,
422
+ tdsState.postFilterState,
423
+ tdsColState,
424
+ );
425
+ existingPostFilterNode === undefined
426
+ ? generateNewPostFilterConditionNodeData(
427
+ applicationStore,
428
+ operator,
429
+ _cellData,
430
+ tdsState,
431
+ tdsColState,
432
+ ).catch(applicationStore.alertUnhandledError)
433
+ : updateExistingPostFilterConditionNodeData(
434
+ existingPostFilterNode,
435
+ isFilterBy,
436
+ _cellData,
437
+ operator,
438
+ data,
439
+ tdsState,
464
440
  );
465
- };
441
+ };
466
442
 
467
- const handleCopyCellValue = isEnterpriseModeEnabled
468
- ? (): void => {
469
- dataGridApi.copySelectedRangeToClipboard();
470
- }
471
- : applicationStore.guardUnhandledError(() =>
472
- applicationStore.clipboardService.copyTextToClipboard(
473
- tdsState.queryBuilderState.resultState.selectedCells
474
- .map((cellData) => cellData.value)
475
- .join(','),
476
- ),
477
- );
443
+ export const filterByOrOutValues = (
444
+ applicationStore: ApplicationStore<
445
+ LegendApplicationConfig,
446
+ LegendApplicationPluginManager<LegendApplicationPlugin>
447
+ >,
448
+ data: QueryBuilderTDSResultCellData | null,
449
+ isFilterBy: boolean,
450
+ tdsState: QueryBuilderTDSState,
451
+ ): void => {
452
+ tdsState.queryBuilderState.resultState.selectedCells.forEach((_cellData) => {
453
+ filterByOrOutValue(applicationStore, isFilterBy, _cellData, data, tdsState);
454
+ });
455
+ };
478
456
 
479
- const findRowFromRowIndex = (rowIndex: number): string => {
480
- if (
481
- !tdsState.queryBuilderState.resultState.executionResult ||
482
- !(
483
- tdsState.queryBuilderState.resultState.executionResult instanceof
484
- TDSExecutionResult
485
- )
486
- ) {
487
- return '';
488
- }
489
- // try to get the entire row value separated by comma
490
- // rowData is in format of {columnName: value, columnName1: value, ...., rowNumber:value}
491
- const valueArr: QueryBuilderTDSResultCellDataType[] = [];
492
- Object.entries(
493
- dataGridApi.getRenderedNodes().find((n) => n.rowIndex === rowIndex)
494
- ?.data as QueryBuilderTDSRowDataType,
495
- ).forEach((entry) => {
496
- if (entry[0] !== 'rowNumber') {
497
- valueArr.push(entry[1] as QueryBuilderTDSResultCellDataType);
498
- }
499
- });
500
- return valueArr.join(',');
501
- };
457
+ export const QueryBuilderGridResultContextMenu = observer(
458
+ forwardRef<
459
+ HTMLDivElement,
460
+ {
461
+ data: QueryBuilderTDSResultCellData | null;
462
+ tdsState: QueryBuilderTDSState;
463
+ copyCellValueFunc: () => void;
464
+ copyCellRowValueFunc: () => void;
465
+ }
466
+ >(function QueryBuilderResultContextMenu(props, ref) {
467
+ const { data, tdsState, copyCellValueFunc, copyCellRowValueFunc } = props;
468
+ const applicationStore = useApplicationStore();
502
469
 
503
- const handleCopyRowValue = isEnterpriseModeEnabled
504
- ? (): void => {
505
- dataGridApi.copySelectedRowsToClipboard();
506
- }
507
- : applicationStore.guardUnhandledError(() =>
508
- applicationStore.clipboardService.copyTextToClipboard(
509
- findRowFromRowIndex(
510
- tdsState.queryBuilderState.resultState.selectedCells[0]
511
- ?.coordinates.rowIndex ?? 0,
512
- ),
513
- ),
514
- );
470
+ const tdsColState = data?.columnName
471
+ ? getTDSColumnState(tdsState, data.columnName)
472
+ : undefined;
515
473
 
516
474
  return (
517
475
  <MenuContent ref={ref}>
518
476
  <MenuContentItem
519
- disabled={!projectionColumnState}
477
+ disabled={!tdsColState}
520
478
  onClick={(): void => {
521
- filterByOrOutValues(true);
479
+ filterByOrOutValues(applicationStore, data, true, tdsState);
522
480
  }}
523
481
  >
524
482
  Filter By
525
483
  </MenuContentItem>
526
484
  <MenuContentItem
527
- disabled={!projectionColumnState}
485
+ disabled={!tdsColState}
528
486
  onClick={(): void => {
529
- filterByOrOutValues(false);
487
+ filterByOrOutValues(applicationStore, data, false, tdsState);
530
488
  }}
531
489
  >
532
490
  Filter Out
533
491
  </MenuContentItem>
534
492
  <MenuContentDivider />
535
- <MenuContentItem onClick={handleCopyCellValue}>
493
+ <MenuContentItem onClick={copyCellValueFunc}>
536
494
  Copy Cell Value
537
495
  </MenuContentItem>
538
- <MenuContentItem onClick={handleCopyRowValue}>
496
+ <MenuContentItem onClick={copyCellRowValueFunc}>
539
497
  Copy Row Value
540
498
  </MenuContentItem>
541
499
  </MenuContent>
542
500
  );
543
501
  }),
544
502
  );
545
-
546
- export const QueryResultEnterpriseCellRenderer = observer(
547
- (params: IQueryRendererParamsWithGridType) => {
548
- const resultState = params.resultState;
549
- const fetchStructureImplementation =
550
- resultState.queryBuilderState.fetchStructureState.implementation;
551
- const cellValue = params.value as QueryBuilderTDSResultCellDataType;
552
- const formattedCellValue = (): QueryBuilderTDSResultCellDataType => {
553
- if (isNumber(cellValue)) {
554
- return Intl.NumberFormat(DEFAULT_LOCALE, {
555
- maximumFractionDigits: 4,
556
- }).format(Number(cellValue));
557
- }
558
- return cellValue;
559
- };
560
- const cellValueUrlLink =
561
- isString(cellValue) && isValidURL(cellValue) ? cellValue : undefined;
562
-
563
- const mouseDown: React.MouseEventHandler = (event) => {
564
- event.preventDefault();
565
- if (event.button === 0 || event.button === 2) {
566
- resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
567
- }
568
- };
569
- const mouseUp: React.MouseEventHandler = (event) => {
570
- resultState.setIsSelectingCells(false);
571
- };
572
- const mouseOver: React.MouseEventHandler = (event) => {
573
- resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
574
- };
575
-
576
- return (
577
- <ContextMenu
578
- content={
579
- // NOTE: we only support this functionality for grid result with a projection fetch-structure
580
- fetchStructureImplementation instanceof QueryBuilderTDSState ? (
581
- <QueryBuilderGridResultContextMenu
582
- data={resultState.mousedOverCell}
583
- tdsState={fetchStructureImplementation}
584
- dataGridApi={params.api}
585
- />
586
- ) : null
587
- }
588
- disabled={
589
- !(
590
- resultState.queryBuilderState.fetchStructureState
591
- .implementation instanceof QueryBuilderTDSState
592
- ) ||
593
- !resultState.queryBuilderState.isQuerySupported ||
594
- !resultState.mousedOverCell
595
- }
596
- menuProps={{ elevation: 7 }}
597
- className={clsx('ag-theme-balham-dark query-builder__result__tds-grid')}
598
- >
599
- <div
600
- className={clsx('query-builder__result__values__table__cell')}
601
- onMouseDown={(event) => mouseDown(event)}
602
- onMouseUp={(event) => mouseUp(event)}
603
- onMouseOver={(event) => mouseOver(event)}
604
- >
605
- {cellValueUrlLink ? (
606
- <a href={cellValueUrlLink} target="_blank" rel="noreferrer">
607
- {cellValueUrlLink}
608
- </a>
609
- ) : (
610
- <span>{formattedCellValue()}</span>
611
- )}
612
- </div>
613
- </ContextMenu>
614
- );
615
- },
616
- );
617
-
618
- export const QueryResultCellRenderer = observer(
619
- (params: IQueryRendererParamsWithGridType) => {
620
- const resultState = params.resultState;
621
- const tdsExecutionResult = params.tdsExecutionResult;
622
- const fetchStructureImplementation =
623
- resultState.queryBuilderState.fetchStructureState.implementation;
624
- const cellValue = params.value as QueryBuilderTDSResultCellDataType;
625
- const formattedCellValue = (): QueryBuilderTDSResultCellDataType => {
626
- if (isNumber(cellValue)) {
627
- return Intl.NumberFormat(DEFAULT_LOCALE, {
628
- maximumFractionDigits: 4,
629
- }).format(Number(cellValue));
630
- }
631
- return cellValue;
632
- };
633
- const cellValueUrlLink =
634
- isString(cellValue) && isValidURL(cellValue) ? cellValue : undefined;
635
- const columnName = params.column?.getColId() ?? '';
636
- const findCoordinatesFromResultValue = (
637
- colId: string,
638
- rowNumber: number,
639
- ): QueryBuilderTDSResultCellCoordinate => {
640
- const colIndex = tdsExecutionResult.result.columns.findIndex(
641
- (col) => col === colId,
642
- );
643
- return { rowIndex: rowNumber, colIndex: colIndex };
644
- };
645
-
646
- const currentCellCoordinates = findCoordinatesFromResultValue(
647
- columnName,
648
- params.rowIndex,
649
- );
650
- const cellInFilteredResults = resultState.selectedCells.some(
651
- (result) =>
652
- result.coordinates.colIndex === currentCellCoordinates.colIndex &&
653
- result.coordinates.rowIndex === currentCellCoordinates.rowIndex,
654
- );
655
-
656
- const findColumnFromCoordinates = (
657
- colIndex: number,
658
- ): QueryBuilderTDSResultCellDataType => {
659
- if (
660
- !resultState.executionResult ||
661
- !(resultState.executionResult instanceof TDSExecutionResult)
662
- ) {
663
- return undefined;
664
- }
665
- return resultState.executionResult.result.columns[colIndex];
666
- };
667
-
668
- const findResultValueFromCoordinates = (
669
- resultCoordinate: [number, number],
670
- ): QueryBuilderTDSResultCellDataType => {
671
- const rowIndex = resultCoordinate[0];
672
- const colIndex = resultCoordinate[1];
673
-
674
- if (
675
- !resultState.executionResult ||
676
- !(resultState.executionResult instanceof TDSExecutionResult)
677
- ) {
678
- return undefined;
679
- }
680
- if (params.columnApi.getColumnState()[colIndex]?.sort === 'asc') {
681
- resultState.executionResult.result.rows.sort((a, b) =>
682
- getTDSRowRankByColumnInAsc(a, b, colIndex),
683
- );
684
- } else if (params.columnApi.getColumnState()[colIndex]?.sort === 'desc') {
685
- resultState.executionResult.result.rows.sort((a, b) =>
686
- getTDSRowRankByColumnInAsc(b, a, colIndex),
687
- );
688
- }
689
- return resultState.executionResult.result.rows[rowIndex]?.values[
690
- colIndex
691
- ];
692
- };
693
-
694
- const isCoordinatesSelected = (
695
- resultCoordinate: QueryBuilderTDSResultCellCoordinate,
696
- ): boolean =>
697
- resultState.selectedCells.some(
698
- (cell) =>
699
- cell.coordinates.rowIndex === resultCoordinate.rowIndex &&
700
- cell.coordinates.colIndex === resultCoordinate.colIndex,
701
- );
702
-
703
- const mouseDown: React.MouseEventHandler = (event) => {
704
- event.preventDefault();
705
-
706
- if (event.shiftKey) {
707
- const coordinates = findCoordinatesFromResultValue(
708
- columnName,
709
- params.rowIndex,
710
- );
711
- const actualValue = findResultValueFromCoordinates([
712
- coordinates.rowIndex,
713
- coordinates.colIndex,
714
- ]);
715
- resultState.addSelectedCell({
716
- value: actualValue,
717
- columnName: columnName,
718
- coordinates: coordinates,
719
- });
720
- return;
721
- }
722
-
723
- if (event.button === 0) {
724
- resultState.setIsSelectingCells(true);
725
- resultState.setSelectedCells([]);
726
- const coordinates = findCoordinatesFromResultValue(
727
- columnName,
728
- params.rowIndex,
729
- );
730
- const actualValue = findResultValueFromCoordinates([
731
- coordinates.rowIndex,
732
- coordinates.colIndex,
733
- ]);
734
- resultState.setSelectedCells([
735
- {
736
- value: actualValue,
737
- columnName: columnName,
738
- coordinates: coordinates,
739
- },
740
- ]);
741
- resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
742
- }
743
-
744
- if (event.button === 2) {
745
- const coordinates = findCoordinatesFromResultValue(
746
- columnName,
747
- params.rowIndex,
748
- );
749
- const isInSelected = isCoordinatesSelected(coordinates);
750
- if (!isInSelected) {
751
- const actualValue = findResultValueFromCoordinates([
752
- coordinates.rowIndex,
753
- coordinates.colIndex,
754
- ]);
755
- resultState.setSelectedCells([
756
- {
757
- value: actualValue,
758
- columnName: columnName,
759
- coordinates: coordinates,
760
- },
761
- ]);
762
- resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
763
- }
764
- }
765
- };
766
-
767
- const mouseUp: React.MouseEventHandler = (event) => {
768
- resultState.setIsSelectingCells(false);
769
- };
770
-
771
- const mouseOver: React.MouseEventHandler = (event) => {
772
- if (resultState.isSelectingCells) {
773
- if (resultState.selectedCells.length < 1) {
774
- return;
775
- }
776
- const results = resultState.selectedCells[0];
777
- if (!results) {
778
- return;
779
- }
780
-
781
- const firstCorner = results.coordinates;
782
- const secondCorner = findCoordinatesFromResultValue(
783
- columnName,
784
- params.rowIndex,
785
- );
786
-
787
- resultState.setSelectedCells([results]);
788
-
789
- const minRow = Math.min(firstCorner.rowIndex, secondCorner.rowIndex);
790
- const minCol = Math.min(firstCorner.colIndex, secondCorner.colIndex);
791
- const maxRow = Math.max(firstCorner.rowIndex, secondCorner.rowIndex);
792
- const maxCol = Math.max(firstCorner.colIndex, secondCorner.colIndex);
793
-
794
- for (let x = minRow; x <= maxRow; x++) {
795
- for (let y = minCol; y <= maxCol; y++) {
796
- const actualValue = findResultValueFromCoordinates([x, y]);
797
-
798
- const valueAndColumnId = {
799
- value: actualValue,
800
- columnName: findColumnFromCoordinates(y),
801
- coordinates: {
802
- rowIndex: x,
803
- colIndex: y,
804
- },
805
- } as QueryBuilderTDSResultCellData;
806
-
807
- if (
808
- !resultState.selectedCells.find(
809
- (result) =>
810
- result.coordinates.colIndex === y &&
811
- result.coordinates.rowIndex === x,
812
- )
813
- ) {
814
- resultState.addSelectedCell(valueAndColumnId);
815
- }
816
- }
817
- }
818
- }
819
-
820
- resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
821
- };
822
-
823
- return (
824
- <ContextMenu
825
- content={
826
- // NOTE: we only support this functionality for grid result with a projection fetch-structure
827
- fetchStructureImplementation instanceof QueryBuilderTDSState ? (
828
- <QueryBuilderGridResultContextMenu
829
- data={resultState.mousedOverCell}
830
- tdsState={fetchStructureImplementation}
831
- dataGridApi={params.api}
832
- />
833
- ) : null
834
- }
835
- disabled={
836
- !(
837
- resultState.queryBuilderState.fetchStructureState
838
- .implementation instanceof QueryBuilderTDSState
839
- ) ||
840
- !resultState.queryBuilderState.isQuerySupported ||
841
- !resultState.mousedOverCell
842
- }
843
- menuProps={{ elevation: 7 }}
844
- className={clsx('ag-theme-balham-dark query-builder__result__tds-grid')}
845
- >
846
- <div
847
- className={clsx('query-builder__result__values__table__cell', {
848
- 'query-builder__result__values__table__cell--active':
849
- cellInFilteredResults,
850
- })}
851
- onMouseDown={(event) => mouseDown(event)}
852
- onMouseUp={(event) => mouseUp(event)}
853
- onMouseOver={(event) => mouseOver(event)}
854
- >
855
- {cellValueUrlLink ? (
856
- <a href={cellValueUrlLink} target="_blank" rel="noreferrer">
857
- {cellValueUrlLink}
858
- </a>
859
- ) : (
860
- <span>{formattedCellValue()}</span>
861
- )}
862
- </div>
863
- </ContextMenu>
864
- );
865
- },
866
- );