@finos/legend-query-builder 4.0.8 → 4.0.10

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.
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>