@finos/legend-query-builder 4.0.8 → 4.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. package/lib/__lib__/QueryBuilderColorTheme.js +1 -1
  2. package/lib/__lib__/QueryBuilderColorTheme.js.map +1 -1
  3. package/lib/__lib__/QueryBuilderDocumentation.js +1 -1
  4. package/lib/__lib__/QueryBuilderDocumentation.js.map +1 -1
  5. package/lib/__lib__/QueryBuilderEvent.js +3 -3
  6. package/lib/__lib__/QueryBuilderEvent.js.map +1 -1
  7. package/lib/__lib__/QueryBuilderSetting.js +1 -1
  8. package/lib/__lib__/QueryBuilderSetting.js.map +1 -1
  9. package/lib/__lib__/QueryBuilderTesting.js +1 -1
  10. package/lib/__lib__/QueryBuilderTesting.js.map +1 -1
  11. package/lib/components/QueryBuilderComponentElement.js +1 -1
  12. package/lib/components/QueryBuilderComponentElement.js.map +1 -1
  13. package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
  14. package/lib/components/QueryBuilderResultPanel.js +210 -69
  15. package/lib/components/QueryBuilderResultPanel.js.map +1 -1
  16. package/lib/components/QueryBuilder_LegendApplicationPlugin.js +1 -2
  17. package/lib/components/QueryBuilder_LegendApplicationPlugin.js.map +1 -1
  18. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts.map +1 -1
  19. package/lib/components/explorer/QueryBuilderExplorerPanel.js +3 -3
  20. package/lib/components/explorer/QueryBuilderExplorerPanel.js.map +1 -1
  21. package/lib/components/fetch-structure/QueryBuilderFetchStructurePanel.d.ts.map +1 -1
  22. package/lib/components/fetch-structure/QueryBuilderFetchStructurePanel.js +3 -3
  23. package/lib/components/fetch-structure/QueryBuilderFetchStructurePanel.js.map +1 -1
  24. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts.map +1 -1
  25. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js +60 -21
  26. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js.map +1 -1
  27. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  28. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +10 -2
  29. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  30. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.d.ts.map +1 -1
  31. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +2 -2
  32. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  33. package/lib/components/filter/QueryBuilderFilterPanel.d.ts +4 -0
  34. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  35. package/lib/components/filter/QueryBuilderFilterPanel.js +69 -31
  36. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  37. package/lib/components/shared/CustomDatePicker.js +1 -1
  38. package/lib/components/shared/CustomDatePicker.js.map +1 -1
  39. package/lib/graph/QueryBuilderMetaModelConst.js +3 -3
  40. package/lib/graph/QueryBuilderMetaModelConst.js.map +1 -1
  41. package/lib/graph-manager/QueryBuilderConst.js +1 -1
  42. package/lib/graph-manager/QueryBuilderConst.js.map +1 -1
  43. package/lib/graph-manager/protocol/pure/v1/V1_QueryBuilder_PureGraphManagerExtension.d.ts.map +1 -1
  44. package/lib/graph-manager/protocol/pure/v1/V1_QueryBuilder_PureGraphManagerExtension.js +4 -4
  45. package/lib/graph-manager/protocol/pure/v1/V1_QueryBuilder_PureGraphManagerExtension.js.map +1 -1
  46. package/lib/index.css +17 -1
  47. package/lib/index.css.map +1 -1
  48. package/lib/package.json +7 -7
  49. package/lib/stores/QueryBuilderChangeDetectionState.js +1 -1
  50. package/lib/stores/QueryBuilderChangeDetectionState.js.map +1 -1
  51. package/lib/stores/QueryBuilderCommand.js +1 -1
  52. package/lib/stores/QueryBuilderCommand.js.map +1 -1
  53. package/lib/stores/QueryBuilderConfig.js +2 -2
  54. package/lib/stores/QueryBuilderConfig.js.map +1 -1
  55. package/lib/stores/QueryBuilderGroupOperationHelper.js +1 -1
  56. package/lib/stores/QueryBuilderGroupOperationHelper.js.map +1 -1
  57. package/lib/stores/QueryBuilderResultState.d.ts +19 -0
  58. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  59. package/lib/stores/QueryBuilderResultState.js +48 -1
  60. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  61. package/lib/stores/QueryBuilderStateHashUtils.js +1 -1
  62. package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
  63. package/lib/stores/QueryBuilderTextEditorState.js +1 -1
  64. package/lib/stores/QueryBuilderTextEditorState.js.map +1 -1
  65. package/lib/stores/execution-plan/ExecutionPlanState.js +1 -1
  66. package/lib/stores/execution-plan/ExecutionPlanState.js.map +1 -1
  67. package/lib/stores/explorer/QueryBuilderExplorerState.js +1 -1
  68. package/lib/stores/explorer/QueryBuilderExplorerState.js.map +1 -1
  69. package/lib/stores/fetch-structure/QueryBuilderFetchStructureImplementationState.js +1 -1
  70. package/lib/stores/fetch-structure/QueryBuilderFetchStructureImplementationState.js.map +1 -1
  71. package/lib/stores/fetch-structure/graph-fetch/QueryBuilderGraphFetchTreeState.js +2 -2
  72. package/lib/stores/fetch-structure/graph-fetch/QueryBuilderGraphFetchTreeState.js.map +1 -1
  73. package/lib/stores/fetch-structure/tds/QueryBuilderTDSState.js +1 -1
  74. package/lib/stores/fetch-structure/tds/QueryBuilderTDSState.js.map +1 -1
  75. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.js +1 -1
  76. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.js.map +1 -1
  77. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts.map +1 -1
  78. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js +2 -2
  79. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js.map +1 -1
  80. package/lib/stores/filter/QueryBuilderFilterState.js +1 -1
  81. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  82. package/lib/stores/shared/LambdaParameterState.d.ts.map +1 -1
  83. package/lib/stores/shared/LambdaParameterState.js +2 -2
  84. package/lib/stores/shared/LambdaParameterState.js.map +1 -1
  85. package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts +5 -2
  86. package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts.map +1 -1
  87. package/lib/stores/shared/ValueSpecificationEditorHelper.js +30 -40
  88. package/lib/stores/shared/ValueSpecificationEditorHelper.js.map +1 -1
  89. package/package.json +15 -15
  90. package/src/components/QueryBuilderResultPanel.tsx +358 -93
  91. package/src/components/explorer/QueryBuilderExplorerPanel.tsx +10 -14
  92. package/src/components/fetch-structure/QueryBuilderFetchStructurePanel.tsx +6 -4
  93. package/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +103 -14
  94. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +18 -1
  95. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +12 -10
  96. package/src/components/filter/QueryBuilderFilterPanel.tsx +134 -42
  97. package/src/graph-manager/protocol/pure/v1/V1_QueryBuilder_PureGraphManagerExtension.ts +4 -5
  98. package/src/stores/QueryBuilderResultState.ts +82 -0
  99. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.ts +1 -0
  100. package/src/stores/shared/LambdaParameterState.ts +1 -0
  101. package/src/stores/shared/ValueSpecificationEditorHelper.ts +80 -57
