@finos/legend-query-builder 2.1.2 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. package/{src/stores/QueryBuilderBootstraper.ts → lib/application/QueryBuilderDocumentation.d.ts} +6 -8
  2. package/lib/application/QueryBuilderDocumentation.d.ts.map +1 -0
  3. package/lib/application/QueryBuilderDocumentation.js +23 -0
  4. package/lib/application/QueryBuilderDocumentation.js.map +1 -0
  5. package/lib/components/QueryBuilder.js +3 -2
  6. package/lib/components/QueryBuilder.js.map +1 -1
  7. package/lib/components/QueryBuilderConstantExpressionPanel.d.ts.map +1 -1
  8. package/lib/components/QueryBuilderConstantExpressionPanel.js +3 -2
  9. package/lib/components/QueryBuilderConstantExpressionPanel.js.map +1 -1
  10. package/lib/components/QueryBuilderDiffPanel.d.ts.map +1 -1
  11. package/lib/components/QueryBuilderDiffPanel.js +9 -8
  12. package/lib/components/QueryBuilderDiffPanel.js.map +1 -1
  13. package/lib/components/QueryBuilderParametersPanel.js +3 -2
  14. package/lib/components/QueryBuilderParametersPanel.js.map +1 -1
  15. package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
  16. package/lib/components/QueryBuilderResultPanel.js +8 -7
  17. package/lib/components/QueryBuilderResultPanel.js.map +1 -1
  18. package/lib/components/QueryBuilderTextEditor.js +3 -2
  19. package/lib/components/QueryBuilderTextEditor.js.map +1 -1
  20. package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts +2 -1
  21. package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts.map +1 -1
  22. package/lib/components/QueryBuilder_LegendApplicationPlugin.js +8 -0
  23. package/lib/components/QueryBuilder_LegendApplicationPlugin.js.map +1 -1
  24. package/lib/{stores/QueryBuilderBootstraper.js → components/execution-plan/ExecutionPlanViewer.d.ts} +12 -6
  25. package/lib/components/execution-plan/ExecutionPlanViewer.d.ts.map +1 -0
  26. package/lib/components/execution-plan/ExecutionPlanViewer.js +182 -0
  27. package/lib/components/execution-plan/ExecutionPlanViewer.js.map +1 -0
  28. package/lib/components/execution-plan/SQLExecutionNodeViewer.d.ts +31 -0
  29. package/lib/components/execution-plan/SQLExecutionNodeViewer.d.ts.map +1 -0
  30. package/lib/components/execution-plan/SQLExecutionNodeViewer.js +32 -0
  31. package/lib/components/execution-plan/SQLExecutionNodeViewer.js.map +1 -0
  32. package/lib/components/explorer/QueryBuilderPropertySearchPanel.js +2 -2
  33. package/lib/components/explorer/QueryBuilderPropertySearchPanel.js.map +1 -1
  34. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.d.ts.map +1 -1
  35. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +4 -4
  36. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  37. package/lib/components/shared/BasicValueSpecificationEditor.d.ts.map +1 -1
  38. package/lib/components/shared/BasicValueSpecificationEditor.js +2 -3
  39. package/lib/components/shared/BasicValueSpecificationEditor.js.map +1 -1
  40. package/lib/components/shared/LambdaEditor.d.ts.map +1 -1
  41. package/lib/components/shared/LambdaEditor.js +20 -19
  42. package/lib/components/shared/LambdaEditor.js.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/index.d.ts +3 -2
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +3 -2
  48. package/lib/index.js.map +1 -1
  49. package/lib/package.json +9 -17
  50. package/lib/stores/QueryBuilderResultState.d.ts +1 -1
  51. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  52. package/lib/stores/QueryBuilderResultState.js +2 -1
  53. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  54. package/lib/stores/execution-plan/ExecutionPlanState.d.ts +61 -0
  55. package/lib/stores/execution-plan/ExecutionPlanState.d.ts.map +1 -0
  56. package/lib/stores/execution-plan/ExecutionPlanState.js +118 -0
  57. package/lib/stores/execution-plan/ExecutionPlanState.js.map +1 -0
  58. package/lib/stores/explorer/QueryBuilderPropertySearchState.d.ts +3 -4
  59. package/lib/stores/explorer/QueryBuilderPropertySearchState.d.ts.map +1 -1
  60. package/lib/stores/explorer/QueryBuilderPropertySearchState.js +3 -4
  61. package/lib/stores/explorer/QueryBuilderPropertySearchState.js.map +1 -1
  62. package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
  63. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  64. package/package.json +16 -24
  65. package/{lib/stores/QueryBuilderBootstraper.d.ts → src/application/QueryBuilderDocumentation.ts} +8 -2
  66. package/src/components/QueryBuilder.tsx +2 -2
  67. package/src/components/QueryBuilderConstantExpressionPanel.tsx +2 -2
  68. package/src/components/QueryBuilderDiffPanel.tsx +16 -19
  69. package/src/components/QueryBuilderParametersPanel.tsx +2 -2
  70. package/src/components/QueryBuilderResultPanel.tsx +14 -14
  71. package/src/components/QueryBuilderTextEditor.tsx +4 -4
  72. package/src/components/QueryBuilder_LegendApplicationPlugin.ts +10 -0
  73. package/src/components/execution-plan/ExecutionPlanViewer.tsx +543 -0
  74. package/src/components/execution-plan/SQLExecutionNodeViewer.tsx +46 -0
  75. package/src/components/explorer/QueryBuilderPropertySearchPanel.tsx +2 -2
  76. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +12 -18
  77. package/src/components/shared/BasicValueSpecificationEditor.tsx +2 -2
  78. package/src/components/shared/LambdaEditor.tsx +27 -25
  79. package/src/index.ts +3 -2
  80. package/src/stores/QueryBuilderResultState.ts +2 -1
  81. package/src/stores/execution-plan/ExecutionPlanState.ts +153 -0
  82. package/src/stores/explorer/QueryBuilderPropertySearchState.ts +4 -4
  83. package/src/stores/filter/QueryBuilderFilterState.ts +2 -0
  84. package/tsconfig.json +4 -1
  85. package/lib/stores/QueryBuilderBootstraper.d.ts.map +0 -1
  86. package/lib/stores/QueryBuilderBootstraper.js.map +0 -1
