@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,502 @@
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 {
18
+ MenuContent,
19
+ MenuContentItem,
20
+ MenuContentDivider,
21
+ } from '@finos/legend-art';
22
+ import { observer } from 'mobx-react-lite';
23
+ import { flowResult } from 'mobx';
24
+ import {
25
+ type TDSExecutionResult,
26
+ type Enumeration,
27
+ InstanceValue,
28
+ EnumValueInstanceValue,
29
+ EnumValueExplicitReference,
30
+ PRIMITIVE_TYPE,
31
+ type ExecutionResult,
32
+ RelationalExecutionActivities,
33
+ } from '@finos/legend-graph';
34
+ import { format as formatSQL } from 'sql-formatter';
35
+ import {
36
+ type ApplicationStore,
37
+ type LegendApplicationConfig,
38
+ type LegendApplicationPlugin,
39
+ type LegendApplicationPluginManager,
40
+ useApplicationStore,
41
+ } from '@finos/legend-application';
42
+ import {
43
+ assertErrorThrown,
44
+ guaranteeNonNullable,
45
+ filterByType,
46
+ isBoolean,
47
+ } from '@finos/legend-shared';
48
+ import { forwardRef } from 'react';
49
+ import {
50
+ QueryBuilderDerivationProjectionColumnState,
51
+ QueryBuilderProjectionColumnState,
52
+ } from '../../../stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
53
+ import {
54
+ type QueryBuilderPostFilterTreeNodeData,
55
+ type QueryBuilderPostFilterState,
56
+ PostFilterConditionState,
57
+ QueryBuilderPostFilterTreeConditionNodeData,
58
+ PostFilterValueSpecConditionValueState,
59
+ } from '../../../stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js';
60
+ import {
61
+ QueryBuilderPostFilterOperator_Equal,
62
+ QueryBuilderPostFilterOperator_NotEqual,
63
+ } from '../../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.js';
64
+ import {
65
+ QueryBuilderPostFilterOperator_In,
66
+ QueryBuilderPostFilterOperator_NotIn,
67
+ } from '../../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.js';
68
+ import type { QueryBuilderPostFilterOperator } from '../../../stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.js';
69
+ import type { QueryBuilderTDSState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
70
+ import {
71
+ instanceValue_setValue,
72
+ instanceValue_setValues,
73
+ } from '../../../stores/shared/ValueSpecificationModifierHelper.js';
74
+ import { type DataGridCellRendererParams } from '@finos/legend-lego/data-grid';
75
+ import type {
76
+ QueryBuilderResultState,
77
+ QueryBuilderTDSResultCellData,
78
+ QueryBuilderTDSRowDataType,
79
+ } from '../../../stores/QueryBuilderResultState.js';
80
+ import {
81
+ QueryBuilderPostFilterOperator_IsEmpty,
82
+ QueryBuilderPostFilterOperator_IsNotEmpty,
83
+ } from '../../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js';
84
+ import { getTDSColumnState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSHelper.js';
85
+ import type { QueryBuilderTDSColumnState } from '../../../stores/fetch-structure/tds/QueryBuilderTDSColumnState.js';
86
+
87
+ export const tryToFormatSql = (sql: string): string => {
88
+ try {
89
+ const formattedSql = formatSQL(sql, { language: 'mysql' });
90
+ return formattedSql;
91
+ } catch {
92
+ try {
93
+ const formattedSql = formatSQL(sql);
94
+ return formattedSql;
95
+ } catch {
96
+ return sql;
97
+ }
98
+ }
99
+ };
100
+
101
+ export const getExecutedSqlFromExecutionResult = (
102
+ execResult: ExecutionResult,
103
+ ): string | undefined => {
104
+ const executedSqls = execResult.activities
105
+ ?.filter(filterByType(RelationalExecutionActivities))
106
+ .map((relationalActivity) => relationalActivity.sql);
107
+ if (executedSqls?.length) {
108
+ let executedSql = '';
109
+ if (executedSqls.length > 1) {
110
+ for (let i = 0; i < executedSqls.length; i++) {
111
+ executedSql += `\n--QUERY #${i + 1}\n`;
112
+ executedSql += `${executedSqls[i]}\n`;
113
+ }
114
+ } else {
115
+ executedSql += executedSqls[0];
116
+ }
117
+ return executedSql;
118
+ }
119
+ return undefined;
120
+ };
121
+
122
+ export const getAggregationTDSColumnCustomizations = (
123
+ result: TDSExecutionResult,
124
+ columnName: string,
125
+ ): object | undefined => {
126
+ const columnType = result.builder.columns.find(
127
+ (col) => col.name === columnName,
128
+ )?.type;
129
+ switch (columnType) {
130
+ case PRIMITIVE_TYPE.STRING:
131
+ return {
132
+ filter: 'agTextColumnFilter',
133
+ allowedAggFuncs: ['count'],
134
+ };
135
+ case PRIMITIVE_TYPE.DATE:
136
+ case PRIMITIVE_TYPE.DATETIME:
137
+ case PRIMITIVE_TYPE.STRICTDATE:
138
+ return {
139
+ filter: 'agDateColumnFilter',
140
+ allowedAggFuncs: ['count'],
141
+ };
142
+ case PRIMITIVE_TYPE.DECIMAL:
143
+ case PRIMITIVE_TYPE.INTEGER:
144
+ case PRIMITIVE_TYPE.FLOAT:
145
+ return {
146
+ filter: 'agNumberColumnFilter',
147
+ allowedAggFuncs: ['count', 'sum', 'max', 'min', 'avg'],
148
+ };
149
+ default:
150
+ return {
151
+ allowedAggFuncs: ['count'],
152
+ };
153
+ }
154
+ };
155
+
156
+ export const getRowDataFromExecutionResult = (
157
+ executionResult: TDSExecutionResult,
158
+ ): QueryBuilderTDSRowDataType[] => {
159
+ const rowData = executionResult.result.rows.map((_row, rowIdx) => {
160
+ const row: QueryBuilderTDSRowDataType = {};
161
+ const cols = executionResult.result.columns;
162
+ _row.values.forEach((value, colIdx) => {
163
+ // `ag-grid` shows `false` value as empty string so we have
164
+ // call `.toString()` to avoid this behavior.
165
+ // See https://github.com/finos/legend-studio/issues/1008
166
+ row[cols[colIdx] as string] = isBoolean(value) ? String(value) : value;
167
+ });
168
+
169
+ row.rowNumber = rowIdx;
170
+ return row;
171
+ });
172
+ return rowData;
173
+ };
174
+
175
+ export type IQueryRendererParamsWithGridType = DataGridCellRendererParams & {
176
+ resultState: QueryBuilderResultState;
177
+ tdsExecutionResult: TDSExecutionResult;
178
+ };
179
+
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
+ };
233
+
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
+ );
262
+
263
+ if (tdsColState instanceof QueryBuilderDerivationProjectionColumnState) {
264
+ await flowResult(tdsColState.fetchDerivationLambdaReturnType());
265
+ }
266
+
267
+ const defaultFilterConditionValue =
268
+ postFilterConditionState.operator.getDefaultFilterConditionValue(
269
+ postFilterConditionState,
270
+ );
271
+
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
+ };
292
+
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,
334
+ );
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
+ );
348
+ }
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
+ );
376
+ }
377
+ }
378
+ }
379
+ };
380
+
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
+ };
399
+
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,
440
+ );
441
+ };
442
+
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
+ };
456
+
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();
469
+
470
+ const tdsColState = data?.columnName
471
+ ? getTDSColumnState(tdsState, data.columnName)
472
+ : undefined;
473
+
474
+ return (
475
+ <MenuContent ref={ref}>
476
+ <MenuContentItem
477
+ disabled={!tdsColState}
478
+ onClick={(): void => {
479
+ filterByOrOutValues(applicationStore, data, true, tdsState);
480
+ }}
481
+ >
482
+ Filter By
483
+ </MenuContentItem>
484
+ <MenuContentItem
485
+ disabled={!tdsColState}
486
+ onClick={(): void => {
487
+ filterByOrOutValues(applicationStore, data, false, tdsState);
488
+ }}
489
+ >
490
+ Filter Out
491
+ </MenuContentItem>
492
+ <MenuContentDivider />
493
+ <MenuContentItem onClick={copyCellValueFunc}>
494
+ Copy Cell Value
495
+ </MenuContentItem>
496
+ <MenuContentItem onClick={copyCellRowValueFunc}>
497
+ Copy Row Value
498
+ </MenuContentItem>
499
+ </MenuContent>
500
+ );
501
+ }),
502
+ );