@@ -66,10 +66,9 @@ import {
66
66
  isBoolean,
67
67
  type PlainObject,
68
68
  prettyDuration,
69
- prettyCONSTName,
70
69
  filterByType,
71
70
  } from '@finos/legend-shared';
72
- import { type MutableRefObject, forwardRef, useRef, useState } from 'react';
71
+ import { forwardRef, useState } from 'react';
73
72
  import {
74
73
  QueryBuilderDerivationProjectionColumnState,
75
74
  QueryBuilderProjectionColumnState,
@@ -97,13 +96,22 @@ import { PARAMETER_SUBMIT_ACTION } from '../stores/shared/LambdaParameterState.j
97
96
  import { QUERY_BUILDER_TEST_ID } from '../__lib__/QueryBuilderTesting.js';
98
97
  import {
99
98
  DataGrid,
100
- type DataGridCellMouseOverEvent,
99
+ type DataGridCellRendererParams,
101
100
  } from '@finos/legend-lego/data-grid';
102
101
  import {
103
102
  CODE_EDITOR_LANGUAGE,
104
103
  CodeEditor,
105
104
  } from '@finos/legend-lego/code-editor';
106
105
  import { ExecutionPlanViewer } from './execution-plan/ExecutionPlanViewer.js';
106
+ import type {
107
+ QueryBuilderTDSResultCellCoordinate,
108
+ QueryBuilderResultState,
109
+ QueryBuilderTDSResultCellData,
110
+ } from '../stores/QueryBuilderResultState.js';
111
+ import {
112
+ QueryBuilderPostFilterOperator_IsEmpty,
113
+ QueryBuilderPostFilterOperator_IsNotEmpty,
114
+ } from '../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js';
107
115
 
108
116
  export const tryToFormatSql = (sql: string): string => {
109
117
  try {
@@ -118,7 +126,7 @@ const QueryBuilderGridResultContextMenu = observer(
118
126
  forwardRef<
119
127
  HTMLDivElement,
120
128
  {
121
- event: MutableRefObject<DataGridCellMouseOverEvent | null>;
129
+ event: QueryBuilderTDSResultCellData | null;
122
130
  tdsState: QueryBuilderTDSState;
123
131
  }
124
132
  >(function QueryBuilderResultContextMenu(props, ref) {
@@ -126,20 +134,26 @@ const QueryBuilderGridResultContextMenu = observer(
126
134
  const applicationStore = useApplicationStore();
127
135
  const postFilterEqualOperator = new QueryBuilderPostFilterOperator_Equal();
128
136
  const postFilterInOperator = new QueryBuilderPostFilterOperator_In();
137
+ const postFilterEmptyOperator =
138
+ new QueryBuilderPostFilterOperator_IsEmpty();
139
+ const postFilterNotEmptyOperator =
140
+ new QueryBuilderPostFilterOperator_IsNotEmpty();
141
+
129
142
  const postFilterNotEqualOperator =
130
143
  new QueryBuilderPostFilterOperator_NotEqual();
131
144
  const postFilterNotInOperator = new QueryBuilderPostFilterOperator_NotIn();
132
145
  const postFilterState = tdsState.postFilterState;
133
- const projectionColumnState = tdsState.tdsColumns
134
- .filter((c) => c.columnName === event.current?.column.getColId())
146
+
147
+ const projectionColumnState = tdsState.projectionColumns
148
+ .filter((c) => c.columnName === event?.columnName)
135
149
  .concat(
136
150
  tdsState.aggregationState.columns
137
- .filter((c) => c.columnName === event.current?.column.getColId())
151
+ .filter((c) => c.columnName === event?.columnName)
138
152
  .map((ag) => ag.projectionColumnState),
139
153
  )[0];
140
-
141
154
  const getExistingPostFilterNode = (
142
155
  operators: QueryBuilderPostFilterOperator[],
156
+ projectionColumnName: string | undefined,
143
157
  ): QueryBuilderPostFilterTreeNodeData | undefined =>
144
158
  Array.from(postFilterState.nodes.values())
145
159
  .filter(
@@ -151,7 +165,8 @@ const QueryBuilderGridResultContextMenu = observer(
151
165
  .filter(
152
166
  (n) =>
153
167
  (n as QueryBuilderPostFilterTreeConditionNodeData).condition
154
- .columnState.columnName === projectionColumnState?.columnName &&
168
+ .columnState.columnName ===
169
+ (projectionColumnName ?? projectionColumnState?.columnName) &&
155
170
  operators
156
171
  .map((op) => op.getLabel())
157
172
  .includes(
@@ -163,8 +178,9 @@ const QueryBuilderGridResultContextMenu = observer(
163
178
 
164
179
  const updateFilterConditionValue = (
165
180
  conditionValue: InstanceValue,
181
+ cellData: QueryBuilderTDSResultCellData,
166
182
  ): void => {
167
- if (event.current?.value !== null) {
183
+ if (cellData.value) {
168
184
  instanceValue_setValue(
169
185
  conditionValue,
170
186
  conditionValue instanceof EnumValueInstanceValue
@@ -173,10 +189,10 @@ const QueryBuilderGridResultContextMenu = observer(
173
189
  (
174
190
  conditionValue.genericType?.ownerReference
175
191
  .value as Enumeration
176
- ).values.filter((v) => v.name === event.current?.value)[0],
192
+ ).values.filter((v) => v.name === cellData.value)[0],
177
193
  ),
178
194
  )
179
- : event.current?.value,
195
+ : cellData.value,
180
196
  0,
181
197
  tdsState.queryBuilderState.observerContext,
182
198
  );
@@ -185,15 +201,28 @@ const QueryBuilderGridResultContextMenu = observer(
185
201
 
186
202
  const generateNewPostFilterConditionNodeData = async (
187
203
  operator: QueryBuilderPostFilterOperator,
204
+ cellData: QueryBuilderTDSResultCellData,
188
205
  ): Promise<void> => {
189
- if (projectionColumnState) {
190
- try {
191
- const postFilterConditionState = new PostFilterConditionState(
206
+ let postFilterConditionState: PostFilterConditionState;
207
+ try {
208
+ const possibleProjectionColumnState = cellData.columnName
209
+ ? tdsState.projectionColumns
210
+ .filter((c) => c.columnName === cellData.columnName)
211
+ .concat(
212
+ tdsState.aggregationState.columns
213
+ .filter((c) => c.columnName === cellData.columnName)
214
+ .map((ag) => ag.projectionColumnState),
215
+ )[0]
216
+ : projectionColumnState;
217
+
218
+ if (possibleProjectionColumnState) {
219
+ postFilterConditionState = new PostFilterConditionState(
192
220
  postFilterState,
193
- projectionColumnState,
221
+ possibleProjectionColumnState,
194
222
  undefined,
195
223
  operator,
196
224
  );
225
+
197
226
  if (
198
227
  projectionColumnState instanceof
199
228
  QueryBuilderDerivationProjectionColumnState
@@ -202,13 +231,16 @@ const QueryBuilderGridResultContextMenu = observer(
202
231
  projectionColumnState.fetchDerivationLambdaReturnType(),
203
232
  );
204
233
  }
234
+
205
235
  const defaultFilterConditionValue =
206
236
  postFilterConditionState.operator.getDefaultFilterConditionValue(
207
237
  postFilterConditionState,
208
238
  );
239
+
209
240
  postFilterConditionState.setValue(defaultFilterConditionValue);
210
241
  updateFilterConditionValue(
211
242
  defaultFilterConditionValue as InstanceValue,
243
+ cellData,
212
244
  );
213
245
  postFilterState.addNodeFromNode(
214
246
  new QueryBuilderPostFilterTreeConditionNodeData(
@@ -217,41 +249,56 @@ const QueryBuilderGridResultContextMenu = observer(
217
249
  ),
218
250
  undefined,
219
251
  );
220
- } catch (error) {
221
- assertErrorThrown(error);
222
- applicationStore.notificationService.notifyWarning(error.message);
223
- return;
224
252
  }
253
+ } catch (error) {
254
+ assertErrorThrown(error);
255
+ applicationStore.notificationService.notifyWarning(error.message);
256
+ return;
225
257
  }
226
258
  };
227
259
 
228
260
  const updateExistingPostFilterConditionNodeData = (
229
261
  existingPostFilterNode: QueryBuilderPostFilterTreeNodeData,
230
262
  isFilterBy: boolean,
263
+ cellData: QueryBuilderTDSResultCellData,
264
+ operator: QueryBuilderPostFilterOperator,
231
265
  ): void => {
266
+ if (
267
+ operator === postFilterEmptyOperator ||
268
+ operator === postFilterNotEmptyOperator
269
+ ) {
270
+ const conditionState = (
271
+ existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
272
+ ).condition;
273
+ if (conditionState.operator.getLabel() !== operator.getLabel()) {
274
+ conditionState.changeOperator(
275
+ isFilterBy ? postFilterEmptyOperator : postFilterNotEmptyOperator,
276
+ );
277
+ }
278
+ return;
279
+ }
232
280
  const conditionState = (
233
281
  existingPostFilterNode as QueryBuilderPostFilterTreeConditionNodeData
234
282
  ).condition;
235
- if (
236
- conditionState.operator.getLabel() ===
237
- (isFilterBy
238
- ? postFilterEqualOperator
239
- : postFilterNotEqualOperator
240
- ).getLabel()
241
- ) {
283
+
284
+ if (conditionState.operator.getLabel() === operator.getLabel()) {
242
285
  const doesValueAlreadyExist =
243
286
  conditionState.value instanceof InstanceValue &&
244
287
  (conditionState.value instanceof EnumValueInstanceValue
245
288
  ? conditionState.value.values.map((ef) => ef.value.name)
246
289
  : conditionState.value.values
247
- ).includes(event.current?.value);
290
+ ).includes(cellData.value);
291
+
248
292
  if (!doesValueAlreadyExist) {
249
293
  const currentValueSpecificaton = conditionState.value;
250
294
  const newValueSpecification =
251
295
  conditionState.operator.getDefaultFilterConditionValue(
252
296
  conditionState,
253
297
  );
254
- updateFilterConditionValue(newValueSpecification as InstanceValue);
298
+ updateFilterConditionValue(
299
+ newValueSpecification as InstanceValue,
300
+ cellData,
301
+ );
255
302
  conditionState.changeOperator(
256
303
  isFilterBy ? postFilterInOperator : postFilterNotInOperator,
257
304
  );
@@ -272,12 +319,16 @@ const QueryBuilderGridResultContextMenu = observer(
272
319
  : (v as InstanceValue).values,
273
320
  )
274
321
  .flat()
275
- .includes(event.current?.value);
322
+ .includes(cellData.value ?? event?.value);
323
+
276
324
  if (!doesValueAlreadyExist) {
277
325
  const newValueSpecification = (
278
326
  isFilterBy ? postFilterEqualOperator : postFilterNotEqualOperator
279
327
  ).getDefaultFilterConditionValue(conditionState);
280
- updateFilterConditionValue(newValueSpecification as InstanceValue);
328
+ updateFilterConditionValue(
329
+ newValueSpecification as InstanceValue,
330
+ cellData,
331
+ );
281
332
  instanceValue_setValues(
282
333
  conditionState.value as InstanceValue,
283
334
  [
@@ -290,32 +341,76 @@ const QueryBuilderGridResultContextMenu = observer(
290
341
  }
291
342
  };
292
343
 
293
- const filterByOrOut = (isFilterBy: boolean): void => {
344
+ const getFilterOperator = (
345
+ isFilterBy: boolean,
346
+ cellData: QueryBuilderTDSResultCellData,
347
+ ): QueryBuilderPostFilterOperator => {
348
+ if (isFilterBy === true) {
349
+ if (cellData.value === null) {
350
+ return postFilterEmptyOperator;
351
+ } else {
352
+ return postFilterEqualOperator;
353
+ }
354
+ } else {
355
+ if (cellData.value === null) {
356
+ return postFilterNotEmptyOperator;
357
+ } else {
358
+ return postFilterNotEqualOperator;
359
+ }
360
+ }
361
+ };
362
+
363
+ const filterByOrOutValue = (
364
+ isFilterBy: boolean,
365
+ cellData: QueryBuilderTDSResultCellData,
366
+ ): void => {
294
367
  tdsState.setShowPostFilterPanel(true);
368
+
369
+ const operator = getFilterOperator(isFilterBy, cellData);
370
+
295
371
  const existingPostFilterNode = getExistingPostFilterNode(
296
- isFilterBy
372
+ cellData.value === null
373
+ ? [postFilterEmptyOperator, postFilterNotEmptyOperator]
374
+ : isFilterBy
297
375
  ? [postFilterEqualOperator, postFilterInOperator]
298
376
  : [postFilterNotEqualOperator, postFilterNotInOperator],
377
+ cellData.columnName,
299
378
  );
379
+
300
380
  existingPostFilterNode === undefined
301
- ? generateNewPostFilterConditionNodeData(
302
- isFilterBy ? postFilterEqualOperator : postFilterNotEqualOperator,
303
- ).catch(applicationStore.alertUnhandledError)
381
+ ? generateNewPostFilterConditionNodeData(operator, cellData).catch(
382
+ applicationStore.alertUnhandledError,
383
+ )
304
384
  : updateExistingPostFilterConditionNodeData(
305
385
  existingPostFilterNode,
306
386
  isFilterBy,
387
+ cellData,
388
+ operator,
307
389
  );
308
390
  };
309
391
 
392
+ const filterByOrOutValues = (isFilterBy: boolean): void => {
393
+ tdsState.queryBuilderState.resultState.selectedCells.forEach(
394
+ (cellData) => {
395
+ filterByOrOutValue(isFilterBy, cellData);
396
+ },
397
+ );
398
+ };
399
+
310
400
  const handleCopyCellValue = applicationStore.guardUnhandledError(() =>
311
401
  applicationStore.clipboardService.copyTextToClipboard(
312
- event.current?.value,
402
+ event?.value?.toString() ?? '',
313
403
  ),
314
404
  );
315
405
 
316
406
  const handleCopyRowValue = applicationStore.guardUnhandledError(() =>
317
407
  applicationStore.clipboardService.copyTextToClipboard(
318
- Object.values(event.current?.data).toString(),
408
+ tdsState.queryBuilderState.resultState
409
+ .findRowFromRowIndex(
410
+ tdsState.queryBuilderState.resultState.selectedCells[0]?.coordinates
411
+ .rowIndex ?? 0,
412
+ )
413
+ .toString(),
319
414
  ),
320
415
  );
321
416
 
@@ -324,7 +419,7 @@ const QueryBuilderGridResultContextMenu = observer(
324
419
  <MenuContentItem
325
420
  disabled={!projectionColumnState}
326
421
  onClick={(): void => {
327
- filterByOrOut(true);
422
+ filterByOrOutValues(true);
328
423
  }}
329
424
  >
330
425
  Filter By
@@ -332,7 +427,7 @@ const QueryBuilderGridResultContextMenu = observer(
332
427
  <MenuContentItem
333
428
  disabled={!projectionColumnState}
334
429
  onClick={(): void => {
335
- filterByOrOut(false);
430
+ filterByOrOutValues(false);
336
431
  }}
337
432
  >
338
433
  Filter Out
@@ -349,18 +444,201 @@ const QueryBuilderGridResultContextMenu = observer(
349
444
  }),
350
445
  );
351
446
 
447
+ type IQueryRendererParamsWithGridType = DataGridCellRendererParams & {
448
+ resultState: QueryBuilderResultState;
449
+ tdsExecutionResult: TDSExecutionResult;
450
+ };
451
+
452
+ const QueryResultCellRenderer = observer(
453
+ (params: IQueryRendererParamsWithGridType) => {
454
+ const resultState = params.resultState;
455
+ const tdsExecutionResult = params.tdsExecutionResult;
456
+
457
+ const cellValue = params.value as string;
458
+ const columnName = params.column?.getColId() ?? '';
459
+
460
+ const findCoordinatesFromResultValue = (
461
+ colId: string,
462
+ rowNumber: number,
463
+ ): QueryBuilderTDSResultCellCoordinate => {
464
+ const colIndex = tdsExecutionResult.result.columns.findIndex(
465
+ (col) => col === colId,
466
+ );
467
+
468
+ return { rowIndex: rowNumber, colIndex: colIndex };
469
+ };
470
+
471
+ const currentCellCoordinates = findCoordinatesFromResultValue(
472
+ columnName,
473
+ params.rowIndex,
474
+ );
475
+
476
+ const cellInFilteredResults = resultState.selectedCells.some(
477
+ (result) =>
478
+ result.coordinates.colIndex === currentCellCoordinates.colIndex &&
479
+ result.coordinates.rowIndex === currentCellCoordinates.rowIndex,
480
+ );
481
+
482
+ const mouseDown: React.MouseEventHandler = (event) => {
483
+ event.preventDefault();
484
+
485
+ if (event.shiftKey) {
486
+ const coordinates = findCoordinatesFromResultValue(
487
+ columnName,
488
+ params.rowIndex,
489
+ );
490
+ const actualValue = resultState.findResultValueFromCoordinates([
491
+ coordinates.rowIndex,
492
+ coordinates.colIndex,
493
+ ]);
494
+ resultState.addCellData({
495
+ value: actualValue,
496
+ columnName: columnName,
497
+ coordinates: coordinates,
498
+ });
499
+ return;
500
+ }
501
+
502
+ if (event.button === 0) {
503
+ resultState.setIsSelectingCells(true);
504
+ resultState.setSelectedCells([]);
505
+ const coordinates = findCoordinatesFromResultValue(
506
+ columnName,
507
+ params.rowIndex,
508
+ );
509
+ const actualValue = resultState.findResultValueFromCoordinates([
510
+ coordinates.rowIndex,
511
+ coordinates.colIndex,
512
+ ]);
513
+
514
+ const rowNode = params.api.getRowNode(params.rowIndex.toString());
515
+
516
+ if (rowNode) {
517
+ params.api.refreshCells({
518
+ force: true,
519
+ columns: [columnName],
520
+ rowNodes: [rowNode],
521
+ });
522
+ }
523
+ resultState.setSelectedCells([
524
+ {
525
+ value: actualValue,
526
+ columnName: columnName,
527
+ coordinates: coordinates,
528
+ },
529
+ ]);
530
+ }
531
+ };
532
+ const mouseUp: React.MouseEventHandler = (event) => {
533
+ resultState.setIsSelectingCells(false);
534
+ };
535
+
536
+ const mouseOver: React.MouseEventHandler = (event) => {
537
+ if (resultState.isSelectingCells) {
538
+ if (resultState.selectedCells.length < 1) {
539
+ return;
540
+ }
541
+ const results = resultState.selectedCells[0];
542
+ if (!results) {
543
+ return;
544
+ }
545
+
546
+ const firstCorner = results.coordinates;
547
+ const secondCorner = findCoordinatesFromResultValue(
548
+ columnName,
549
+ params.rowIndex,
550
+ );
551
+
552
+ resultState.setSelectedCells([results]);
553
+
554
+ const minRow = Math.min(firstCorner.rowIndex, secondCorner.rowIndex);
555
+ const minCol = Math.min(firstCorner.colIndex, secondCorner.colIndex);
556
+ const maxRow = Math.max(firstCorner.rowIndex, secondCorner.rowIndex);
557
+ const maxCol = Math.max(firstCorner.colIndex, secondCorner.colIndex);
558
+
559
+ for (let x = minRow; x <= maxRow; x++) {
560
+ for (let y = minCol; y <= maxCol; y++) {
561
+ const actualValue = resultState.findResultValueFromCoordinates([
562
+ x,
563
+ y,
564
+ ]);
565
+
566
+ const valueAndColumnId = {
567
+ value: actualValue,
568
+ columnName: resultState.findColumnFromCoordinates(y),
569
+ coordinates: {
570
+ rowIndex: x,
571
+ colIndex: y,
572
+ },
573
+ } as QueryBuilderTDSResultCellData;
574
+
575
+ if (
576
+ !resultState.selectedCells.find(
577
+ (result) =>
578
+ result.coordinates.colIndex === y &&
579
+ result.coordinates.rowIndex === x,
580
+ )
581
+ ) {
582
+ resultState.addCellData(valueAndColumnId);
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
589
+ };
590
+
591
+ const fetchStructureImplementation =
592
+ resultState.queryBuilderState.fetchStructureState.implementation;
593
+
594
+ return (
595
+ <ContextMenu
596
+ content={
597
+ // NOTE: we only support this functionality for grid result with a projection fetch-structure
598
+ fetchStructureImplementation instanceof QueryBuilderTDSState ? (
599
+ <QueryBuilderGridResultContextMenu
600
+ event={resultState.mousedOverCell}
601
+ tdsState={fetchStructureImplementation}
602
+ />
603
+ ) : null
604
+ }
605
+ disabled={
606
+ !(
607
+ resultState.queryBuilderState.fetchStructureState
608
+ .implementation instanceof QueryBuilderTDSState
609
+ ) ||
610
+ !resultState.queryBuilderState.isQuerySupported ||
611
+ !resultState.mousedOverCell
612
+ }
613
+ menuProps={{ elevation: 7 }}
614
+ key={params.value as string}
615
+ className={clsx('ag-theme-balham-dark query-builder__result__tds-grid')}
616
+ >
617
+ <div
618
+ className={clsx('query-builder__result__values__table__cell', {
619
+ 'query-builder__result__values__table__cell--active':
620
+ cellInFilteredResults,
621
+ })}
622
+ onMouseDown={(event) => mouseDown(event)}
623
+ onMouseUp={(event) => mouseUp(event)}
624
+ onMouseOver={(event) => mouseOver(event)}
625
+ >
626
+ <span>{cellValue}</span>
627
+ </div>
628
+ </ContextMenu>
629
+ );
630
+ },
631
+ );
632
+
352
633
  const QueryBuilderGridResult = observer(
353
634
  (props: {
354
635
  executionResult: TDSExecutionResult;
355
636
  queryBuilderState: QueryBuilderState;
356
637
  }) => {
357
638
  const { executionResult, queryBuilderState } = props;
358
- const fetchStructureImplementation =
359
- queryBuilderState.fetchStructureState.implementation;
360
- const cellDoubleClickedEvent = useRef<DataGridCellMouseOverEvent | null>(
361
- null,
362
- );
363
- const columns = executionResult.result.columns;
639
+
640
+ const resultState = queryBuilderState.resultState;
641
+
364
642
  const rowData = executionResult.result.rows.map((_row, rowIdx) => {
365
643
  const row: PlainObject = {};
366
644
  const cols = executionResult.result.columns;
@@ -370,57 +648,42 @@ const QueryBuilderGridResult = observer(
370
648
  // See https://github.com/finos/legend-studio/issues/1008
371
649
  row[cols[colIdx] as string] = isBoolean(value) ? String(value) : value;
372
650
  });
651
+
373
652
  row.rowNumber = rowIdx;
374
653
  return row;
375
654
  });
376
655
 
377
656
  return (
378
- <ContextMenu
379
- content={
380
- // NOTE: we only support this functionality for grid result with a projection fetch-structure
381
- fetchStructureImplementation instanceof QueryBuilderTDSState ? (
382
- <QueryBuilderGridResultContextMenu
383
- event={cellDoubleClickedEvent}
384
- tdsState={fetchStructureImplementation}
385
- />
386
- ) : null
387
- }
388
- disabled={
389
- !(fetchStructureImplementation instanceof QueryBuilderTDSState) ||
390
- !queryBuilderState.isQuerySupported ||
391
- !cellDoubleClickedEvent
392
- }
393
- menuProps={{ elevation: 7 }}
394
- key={executionResult._UUID}
395
- className={clsx('ag-theme-balham-dark query-builder__result__tds-grid')}
396
- >
397
- <DataGrid
398
- rowData={rowData}
399
- gridOptions={{
400
- suppressScrollOnNewData: true,
401
- getRowId: function (data) {
402
- return data.data.rowNumber as string;
403
- },
404
- }}
405
- // NOTE: we use onCellMouseOver as a bit of a workaround
406
- // since we use the context menu so we want the user to be
407
- // able to right click any cell and have the context menu
408
- // options use the data belonging to the row that they are
409
- // in. hence why we set the cell every time we mouse over
410
- // rather than making user click multiple times.
411
- onCellMouseOver={(event): void => {
412
- cellDoubleClickedEvent.current = event;
413
- }}
414
- suppressFieldDotNotation={true}
415
- columnDefs={columns.map((colName) => ({
416
- minWidth: 50,
417
- sortable: true,
418
- resizable: true,
419
- field: colName,
420
- flex: 1,
421
- }))}
422
- />
423
- </ContextMenu>
657
+ <div className="query-builder__result__values__table">
658
+ <div
659
+ className={clsx(
660
+ 'ag-theme-balham-dark query-builder__result__tds-grid',
661
+ )}
662
+ >
663
+ <DataGrid
664
+ rowData={rowData}
665
+ gridOptions={{
666
+ suppressScrollOnNewData: true,
667
+ getRowId: function (data) {
668
+ return data.data.rowNumber as string;
669
+ },
670
+ }}
671
+ suppressFieldDotNotation={true}
672
+ columnDefs={executionResult.result.columns.map((colName) => ({
673
+ minWidth: 50,
674
+ sortable: true,
675
+ resizable: true,
676
+ field: colName,
677
+ flex: 1,
678
+ cellRenderer: QueryResultCellRenderer,
679
+ cellRendererParams: {
680
+ resultState: resultState,
681
+ tdsExecutionResult: executionResult,
682
+ },
683
+ }))}
684
+ />
685
+ </div>
686
+ </div>
424
687
  );
425
688
  },
426
689
  );
@@ -532,6 +795,7 @@ export const QueryBuilderResultPanel = observer(
532
795
  !queryBuilderState.isQuerySupported || isSupportedQueryValid;
533
796
 
534
797
  const runQuery = (): void => {
798
+ resultState.setSelectedCells([]);
535
799
  resultState.pressedRunQuery.inProgress();
536
800
  if (queryParametersState.parameterStates.length) {
537
801
  queryParametersState.parameterValuesEditorState.open(
@@ -565,6 +829,7 @@ export const QueryBuilderResultPanel = observer(
565
829
  val === '' ? 0 : parseInt(val, 10),
566
830
  );
567
831
  };
832
+
568
833
  const allowSettingPreviewLimit = queryBuilderState.isQuerySupported;
569
834
 
570
835
  const copyExpression = (value: string): void => {
@@ -773,7 +1038,7 @@ export const QueryBuilderResultPanel = observer(
773
1038
  className="query-builder__result__export__dropdown__menu__item"
774
1039
  onClick={(): void => confirmExport(format)}
775
1040
  >
776
- {prettyCONSTName(format)}
1041
+ {format}
777
1042
  </MenuContentItem>
778
1043
  ))}
779
1044
  </MenuContent>