@finos/legend-query-builder 4.11.5 → 4.11.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- );