@@ -0,0 +1,543 @@
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 { useState } from 'react';
18
+ import {
19
+ type TreeNodeContainerProps,
20
+ type TreeData,
21
+ Dialog,
22
+ ResizablePanelGroup,
23
+ ResizablePanelSplitter,
24
+ ResizablePanel,
25
+ ResizablePanelSplitterLine,
26
+ clsx,
27
+ TreeView,
28
+ ChevronDownIcon,
29
+ ChevronRightIcon,
30
+ MenuContentItem,
31
+ MenuContent,
32
+ DropdownMenu,
33
+ BlankPanelContent,
34
+ PanelContent,
35
+ ModalHeader,
36
+ Modal,
37
+ ModalBody,
38
+ ModalFooter,
39
+ PanelSideBarHeader,
40
+ ModalFooterButton,
41
+ } from '@finos/legend-art';
42
+ import {
43
+ addUniqueEntry,
44
+ filterByType,
45
+ isNonNullable,
46
+ } from '@finos/legend-shared';
47
+ import {
48
+ ExecutionNodeTreeNodeData,
49
+ ExecutionPlanViewTreeNodeData,
50
+ EXECUTION_PLAN_VIEW_MODE,
51
+ type ExecutionPlanState,
52
+ } from '../../stores/execution-plan/ExecutionPlanState.js';
53
+ import { observer } from 'mobx-react-lite';
54
+ import {
55
+ ExecutionPlan,
56
+ ExecutionNode,
57
+ SQLExecutionNode,
58
+ RelationalTDSInstantiationExecutionNode,
59
+ type RawExecutionPlan,
60
+ } from '@finos/legend-graph';
61
+ import { SQLExecutionNodeViewer } from './SQLExecutionNodeViewer.js';
62
+ import { CodeEditor } from '@finos/legend-lego/code-editor';
63
+ import { CODE_EDITOR_LANGUAGE, TAB_SIZE } from '@finos/legend-application';
64
+
65
+ /**
66
+ * @modularize
67
+ * See https://github.com/finos/legend-studio/issues/65
68
+ */
69
+ const generateExecutionNodeLabel = (type: ExecutionNode): string => {
70
+ if (type instanceof SQLExecutionNode) {
71
+ return `SQL Execution Node`;
72
+ } else if (type instanceof RelationalTDSInstantiationExecutionNode) {
73
+ return `Relational TDS Instantiation Execution Node`;
74
+ } else {
75
+ return 'Other';
76
+ }
77
+ };
78
+
79
+ const generateExecutionNodeTreeNodeData = (
80
+ executionNode: ExecutionNode,
81
+ label: string,
82
+ parentNode:
83
+ | ExecutionNodeTreeNodeData
84
+ | ExecutionPlanViewTreeNodeData
85
+ | undefined,
86
+ ): ExecutionNodeTreeNodeData => {
87
+ const executionNodeTreeNode = new ExecutionNodeTreeNodeData(
88
+ executionNode._UUID,
89
+ label,
90
+ executionNode,
91
+ );
92
+
93
+ const childrenIds: string[] = [];
94
+
95
+ executionNode.executionNodes
96
+ .slice()
97
+ .filter(filterByType(ExecutionNode))
98
+ .forEach((childExecutionNode) => {
99
+ addUniqueEntry(childrenIds, childExecutionNode._UUID);
100
+ });
101
+
102
+ executionNodeTreeNode.childrenIds = childrenIds;
103
+
104
+ return executionNodeTreeNode;
105
+ };
106
+
107
+ const generateExecutionPlanTreeNodeData = (
108
+ executionPlan: ExecutionPlan,
109
+ ): ExecutionPlanViewTreeNodeData => {
110
+ const executionPlanNode = new ExecutionPlanViewTreeNodeData(
111
+ `Execution Plan`,
112
+ `Execution Plan`,
113
+ executionPlan,
114
+ );
115
+
116
+ const childrenIds: string[] = [];
117
+
118
+ const rootNodeId = executionPlan.rootExecutionNode._UUID;
119
+ addUniqueEntry(childrenIds, rootNodeId);
120
+ executionPlanNode.childrenIds = childrenIds;
121
+ return executionPlanNode;
122
+ };
123
+
124
+ const getExecutionPlanTreeData = (
125
+ executionPlan: ExecutionPlan,
126
+ ): TreeData<ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData> => {
127
+ const rootIds: string[] = [];
128
+ const nodes = new Map<
129
+ string,
130
+ ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData
131
+ >();
132
+ const executionPlanTreeNode =
133
+ generateExecutionPlanTreeNodeData(executionPlan);
134
+ addUniqueEntry(rootIds, executionPlanTreeNode.id);
135
+ nodes.set(executionPlanTreeNode.id, executionPlanTreeNode);
136
+ return { rootIds, nodes };
137
+ };
138
+
139
+ const ExecutionNodeElementTreeNodeContainer: React.FC<
140
+ TreeNodeContainerProps<
141
+ ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData,
142
+ {
143
+ onNodeExpand: (
144
+ node: ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData,
145
+ ) => void;
146
+ }
147
+ >
148
+ > = (props) => {
149
+ const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props;
150
+ const { onNodeExpand } = innerProps;
151
+ const isExpandable = Boolean(node.childrenIds?.length);
152
+ const selectNode = (): void => onNodeSelect?.(node);
153
+ const expandNode = (): void => onNodeExpand(node);
154
+ const nodeExpandIcon = isExpandable ? (
155
+ node.isOpen ? (
156
+ <ChevronDownIcon />
157
+ ) : (
158
+ <ChevronRightIcon />
159
+ )
160
+ ) : (
161
+ <div />
162
+ );
163
+
164
+ return (
165
+ <div
166
+ className={clsx(
167
+ 'tree-view__node__container execution-plan-viewer__explorer-tree__node__container',
168
+ {
169
+ 'menu__trigger--on-menu-open': !node.isSelected,
170
+ },
171
+ {
172
+ 'execution-plan-viewer__explorer-tree__node__container--selected':
173
+ node.isSelected,
174
+ },
175
+ )}
176
+ style={{
177
+ paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1)}rem`,
178
+ }}
179
+ onClick={selectNode}
180
+ >
181
+ <div className="tree-view__node__icon">
182
+ <div className="tree-view__node__expand-icon" onClick={expandNode}>
183
+ {nodeExpandIcon}
184
+ </div>
185
+ </div>
186
+ <button
187
+ className="tree-view__node__label execution-plan-viewer__explorer-tree__node__label"
188
+ tabIndex={-1}
189
+ title={node.id}
190
+ >
191
+ {node.label}
192
+ </button>
193
+ </div>
194
+ );
195
+ };
196
+
197
+ export const ExecutionPlanTree: React.FC<{
198
+ executionPlanState: ExecutionPlanState;
199
+ executionPlan: ExecutionPlan;
200
+ }> = (props) => {
201
+ const { executionPlanState, executionPlan } = props;
202
+ // NOTE: We only need to compute this once so we use lazy initial state syntax
203
+ // See https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
204
+ const [treeData, setTreeData] = useState<
205
+ TreeData<ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData>
206
+ >(() => getExecutionPlanTreeData(executionPlan));
207
+ const onNodeSelect = (
208
+ node: ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData,
209
+ ): void => {
210
+ if (node instanceof ExecutionPlanViewTreeNodeData) {
211
+ executionPlanState.transformMetadataToProtocolJson(node.executionPlan);
212
+ } else if (node instanceof ExecutionNodeTreeNodeData) {
213
+ executionPlanState.transformMetadataToProtocolJson(node.executionNode);
214
+ }
215
+ executionPlanState.setSelectedNode(node);
216
+ };
217
+
218
+ const onNodeExpand = (
219
+ node: ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData,
220
+ ): void => {
221
+ if (node.childrenIds?.length) {
222
+ node.isOpen = !node.isOpen;
223
+ if (node instanceof ExecutionPlanViewTreeNodeData) {
224
+ const rootNode = node.executionPlan.rootExecutionNode;
225
+ const rootNodeTreeNode = generateExecutionNodeTreeNodeData(
226
+ rootNode,
227
+ generateExecutionNodeLabel(rootNode),
228
+ node,
229
+ );
230
+ treeData.nodes.set(rootNodeTreeNode.id, rootNodeTreeNode);
231
+ } else if (node instanceof ExecutionNodeTreeNodeData) {
232
+ if (node.executionNode.executionNodes.length > 0) {
233
+ node.executionNode.executionNodes.forEach((exen) => {
234
+ const executionNodeTreeNode = generateExecutionNodeTreeNodeData(
235
+ exen,
236
+ generateExecutionNodeLabel(exen),
237
+ node,
238
+ );
239
+
240
+ treeData.nodes.set(executionNodeTreeNode.id, executionNodeTreeNode);
241
+ });
242
+ }
243
+ }
244
+ }
245
+
246
+ setTreeData({ ...treeData });
247
+ };
248
+
249
+ const getChildNodes = (
250
+ node: ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData,
251
+ ): (ExecutionPlanViewTreeNodeData | ExecutionNodeTreeNodeData)[] => {
252
+ if (!node.childrenIds || node.childrenIds.length === 0) {
253
+ return [];
254
+ }
255
+ const childrenNodes = node.childrenIds
256
+ .map((id) => treeData.nodes.get(id))
257
+ .filter(isNonNullable);
258
+
259
+ return childrenNodes;
260
+ };
261
+ return (
262
+ <TreeView
263
+ components={{
264
+ TreeNodeContainer: ExecutionNodeElementTreeNodeContainer,
265
+ }}
266
+ treeData={treeData}
267
+ getChildNodes={getChildNodes}
268
+ onNodeSelect={onNodeSelect}
269
+ innerProps={{
270
+ onNodeExpand,
271
+ }}
272
+ />
273
+ );
274
+ };
275
+
276
+ const ExecutionNodeViewer = observer(
277
+ (props: {
278
+ executionNode: ExecutionNode;
279
+ executionPlanState: ExecutionPlanState;
280
+ }) => {
281
+ const { executionNode, executionPlanState } = props;
282
+ if (executionNode instanceof SQLExecutionNode) {
283
+ return (
284
+ <SQLExecutionNodeViewer
285
+ query={executionNode.sqlQuery}
286
+ resultColumns={executionNode.resultColumns}
287
+ executionPlanState={executionPlanState}
288
+ />
289
+ );
290
+ }
291
+ return (
292
+ <BlankPanelContent>
293
+ <div className="execution-node-viewer__unsupported-view">
294
+ <div className="execution-node-viewer__unsupported-view__summary">
295
+ {`Can't display execution node`}
296
+ </div>
297
+ <button
298
+ className="btn--dark execution-node-viewer__unsupported-view__to-text-mode__btn"
299
+ onClick={(): void =>
300
+ executionPlanState.setViewMode(EXECUTION_PLAN_VIEW_MODE.JSON)
301
+ }
302
+ >
303
+ View JSON
304
+ </button>
305
+ </div>
306
+ </BlankPanelContent>
307
+ );
308
+ },
309
+ );
310
+
311
+ const ExecutionPlanViewPanel = observer(
312
+ (props: { displayData: string; executionPlanState: ExecutionPlanState }) => {
313
+ const { displayData, executionPlanState } = props;
314
+ let currentElement;
315
+ if (executionPlanState.selectedNode !== undefined) {
316
+ if (
317
+ executionPlanState.selectedNode instanceof ExecutionPlanViewTreeNodeData
318
+ ) {
319
+ currentElement = executionPlanState.selectedNode.executionPlan;
320
+ } else if (
321
+ executionPlanState.selectedNode instanceof ExecutionNodeTreeNodeData
322
+ ) {
323
+ currentElement = executionPlanState.selectedNode.executionNode;
324
+ }
325
+ }
326
+ const nativeViewModes = Object.values(EXECUTION_PLAN_VIEW_MODE);
327
+
328
+ return (
329
+ <div className="execution-plan-viewer__panel">
330
+ {executionPlanState.selectedNode !== undefined && (
331
+ <>
332
+ <div className="panel__header execution-plan-viewer__panel__header">
333
+ <div className="execution-plan-viewer__panel__header__tabs">
334
+ <button className="execution-plan-viewer__panel__header__tab execution-plan-viewer__panel__header__tab--active">
335
+ {executionPlanState.selectedNode.label}
336
+ </button>
337
+ </div>
338
+ <DropdownMenu
339
+ className="execution-plan-viewer__panel__view-mode__type"
340
+ title="View as..."
341
+ content={
342
+ <MenuContent className="execution-plan-viewer__panel__view-mode__options execution-plan-viewer__panel__view-mode__options--with-group">
343
+ <div className="execution-plan-viewer__panel__view-mode__option__group execution-plan-viewer__panel__view-mode__option__group--native">
344
+ <div className="execution-plan-viewer__panel__view-mode__option__group__name">
345
+ native
346
+ </div>
347
+ <div className="execution-plan-viewer__panel__view-mode__option__group__options">
348
+ {nativeViewModes.map((mode) => (
349
+ <MenuContentItem
350
+ key={mode}
351
+ className="execution-plan-viewer__panel__view-mode__option"
352
+ onClick={(): void =>
353
+ executionPlanState.setViewMode(mode)
354
+ }
355
+ >
356
+ {mode}
357
+ </MenuContentItem>
358
+ ))}
359
+ </div>
360
+ </div>
361
+ </MenuContent>
362
+ }
363
+ menuProps={{
364
+ anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
365
+ transformOrigin: { vertical: 'top', horizontal: 'right' },
366
+ }}
367
+ >
368
+ <div className="execution-plan-viewer__panel__view-mode__type__label">
369
+ {executionPlanState.viewMode}
370
+ </div>
371
+ </DropdownMenu>
372
+ </div>
373
+ <div className="panel__content execution-plan-viewer__panel__content">
374
+ {executionPlanState.viewMode === EXECUTION_PLAN_VIEW_MODE.JSON &&
375
+ Boolean(displayData) && (
376
+ <CodeEditor
377
+ inputValue={displayData}
378
+ isReadOnly={true}
379
+ language={CODE_EDITOR_LANGUAGE.JSON}
380
+ showMiniMap={false}
381
+ />
382
+ )}
383
+ {executionPlanState.viewMode ===
384
+ EXECUTION_PLAN_VIEW_MODE.FORM && (
385
+ <>
386
+ {currentElement instanceof ExecutionNode && (
387
+ <ExecutionNodeViewer
388
+ executionNode={currentElement}
389
+ executionPlanState={executionPlanState}
390
+ />
391
+ )}
392
+ {currentElement instanceof ExecutionPlan && (
393
+ <BlankPanelContent>
394
+ <div className="execution-plan-viewer__unsupported-view">
395
+ <div className="execution-plan-viewer__unsupported-view__summary">
396
+ {`Can't display full execution plan`}
397
+ </div>
398
+ <button
399
+ className="btn--dark execution-plan-viewer__unsupported-view__to-text-mode__btn"
400
+ onClick={(): void =>
401
+ executionPlanState.setViewMode(
402
+ EXECUTION_PLAN_VIEW_MODE.JSON,
403
+ )
404
+ }
405
+ >
406
+ View JSON
407
+ </button>
408
+ </div>
409
+ </BlankPanelContent>
410
+ )}
411
+ </>
412
+ )}
413
+ </div>
414
+ </>
415
+ )}
416
+ </div>
417
+ );
418
+ },
419
+ );
420
+
421
+ const ExecutionPlanViewerContent = observer(
422
+ (props: {
423
+ executionPlanState: ExecutionPlanState;
424
+ rawPlan: RawExecutionPlan;
425
+ }) => {
426
+ const { executionPlanState, rawPlan } = props;
427
+ const plan = executionPlanState.plan;
428
+
429
+ return (
430
+ <div className="execution-plan-viewer__content">
431
+ {plan ? (
432
+ <ResizablePanelGroup orientation="vertical">
433
+ <ResizablePanel size={300} minSize={300}>
434
+ <div className="panel execution-plan-viewer__explorer">
435
+ <PanelSideBarHeader
436
+ darkMode={true}
437
+ title="execution plan explorer"
438
+ />
439
+ <div className="panel__content execution-plan-viewer__explorer__content__container">
440
+ <ExecutionPlanTree
441
+ executionPlanState={executionPlanState}
442
+ executionPlan={plan}
443
+ />
444
+ </div>
445
+ </div>
446
+ </ResizablePanel>
447
+ <ResizablePanelSplitter>
448
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
449
+ </ResizablePanelSplitter>
450
+ <ResizablePanel>
451
+ <ExecutionPlanViewPanel
452
+ displayData={executionPlanState.displayData}
453
+ executionPlanState={executionPlanState}
454
+ />
455
+ </ResizablePanel>
456
+ </ResizablePanelGroup>
457
+ ) : (
458
+ <CodeEditor
459
+ inputValue={JSON.stringify(rawPlan, undefined, TAB_SIZE)}
460
+ isReadOnly={true}
461
+ language={CODE_EDITOR_LANGUAGE.JSON}
462
+ showMiniMap={true}
463
+ />
464
+ )}
465
+ </div>
466
+ );
467
+ },
468
+ );
469
+
470
+ export const ExecutionPlanViewer = observer(
471
+ (props: { executionPlanState: ExecutionPlanState }) => {
472
+ const { executionPlanState } = props;
473
+ const closePlanViewer = (): void => {
474
+ executionPlanState.setRawPlan(undefined);
475
+ executionPlanState.setPlan(undefined);
476
+ executionPlanState.setExecutionPlanDisplayData('');
477
+ executionPlanState.setSelectedNode(undefined);
478
+ executionPlanState.setDebugText(undefined);
479
+ };
480
+ const rawPlan = executionPlanState.rawPlan;
481
+
482
+ if (!rawPlan) {
483
+ return null;
484
+ }
485
+ return (
486
+ <Dialog
487
+ open={Boolean(executionPlanState.rawPlan)}
488
+ onClose={closePlanViewer}
489
+ classes={{
490
+ root: 'editor-modal__root-container',
491
+ container: 'editor-modal__container',
492
+ paper: 'editor-modal__content',
493
+ }}
494
+ >
495
+ <Modal className="editor-modal" darkMode={true}>
496
+ <ModalHeader title="Execution Plan" />
497
+ <ModalBody>
498
+ {executionPlanState.debugText ? (
499
+ <ResizablePanelGroup orientation="horizontal">
500
+ <ResizablePanel minSize={100}>
501
+ <ExecutionPlanViewerContent
502
+ executionPlanState={executionPlanState}
503
+ rawPlan={rawPlan}
504
+ />
505
+ </ResizablePanel>
506
+ <ResizablePanelSplitter>
507
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
508
+ </ResizablePanelSplitter>
509
+ <ResizablePanel size={200} minSize={28}>
510
+ <div className="panel execution-plan-viewer__debug-panel">
511
+ <div className="panel__header">
512
+ <div className="panel__header__title">
513
+ <div className="panel__header__title__label">
514
+ DEBUG LOG
515
+ </div>
516
+ </div>
517
+ </div>
518
+ <PanelContent>
519
+ <CodeEditor
520
+ inputValue={executionPlanState.debugText}
521
+ isReadOnly={true}
522
+ language={CODE_EDITOR_LANGUAGE.TEXT}
523
+ showMiniMap={true}
524
+ />
525
+ </PanelContent>
526
+ </div>
527
+ </ResizablePanel>
528
+ </ResizablePanelGroup>
529
+ ) : (
530
+ <ExecutionPlanViewerContent
531
+ executionPlanState={executionPlanState}
532
+ rawPlan={rawPlan}
533
+ />
534
+ )}
535
+ </ModalBody>
536
+ <ModalFooter>
537
+ <ModalFooterButton onClick={closePlanViewer} text="Close" />
538
+ </ModalFooter>
539
+ </Modal>
540
+ </Dialog>
541
+ );
542
+ },
543
+ );
@@ -0,0 +1,46 @@
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 { observer } from 'mobx-react-lite';
18
+ import type { ExecutionPlanState } from '../../stores/execution-plan/ExecutionPlanState.js';
19
+ import { format as formatSQL } from 'sql-formatter';
20
+ import type { SQLResultColumn } from '@finos/legend-graph';
21
+ import { CodeEditor } from '@finos/legend-lego/code-editor';
22
+ import { CODE_EDITOR_LANGUAGE } from '@finos/legend-application';
23
+
24
+ /**
25
+ * TODO: Create a new `AbstractPlugin` for this, called `ExecutionPlanViewerPlugin`
26
+ * when we modularize relational and execution plan processing, etc.
27
+ *
28
+ * @modularize
29
+ * See https://github.com/finos/legend-studio/issues/65
30
+ */
31
+ export const SQLExecutionNodeViewer: React.FC<{
32
+ query: string;
33
+ resultColumns: SQLResultColumn[];
34
+ executionPlanState: ExecutionPlanState;
35
+ }> = observer((props) => {
36
+ const { query } = props;
37
+
38
+ return (
39
+ <CodeEditor
40
+ inputValue={formatSQL(query)}
41
+ isReadOnly={true}
42
+ language={CODE_EDITOR_LANGUAGE.SQL}
43
+ showMiniMap={false}
44
+ />
45
+ );
46
+ });
@@ -61,7 +61,7 @@ import {
61
61
  } from './QueryBuilderExplorerPanel.js';
