@finos/legend-query-builder 2.1.2 → 2.1.4
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.
- package/{src/stores/QueryBuilderBootstraper.ts → lib/application/QueryBuilderDocumentation.d.ts} +6 -8
- package/lib/application/QueryBuilderDocumentation.d.ts.map +1 -0
- package/lib/application/QueryBuilderDocumentation.js +23 -0
- package/lib/application/QueryBuilderDocumentation.js.map +1 -0
- package/lib/components/QueryBuilder.js +3 -2
- package/lib/components/QueryBuilder.js.map +1 -1
- package/lib/components/QueryBuilderConstantExpressionPanel.d.ts.map +1 -1
- package/lib/components/QueryBuilderConstantExpressionPanel.js +3 -2
- package/lib/components/QueryBuilderConstantExpressionPanel.js.map +1 -1
- package/lib/components/QueryBuilderDiffPanel.d.ts.map +1 -1
- package/lib/components/QueryBuilderDiffPanel.js +9 -8
- package/lib/components/QueryBuilderDiffPanel.js.map +1 -1
- package/lib/components/QueryBuilderParametersPanel.js +3 -2
- package/lib/components/QueryBuilderParametersPanel.js.map +1 -1
- package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
- package/lib/components/QueryBuilderResultPanel.js +8 -7
- package/lib/components/QueryBuilderResultPanel.js.map +1 -1
- package/lib/components/QueryBuilderTextEditor.js +3 -2
- package/lib/components/QueryBuilderTextEditor.js.map +1 -1
- package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts +2 -1
- package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts.map +1 -1
- package/lib/components/QueryBuilder_LegendApplicationPlugin.js +8 -0
- package/lib/components/QueryBuilder_LegendApplicationPlugin.js.map +1 -1
- package/lib/{stores/QueryBuilderBootstraper.js → components/execution-plan/ExecutionPlanViewer.d.ts} +12 -6
- package/lib/components/execution-plan/ExecutionPlanViewer.d.ts.map +1 -0
- package/lib/components/execution-plan/ExecutionPlanViewer.js +182 -0
- package/lib/components/execution-plan/ExecutionPlanViewer.js.map +1 -0
- package/lib/components/execution-plan/SQLExecutionNodeViewer.d.ts +31 -0
- package/lib/components/execution-plan/SQLExecutionNodeViewer.d.ts.map +1 -0
- package/lib/components/execution-plan/SQLExecutionNodeViewer.js +32 -0
- package/lib/components/execution-plan/SQLExecutionNodeViewer.js.map +1 -0
- package/lib/components/explorer/QueryBuilderPropertySearchPanel.js +2 -2
- package/lib/components/explorer/QueryBuilderPropertySearchPanel.js.map +1 -1
- package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.d.ts.map +1 -1
- package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +4 -4
- package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
- package/lib/components/shared/BasicValueSpecificationEditor.d.ts.map +1 -1
- package/lib/components/shared/BasicValueSpecificationEditor.js +2 -3
- package/lib/components/shared/BasicValueSpecificationEditor.js.map +1 -1
- package/lib/components/shared/LambdaEditor.d.ts.map +1 -1
- package/lib/components/shared/LambdaEditor.js +20 -19
- package/lib/components/shared/LambdaEditor.js.map +1 -1
- package/lib/index.css +1 -17
- package/lib/index.css.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/lib/package.json +9 -17
- package/lib/stores/QueryBuilderResultState.d.ts +1 -1
- package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderResultState.js +2 -1
- package/lib/stores/QueryBuilderResultState.js.map +1 -1
- package/lib/stores/execution-plan/ExecutionPlanState.d.ts +61 -0
- package/lib/stores/execution-plan/ExecutionPlanState.d.ts.map +1 -0
- package/lib/stores/execution-plan/ExecutionPlanState.js +118 -0
- package/lib/stores/execution-plan/ExecutionPlanState.js.map +1 -0
- package/lib/stores/explorer/QueryBuilderPropertySearchState.d.ts +3 -4
- package/lib/stores/explorer/QueryBuilderPropertySearchState.d.ts.map +1 -1
- package/lib/stores/explorer/QueryBuilderPropertySearchState.js +3 -4
- package/lib/stores/explorer/QueryBuilderPropertySearchState.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
- package/package.json +16 -24
- package/{lib/stores/QueryBuilderBootstraper.d.ts → src/application/QueryBuilderDocumentation.ts} +8 -2
- package/src/components/QueryBuilder.tsx +2 -2
- package/src/components/QueryBuilderConstantExpressionPanel.tsx +2 -2
- package/src/components/QueryBuilderDiffPanel.tsx +16 -19
- package/src/components/QueryBuilderParametersPanel.tsx +2 -2
- package/src/components/QueryBuilderResultPanel.tsx +14 -14
- package/src/components/QueryBuilderTextEditor.tsx +4 -4
- package/src/components/QueryBuilder_LegendApplicationPlugin.ts +10 -0
- package/src/components/execution-plan/ExecutionPlanViewer.tsx +543 -0
- package/src/components/execution-plan/SQLExecutionNodeViewer.tsx +46 -0
- package/src/components/explorer/QueryBuilderPropertySearchPanel.tsx +2 -2
- package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +12 -18
- package/src/components/shared/BasicValueSpecificationEditor.tsx +2 -2
- package/src/components/shared/LambdaEditor.tsx +27 -25
- package/src/index.ts +3 -2
- package/src/stores/QueryBuilderResultState.ts +2 -1
- package/src/stores/execution-plan/ExecutionPlanState.ts +153 -0
- package/src/stores/explorer/QueryBuilderPropertySearchState.ts +4 -4
- package/src/stores/filter/QueryBuilderFilterState.ts +2 -0
- package/tsconfig.json +4 -1
- package/lib/stores/QueryBuilderBootstraper.d.ts.map +0 -1
- 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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
366
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
928
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
1074
|
-
<
|
|
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
|
-
</
|
|
1074
|
+
</div>
|
|
1081
1075
|
</DropdownMenu>
|
|
1082
1076
|
</div>
|
|
1083
1077
|
</div>
|