@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.
@@ -14,18 +14,325 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { clsx } from '@finos/legend-art';
17
+ import { ContextMenu, clsx } from '@finos/legend-art';
18
18
  import { observer } from 'mobx-react-lite';
19
19
  import type { QueryBuilderState } from '../../../stores/QueryBuilderState.js';
20
- import type { TDSExecutionResult } from '@finos/legend-graph';
20
+ import {
21
+ getTDSRowRankByColumnInAsc,
22
+ TDSExecutionResult,
23
+ } from '@finos/legend-graph';
21
24
  import {
22
25
  DataGrid,
23
26
  type DataGridColumnDefinition,
24
27
  } from '@finos/legend-lego/data-grid';
25
28
  import {
26
29
  getRowDataFromExecutionResult,
27
- QueryResultCellRenderer,
30
+ QueryBuilderGridResultContextMenu,
31
+ type IQueryRendererParamsWithGridType,
28
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
+ );
29
336
 
30
337
  export const QueryBuilderTDSSimpleGridResult = observer(
31
338
  (props: {