62
62
  import { QueryBuilderPropertyInfoTooltip } from '../shared/QueryBuilderPropertyInfoTooltip.js';
63
63
  import { QUERY_BUILDER_TEST_ID } from '../../application/QueryBuilderTesting.js';
64
- import { TextSearchAdvancedConfigMenu } from '@finos/legend-application';
64
+ import { FuzzySearchAdvancedConfigMenu } from '@finos/legend-application/components';
65
65
 
66
66
  const prettyPropertyNameFromNodeId = (name: string): string => {
67
67
  let propNameArray = name.split('.');
@@ -387,7 +387,7 @@ export const QueryBuilderPropertySearchPanel = observer(
387
387
  horizontal: 'center',
388
388
  }}
389
389
  >
390
- <TextSearchAdvancedConfigMenu
390
+ <FuzzySearchAdvancedConfigMenu
391
391
  configState={propertySearchState.searchConfigurationState}
392
392
  />
393
393
  </BasePopover>
@@ -356,20 +356,18 @@ const QueryBuilderWindowColumnModalEditor = observer(
356
356
  elevation: 7,
357
357
  }}
358
358
  >
359
- <button
359
+ <div
360
360
  className="query-builder__olap__column__operation__operator__badge"
361
- tabIndex={-1}
362
361
  title="Choose Window Function Operator..."
