@finos/legend-query-builder 4.11.4 → 4.11.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. package/lib/components/QueryBuilder.js +1 -1
  2. package/lib/components/QueryBuilder.js.map +1 -1
  3. package/lib/components/execution-plan/SQLExecutionNodeViewer.js +1 -1
  4. package/lib/components/execution-plan/SQLExecutionNodeViewer.js.map +1 -1
  5. package/lib/components/{QueryBuilderResultPanel.d.ts → result/QueryBuilderResultPanel.d.ts} +1 -2
  6. package/lib/components/result/QueryBuilderResultPanel.d.ts.map +1 -0
  7. package/lib/components/result/QueryBuilderResultPanel.js +185 -0
  8. package/lib/components/result/QueryBuilderResultPanel.js.map +1 -0
  9. package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts +24 -0
  10. package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts.map +1 -0
  11. package/lib/components/result/tds/QueryBuilderTDSGridResult.js +204 -0
  12. package/lib/components/result/tds/QueryBuilderTDSGridResult.js.map +1 -0
  13. package/lib/components/result/tds/QueryBuilderTDSResultShared.d.ts +37 -0
  14. package/lib/components/result/tds/QueryBuilderTDSResultShared.d.ts.map +1 -0
  15. package/lib/components/result/tds/QueryBuilderTDSResultShared.js +251 -0
  16. package/lib/components/result/tds/QueryBuilderTDSResultShared.js.map +1 -0
  17. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.d.ts +24 -0
  18. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.d.ts.map +1 -0
  19. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js +224 -0
  20. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js.map +1 -0
  21. package/lib/index.css +2 -2
  22. package/lib/index.css.map +1 -1
  23. package/lib/package.json +1 -1
  24. package/lib/stores/QueryBuilderResultState.d.ts +5 -1
  25. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  26. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  27. package/package.json +3 -3
  28. package/src/components/QueryBuilder.tsx +1 -1
  29. package/src/components/execution-plan/SQLExecutionNodeViewer.tsx +1 -1
  30. package/src/components/result/QueryBuilderResultPanel.tsx +569 -0
  31. package/src/components/result/tds/QueryBuilderTDSGridResult.tsx +346 -0
  32. package/src/components/result/tds/QueryBuilderTDSResultShared.tsx +502 -0
  33. package/src/components/result/tds/QueryBuilderTDSSimpleGridResult.tsx +387 -0
  34. package/src/stores/QueryBuilderResultState.ts +12 -1
  35. package/tsconfig.json +4 -1
  36. package/lib/components/QueryBuilderResultPanel.d.ts.map +0 -1
  37. package/lib/components/QueryBuilderResultPanel.js +0 -633
  38. package/lib/components/QueryBuilderResultPanel.js.map +0 -1
  39. package/src/components/QueryBuilderResultPanel.tsx +0 -1412
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Copyright (c) 2020-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { ContextMenu, clsx } from '@finos/legend-art';
18
+ import { observer } from 'mobx-react-lite';
19
+ import type { QueryBuilderState } from '../../../stores/QueryBuilderState.js';
20
+ import {
21
+ getTDSRowRankByColumnInAsc,
22
+ TDSExecutionResult,
23
+ } from '@finos/legend-graph';
24
+ import {
25
+ DataGrid,
26
+ type DataGridColumnDefinition,
27
+ } from '@finos/legend-lego/data-grid';
28
+ import {
29
+ getRowDataFromExecutionResult,
30
+ QueryBuilderGridResultContextMenu,
31
+ type IQueryRendererParamsWithGridType,
32
+ } from './QueryBuilderTDSResultShared.js';
33
+ import { QueryBuilderTDSState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
34
+ import { DEFAULT_LOCALE } from '../../../graph-manager/QueryBuilderConst.js';
35
+ import { isNumber, isString, isValidURL } from '@finos/legend-shared';
36
+ import type {
37
+ QueryBuilderTDSResultCellCoordinate,
38
+ QueryBuilderTDSResultCellData,
39
+ QueryBuilderTDSResultCellDataType,
40
+ QueryBuilderTDSRowDataType,
41
+ } from '../../../stores/QueryBuilderResultState.js';
42
+
43
+ const QueryResultCellRenderer = observer(
44
+ (params: IQueryRendererParamsWithGridType) => {
45
+ const resultState = params.resultState;
46
+ const tdsExecutionResult = params.tdsExecutionResult;
47
+ const fetchStructureImplementation =
48
+ resultState.queryBuilderState.fetchStructureState.implementation;
49
+ const applicationStore = resultState.queryBuilderState.applicationStore;
50
+ const cellValue = params.value as QueryBuilderTDSResultCellDataType;
51
+ const formattedCellValue = (): QueryBuilderTDSResultCellDataType => {
52
+ if (isNumber(cellValue)) {
53
+ return Intl.NumberFormat(DEFAULT_LOCALE, {
54
+ maximumFractionDigits: 4,
55
+ }).format(Number(cellValue));
56
+ }
57
+ return cellValue;
58
+ };
59
+ const cellValueUrlLink =
60
+ isString(cellValue) && isValidURL(cellValue) ? cellValue : undefined;
61
+ const columnName = params.column?.getColId() ?? '';
62
+ const findCoordinatesFromResultValue = (
63
+ colId: string,
64
+ rowNumber: number,
65
+ ): QueryBuilderTDSResultCellCoordinate => {
66
+ const colIndex = tdsExecutionResult.result.columns.findIndex(
67
+ (col) => col === colId,
68
+ );
69
+ return { rowIndex: rowNumber, colIndex: colIndex };
70
+ };
71
+
72
+ const currentCellCoordinates = findCoordinatesFromResultValue(
73
+ columnName,
74
+ params.rowIndex,
75
+ );
76
+ const cellInFilteredResults = resultState.selectedCells.some(
77
+ (result) =>
78
+ result.coordinates.colIndex === currentCellCoordinates.colIndex &&
79
+ result.coordinates.rowIndex === currentCellCoordinates.rowIndex,
80
+ );
81
+
82
+ const findColumnFromCoordinates = (
83
+ colIndex: number,
84
+ ): QueryBuilderTDSResultCellDataType => {
85
+ if (
86
+ !resultState.executionResult ||
87
+ !(resultState.executionResult instanceof TDSExecutionResult)
88
+ ) {
89
+ return undefined;
90
+ }
91
+ return resultState.executionResult.result.columns[colIndex];
92
+ };
93
+
94
+ const findResultValueFromCoordinates = (
95
+ resultCoordinate: [number, number],
96
+ ): QueryBuilderTDSResultCellDataType => {
97
+ const rowIndex = resultCoordinate[0];
98
+ const colIndex = resultCoordinate[1];
99
+
100
+ if (
101
+ !resultState.executionResult ||
102
+ !(resultState.executionResult instanceof TDSExecutionResult)
103
+ ) {
104
+ return undefined;
105
+ }
106
+ if (params.columnApi.getColumnState()[colIndex]?.sort === 'asc') {
107
+ resultState.executionResult.result.rows.sort((a, b) =>
108
+ getTDSRowRankByColumnInAsc(a, b, colIndex),
109
+ );
110
+ } else if (params.columnApi.getColumnState()[colIndex]?.sort === 'desc') {
111
+ resultState.executionResult.result.rows.sort((a, b) =>
112
+ getTDSRowRankByColumnInAsc(b, a, colIndex),
113
+ );
114
+ }
115
+ return resultState.executionResult.result.rows[rowIndex]?.values[
116
+ colIndex
117
+ ];
118
+ };
119
+
120
+ const isCoordinatesSelected = (
121
+ resultCoordinate: QueryBuilderTDSResultCellCoordinate,
122
+ ): boolean =>
123
+ resultState.selectedCells.some(
124
+ (cell) =>
125
+ cell.coordinates.rowIndex === resultCoordinate.rowIndex &&
126
+ cell.coordinates.colIndex === resultCoordinate.colIndex,
127
+ );
128
+
129
+ const mouseDown: React.MouseEventHandler = (event) => {
130
+ event.preventDefault();
131
+
132
+ if (event.shiftKey) {
133
+ const coordinates = findCoordinatesFromResultValue(
134
+ columnName,
135
+ params.rowIndex,
136
+ );
137
+ const actualValue = findResultValueFromCoordinates([
138
+ coordinates.rowIndex,
139
+ coordinates.colIndex,
140
+ ]);
141
+ resultState.addSelectedCell({
142
+ value: actualValue,
143
+ columnName: columnName,
144
+ coordinates: coordinates,
145
+ });
146
+ return;
147
+ }
148
+
149
+ if (event.button === 0) {
150
+ resultState.setIsSelectingCells(true);
151
+ resultState.setSelectedCells([]);
152
+ const coordinates = findCoordinatesFromResultValue(
153
+ columnName,
154
+ params.rowIndex,
155
+ );
156
+ const actualValue = findResultValueFromCoordinates([
157
+ coordinates.rowIndex,
158
+ coordinates.colIndex,
159
+ ]);
160
+ resultState.setSelectedCells([
161
+ {
162
+ value: actualValue,
163
+ columnName: columnName,
164
+ coordinates: coordinates,
165
+ },
166
+ ]);
167
+ resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
168
+ }
169
+
170
+ if (event.button === 2) {
171
+ const coordinates = findCoordinatesFromResultValue(
172
+ columnName,
173
+ params.rowIndex,
174
+ );
175
+ const isInSelected = isCoordinatesSelected(coordinates);
176
+ if (!isInSelected) {
177
+ const actualValue = findResultValueFromCoordinates([
178
+ coordinates.rowIndex,
179
+ coordinates.colIndex,
180
+ ]);
181
+ resultState.setSelectedCells([
182
+ {
183
+ value: actualValue,
184
+ columnName: columnName,
185
+ coordinates: coordinates,
186
+ },
187
+ ]);
188
+ resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
189
+ }
190
+ }
191
+ };
192
+
193
+ const mouseUp: React.MouseEventHandler = (event) => {
194
+ resultState.setIsSelectingCells(false);
195
+ };
196
+
197
+ const mouseOver: React.MouseEventHandler = (event) => {
198
+ if (resultState.isSelectingCells) {
199
+ if (resultState.selectedCells.length < 1) {
200
+ return;
201
+ }
202
+ const results = resultState.selectedCells[0];
203
+ if (!results) {
204
+ return;
205
+ }
206
+
207
+ const firstCorner = results.coordinates;
208
+ const secondCorner = findCoordinatesFromResultValue(
209
+ columnName,
210
+ params.rowIndex,
211
+ );
212
+
213
+ resultState.setSelectedCells([results]);
214
+
215
+ const minRow = Math.min(firstCorner.rowIndex, secondCorner.rowIndex);
216
+ const minCol = Math.min(firstCorner.colIndex, secondCorner.colIndex);
217
+ const maxRow = Math.max(firstCorner.rowIndex, secondCorner.rowIndex);
218
+ const maxCol = Math.max(firstCorner.colIndex, secondCorner.colIndex);
219
+
220
+ for (let x = minRow; x <= maxRow; x++) {
221
+ for (let y = minCol; y <= maxCol; y++) {
222
+ const actualValue = findResultValueFromCoordinates([x, y]);
223
+
224
+ const valueAndColumnId = {
225
+ value: actualValue,
226
+ columnName: findColumnFromCoordinates(y),
227
+ coordinates: {
228
+ rowIndex: x,
229
+ colIndex: y,
230
+ },
231
+ } as QueryBuilderTDSResultCellData;
232
+
233
+ if (
234
+ !resultState.selectedCells.find(
235
+ (result) =>
236
+ result.coordinates.colIndex === y &&
237
+ result.coordinates.rowIndex === x,
238
+ )
239
+ ) {
240
+ resultState.addSelectedCell(valueAndColumnId);
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ resultState.setMouseOverCell(resultState.selectedCells[0] ?? null);
247
+ };
248
+ const getContextMenuRenderer = (): React.ReactNode => {
249
+ if (fetchStructureImplementation instanceof QueryBuilderTDSState) {
250
+ const copyCellValue = applicationStore.guardUnhandledError(() =>
251
+ applicationStore.clipboardService.copyTextToClipboard(
252
+ fetchStructureImplementation.queryBuilderState.resultState.selectedCells
253
+ .map((cellData) => cellData.value)
254
+ .join(','),
255
+ ),
256
+ );
257
+ const findRowFromRowIndex = (rowIndex: number): string => {
258
+ if (
259
+ !fetchStructureImplementation.queryBuilderState.resultState
260
+ .executionResult ||
261
+ !(
262
+ fetchStructureImplementation.queryBuilderState.resultState
263
+ .executionResult instanceof TDSExecutionResult
264
+ )
265
+ ) {
266
+ return '';
267
+ }
268
+ // try to get the entire row value separated by comma
269
+ // rowData is in format of {columnName: value, columnName1: value, ...., rowNumber:value}
270
+ const valueArr: QueryBuilderTDSResultCellDataType[] = [];
271
+ Object.entries(
272
+ params.api.getRenderedNodes().find((n) => n.rowIndex === rowIndex)
273
+ ?.data as QueryBuilderTDSRowDataType,
274
+ ).forEach((entry) => {
275
+ if (entry[0] !== 'rowNumber') {
276
+ valueArr.push(entry[1] as QueryBuilderTDSResultCellDataType);
277
+ }
278
+ });
279
+ return valueArr.join(',');
280
+ };
281
+ const copyRowValue = applicationStore.guardUnhandledError(() =>
282
+ applicationStore.clipboardService.copyTextToClipboard(
283
+ findRowFromRowIndex(
284
+ fetchStructureImplementation.queryBuilderState.resultState
285
+ .selectedCells[0]?.coordinates.rowIndex ?? 0,
286
+ ),
287
+ ),
288
+ );
289
+ return (
290
+ <QueryBuilderGridResultContextMenu
291
+ data={resultState.mousedOverCell}
292
+ tdsState={fetchStructureImplementation}
293
+ copyCellValueFunc={copyCellValue}
294
+ copyCellRowValueFunc={copyRowValue}
295
+ />
296
+ );
297
+ }
298
+ return null;
299
+ };
300
+
301
+ return (
302
+ <ContextMenu
303
+ content={getContextMenuRenderer()}
304
+ disabled={
305
+ !(
306
+ resultState.queryBuilderState.fetchStructureState
307
+ .implementation instanceof QueryBuilderTDSState
308
+ ) ||
309
+ !resultState.queryBuilderState.isQuerySupported ||
310
+ !resultState.mousedOverCell
311
+ }
312
+ menuProps={{ elevation: 7 }}
313
+ className={clsx('ag-theme-balham-dark query-builder__result__tds-grid')}
314
+ >
315
+ <div
316
+ className={clsx('query-builder__result__values__table__cell', {
317
+ 'query-builder__result__values__table__cell--active':
318
+ cellInFilteredResults,
319
+ })}
320
+ onMouseDown={(event) => mouseDown(event)}
321
+ onMouseUp={(event) => mouseUp(event)}
322
+ onMouseOver={(event) => mouseOver(event)}
323
+ >
324
+ {cellValueUrlLink ? (
325
+ <a href={cellValueUrlLink} target="_blank" rel="noreferrer">
326
+ {cellValueUrlLink}
327
+ </a>
328
+ ) : (
329
+ <span>{formattedCellValue()}</span>
330
+ )}
331
+ </div>
332
+ </ContextMenu>
333
+ );
334
+ },
335
+ );
336
+
337
+ export const QueryBuilderTDSSimpleGridResult = observer(
338
+ (props: {
339
+ executionResult: TDSExecutionResult;
340
+ queryBuilderState: QueryBuilderState;
341
+ }) => {
342
+ const { executionResult, queryBuilderState } = props;
343
+ const resultState = queryBuilderState.resultState;
344
+ const colDefs = executionResult.result.columns.map(
345
+ (colName) =>
346
+ ({
347
+ minWidth: 50,
348
+ sortable: true,
349
+ resizable: true,
350
+ field: colName,
351
+ flex: 1,
352
+ cellRenderer: QueryResultCellRenderer,
353
+ cellRendererParams: {
354
+ resultState: resultState,
355
+ tdsExecutionResult: executionResult,
356
+ },
357
+ }) as DataGridColumnDefinition,
358
+ );
359
+
360
+ return (
361
+ <div className="query-builder__result__values__table">
362
+ <div
363
+ className={clsx(
364
+ 'ag-theme-balham-dark query-builder__result__tds-grid',
365
+ )}
366
+ >
367
+ <DataGrid
368
+ rowData={getRowDataFromExecutionResult(executionResult)}
369
+ gridOptions={{
370
+ suppressScrollOnNewData: true,
371
+ getRowId: (data) => data.data.rowNumber,
372
+ rowSelection: 'multiple',
373
+ }}
374
+ // NOTE: when column definition changed, we need to force refresh the cell to make sure the cell renderer is updated
375
+ // See https://stackoverflow.com/questions/56341073/how-to-refresh-an-ag-grid-when-a-change-occurs-inside-a-custom-cell-renderer-com
376
+ onRowDataUpdated={(params) => {
377
+ params.api.refreshCells({ force: true });
378
+ }}
379
+ suppressFieldDotNotation={true}
380
+ suppressContextMenu={false}
381
+ columnDefs={colDefs}
382
+ />
383
+ </div>
384
+ </div>
385
+ );
386
+ },
387
+ );
@@ -52,13 +52,24 @@ import type { DataGridColumnState } from '@finos/legend-lego/data-grid';
52
52
 
53
53
  export const DEFAULT_LIMIT = 1000;
54
54
 
55
+ export type QueryBuilderTDSResultCellDataType =
56
+ | string
57
+ | number
58
+ | boolean
59
+ | null
60
+ | undefined;
61
+
62
+ export interface QueryBuilderTDSRowDataType {
63
+ [key: string]: QueryBuilderTDSResultCellDataType;
64
+ }
65
+
55
66
  export interface ExportDataInfo {
56
67
  contentType: ContentType;
57
68
  serializationFormat?: EXECUTION_SERIALIZATION_FORMAT | undefined;
58
69
  }
59
70
 
60
71
  export interface QueryBuilderTDSResultCellData {
61
- value: string | number | boolean | null | undefined;
72
+ value: QueryBuilderTDSResultCellDataType;
62
73
  columnName: string;
63
74
  coordinates: QueryBuilderTDSResultCellCoordinate;
64
75
  }
package/tsconfig.json CHANGED
@@ -217,7 +217,6 @@
217
217
  "./src/components/QueryBuilderNavigationBlocker.tsx",
218
218
  "./src/components/QueryBuilderParametersPanel.tsx",
219
219
  "./src/components/QueryBuilderPropertyExpressionEditor.tsx",
220
- "./src/components/QueryBuilderResultPanel.tsx",
221
220
  "./src/components/QueryBuilderSideBar.tsx",
222
221
  "./src/components/QueryBuilderTextEditor.tsx",
223
222
  "./src/components/QueryBuilderUnsupportedQueryEditor.tsx",
@@ -257,6 +256,10 @@
257
256
  "./src/components/fetch-structure/QueryBuilderTDSPanel.tsx",
258
257
  "./src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx",
259
258
  "./src/components/filter/QueryBuilderFilterPanel.tsx",
259
+ "./src/components/result/QueryBuilderResultPanel.tsx",
260
+ "./src/components/result/tds/QueryBuilderTDSGridResult.tsx",
261
+ "./src/components/result/tds/QueryBuilderTDSResultShared.tsx",
262
+ "./src/components/result/tds/QueryBuilderTDSSimpleGridResult.tsx",
260
263
  "./src/components/shared/BasicValueSpecificationEditor.tsx",
261
264
  "./src/components/shared/CustomDatePicker.tsx",
262
265
  "./src/components/shared/LambdaEditor.tsx",
@@ -1 +0,0 @@
1
- {"version":3,"file":"QueryBuilderResultPanel.d.ts","sourceRoot":"","sources":["../../src/components/QueryBuilderResultPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA+BH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAmFxE,eAAO,MAAM,cAAc,QAAS,MAAM,KAAG,MAY5C,CAAC;AAmzBF,eAAO,MAAM,uBAAuB,WAC1B;IAAE,iBAAiB,EAAE,iBAAiB,CAAA;CAAE;;CAmcjD,CAAC"}