363
362
  >
364
363
  <SigmaIcon />
365
- </button>
366
- <button
364
+ </div>
365
+ <div
367
366
  className="query-builder__olap__column__operation__operator__dropdown__trigger"
368
- tabIndex={-1}
369
367
  title="Choose Window Function Operator..."
370
368
  >
371
369
  <CaretDownIcon />
372
- </button>
370
+ </div>
373
371
  </DropdownMenu>
374
372
  </div>
375
373
  </div>
@@ -918,20 +916,18 @@ const QueryBuilderWindowColumnEditor = observer(
918
916
  elevation: 7,
919
917
  }}
920
918
  >
921
- <button
919
+ <div
922
920
  className="query-builder__olap__column__operation__operator__badge"
923
- tabIndex={-1}
924
921
  title="Choose Window Function Operator..."
925
922
  >
926
923
  <SigmaIcon />
927
- </button>
928
- <button
924
+ </div>
925
+ <div
929
926
  className="query-builder__olap__column__operation__operator__dropdown__trigger"
930
- tabIndex={-1}
931
927
  title="Choose Window Function Operator..."
932
928
  >
933
929
  <CaretDownIcon />
934
- </button>
930
+ </div>
935
931
  </DropdownMenu>
936
932
  </div>
937
933
  </div>
@@ -1058,7 +1054,7 @@ const QueryBuilderWindowColumnEditor = observer(
1058
1054
  elevation: 7,
1059
1055
  }}
1060
1056
  >
1061
- <button
1057
+ <div
1062
1058
  className={clsx(
1063
1059
  'query-builder__olap__column__sortby__operator__badge',
1064
1060
  {
@@ -1066,18 +1062,16 @@ const QueryBuilderWindowColumnEditor = observer(
1066
1062
  Boolean(sortByState),
1067
1063
  },
1068
1064
  )}
1069
- tabIndex={-1}
1070
1065
  title="Choose Window Function SortBy Operator..."
1071
1066
  >
1072
1067
  <SortIcon />
1073
- </button>
1074
- <button
1068
+ </div>
1069
+ <div
1075
1070
  className="query-builder__olap__column__sortby__operator__dropdown__trigger"
1076
- tabIndex={-1}
1077
1071
  title="Choose Window Function SortBy Operator..."
1078
1072
  >
1079
1073
  <CaretDownIcon />
1080
- </button>
1074
+ </div>
1081
1075
  </DropdownMenu>
1082
1076
  </div>
1083
1077
  </div>