@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.
- package/lib/components/QueryBuilder.js +1 -1
- package/lib/components/QueryBuilder.js.map +1 -1
- package/lib/components/execution-plan/SQLExecutionNodeViewer.js +1 -1
- package/lib/components/execution-plan/SQLExecutionNodeViewer.js.map +1 -1
- package/lib/components/{QueryBuilderResultPanel.d.ts → result/QueryBuilderResultPanel.d.ts} +1 -2
- package/lib/components/result/QueryBuilderResultPanel.d.ts.map +1 -0
- package/lib/components/result/QueryBuilderResultPanel.js +185 -0
- package/lib/components/result/QueryBuilderResultPanel.js.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts +24 -0
- package/lib/components/result/tds/QueryBuilderTDSGridResult.d.ts.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSGridResult.js +204 -0
- package/lib/components/result/tds/QueryBuilderTDSGridResult.js.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSResultShared.d.ts +37 -0
- package/lib/components/result/tds/QueryBuilderTDSResultShared.d.ts.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSResultShared.js +251 -0
- package/lib/components/result/tds/QueryBuilderTDSResultShared.js.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.d.ts +24 -0
- package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.d.ts.map +1 -0
- package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js +224 -0
- package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/QueryBuilderResultState.d.ts +5 -1
- package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderResultState.js.map +1 -1
- package/package.json +3 -3
- package/src/components/QueryBuilder.tsx +1 -1
- package/src/components/execution-plan/SQLExecutionNodeViewer.tsx +1 -1
- package/src/components/result/QueryBuilderResultPanel.tsx +569 -0
- package/src/components/result/tds/QueryBuilderTDSGridResult.tsx +346 -0
- package/src/components/result/tds/QueryBuilderTDSResultShared.tsx +502 -0
- package/src/components/result/tds/QueryBuilderTDSSimpleGridResult.tsx +387 -0
- package/src/stores/QueryBuilderResultState.ts +12 -1
- package/tsconfig.json +4 -1
- package/lib/components/QueryBuilderResultPanel.d.ts.map +0 -1
- package/lib/components/QueryBuilderResultPanel.js +0 -633
- package/lib/components/QueryBuilderResultPanel.js.map +0 -1
- package/src/components/QueryBuilderResultPanel.tsx +0 -1412
@@ -0,0 +1,569 @@
|
|
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
|
+
BlankPanelContent,
|
19
|
+
PanelLoadingIndicator,
|
20
|
+
PlayIcon,
|
21
|
+
DropdownMenu,
|
22
|
+
MenuContent,
|
23
|
+
MenuContentItem,
|
24
|
+
CaretDownIcon,
|
25
|
+
clsx,
|
26
|
+
PauseCircleIcon,
|
27
|
+
ExclamationTriangleIcon,
|
28
|
+
PanelContent,
|
29
|
+
Button,
|
30
|
+
SQLIcon,
|
31
|
+
Dialog,
|
32
|
+
Modal,
|
33
|
+
ModalBody,
|
34
|
+
ModalFooter,
|
35
|
+
ModalFooterButton,
|
36
|
+
ModalHeader,
|
37
|
+
PanelDivider,
|
38
|
+
SquareIcon,
|
39
|
+
CheckSquareIcon,
|
40
|
+
} from '@finos/legend-art';
|
41
|
+
import { observer } from 'mobx-react-lite';
|
42
|
+
import { flowResult } from 'mobx';
|
43
|
+
import type { QueryBuilderState } from '../../stores/QueryBuilderState.js';
|
44
|
+
import {
|
45
|
+
type ExecutionResult,
|
46
|
+
extractExecutionResultValues,
|
47
|
+
TDSExecutionResult,
|
48
|
+
RawExecutionResult,
|
49
|
+
} from '@finos/legend-graph';
|
50
|
+
import {
|
51
|
+
ActionAlertActionType,
|
52
|
+
ActionAlertType,
|
53
|
+
DEFAULT_TAB_SIZE,
|
54
|
+
useApplicationStore,
|
55
|
+
} from '@finos/legend-application';
|
56
|
+
import { prettyDuration } from '@finos/legend-shared';
|
57
|
+
import { useRef, useState } from 'react';
|
58
|
+
import { QueryBuilderTDSState } from '../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
|
59
|
+
import { PARAMETER_SUBMIT_ACTION } from '../../stores/shared/LambdaParameterState.js';
|
60
|
+
import { QUERY_BUILDER_TEST_ID } from '../../__lib__/QueryBuilderTesting.js';
|
61
|
+
import {
|
62
|
+
CODE_EDITOR_LANGUAGE,
|
63
|
+
CodeEditor,
|
64
|
+
} from '@finos/legend-lego/code-editor';
|
65
|
+
import { ExecutionPlanViewer } from '../execution-plan/ExecutionPlanViewer.js';
|
66
|
+
import { QueryUsageViewer } from '../QueryUsageViewer.js';
|
67
|
+
import { DocumentationLink } from '@finos/legend-lego/application';
|
68
|
+
import { QUERY_BUILDER_DOCUMENTATION_KEY } from '../../__lib__/QueryBuilderDocumentation.js';
|
69
|
+
import { QueryBuilderTDSSimpleGridResult } from './tds/QueryBuilderTDSSimpleGridResult.js';
|
70
|
+
import { isEnterpriseModeEnabled } from '@finos/legend-lego/data-grid';
|
71
|
+
import {
|
72
|
+
getExecutedSqlFromExecutionResult,
|
73
|
+
tryToFormatSql,
|
74
|
+
} from './tds/QueryBuilderTDSResultShared.js';
|
75
|
+
import { QueryBuilderTDSGridResult } from './tds/QueryBuilderTDSGridResult.js';
|
76
|
+
|
77
|
+
const QueryBuilderResultValues = observer(
|
78
|
+
(props: {
|
79
|
+
executionResult: ExecutionResult;
|
80
|
+
queryBuilderState: QueryBuilderState;
|
81
|
+
}) => {
|
82
|
+
const { executionResult, queryBuilderState } = props;
|
83
|
+
if (executionResult instanceof TDSExecutionResult) {
|
84
|
+
if (isEnterpriseModeEnabled) {
|
85
|
+
return (
|
86
|
+
<QueryBuilderTDSGridResult
|
87
|
+
queryBuilderState={queryBuilderState}
|
88
|
+
executionResult={executionResult}
|
89
|
+
/>
|
90
|
+
);
|
91
|
+
} else {
|
92
|
+
return (
|
93
|
+
<QueryBuilderTDSSimpleGridResult
|
94
|
+
queryBuilderState={queryBuilderState}
|
95
|
+
executionResult={executionResult}
|
96
|
+
/>
|
97
|
+
);
|
98
|
+
}
|
99
|
+
} else if (executionResult instanceof RawExecutionResult) {
|
100
|
+
const inputValue =
|
101
|
+
executionResult.value === null
|
102
|
+
? 'null'
|
103
|
+
: executionResult.value.toString();
|
104
|
+
return (
|
105
|
+
<CodeEditor
|
106
|
+
language={CODE_EDITOR_LANGUAGE.TEXT}
|
107
|
+
inputValue={inputValue}
|
108
|
+
isReadOnly={true}
|
109
|
+
/>
|
110
|
+
);
|
111
|
+
}
|
112
|
+
return (
|
113
|
+
<CodeEditor
|
114
|
+
language={CODE_EDITOR_LANGUAGE.JSON}
|
115
|
+
inputValue={JSON.stringify(
|
116
|
+
extractExecutionResultValues(executionResult),
|
117
|
+
null,
|
118
|
+
DEFAULT_TAB_SIZE,
|
119
|
+
)}
|
120
|
+
isReadOnly={true}
|
121
|
+
/>
|
122
|
+
);
|
123
|
+
},
|
124
|
+
);
|
125
|
+
|
126
|
+
export const QueryBuilderResultPanel = observer(
|
127
|
+
(props: { queryBuilderState: QueryBuilderState }) => {
|
128
|
+
const { queryBuilderState } = props;
|
129
|
+
const applicationStore = useApplicationStore();
|
130
|
+
const resultState = queryBuilderState.resultState;
|
131
|
+
const queryParametersState = queryBuilderState.parametersState;
|
132
|
+
const executionResult = resultState.executionResult;
|
133
|
+
const [showSqlModal, setShowSqlModal] = useState(false);
|
134
|
+
const executedSql = executionResult
|
135
|
+
? getExecutedSqlFromExecutionResult(executionResult)
|
136
|
+
: undefined;
|
137
|
+
|
138
|
+
const fetchStructureImplementation =
|
139
|
+
queryBuilderState.fetchStructureState.implementation;
|
140
|
+
const USER_ATTESTATION_MESSAGE =
|
141
|
+
'I attest that I am aware of the sensitive data leakage risk when exporting queried data. The data I export will only be used by me.';
|
142
|
+
const exportQueryResults = async (format: string): Promise<void> => {
|
143
|
+
if (queryBuilderState.parametersState.parameterStates.length) {
|
144
|
+
queryParametersState.parameterValuesEditorState.open(
|
145
|
+
(): Promise<void> =>
|
146
|
+
flowResult(resultState.exportData(format)).catch(
|
147
|
+
applicationStore.alertUnhandledError,
|
148
|
+
),
|
149
|
+
PARAMETER_SUBMIT_ACTION.EXPORT,
|
150
|
+
);
|
151
|
+
} else {
|
152
|
+
await flowResult(resultState.exportData(format)).catch(
|
153
|
+
applicationStore.alertUnhandledError,
|
154
|
+
);
|
155
|
+
}
|
156
|
+
};
|
157
|
+
const confirmExport = (format: string): void => {
|
158
|
+
applicationStore.alertService.setActionAlertInfo({
|
159
|
+
message: USER_ATTESTATION_MESSAGE,
|
160
|
+
type: ActionAlertType.CAUTION,
|
161
|
+
actions: [
|
162
|
+
{
|
163
|
+
label: 'Accept',
|
164
|
+
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
|
165
|
+
handler: applicationStore.guardUnhandledError(() =>
|
166
|
+
exportQueryResults(format),
|
167
|
+
),
|
168
|
+
},
|
169
|
+
{
|
170
|
+
label: 'Decline',
|
171
|
+
type: ActionAlertActionType.PROCEED,
|
172
|
+
default: true,
|
173
|
+
},
|
174
|
+
],
|
175
|
+
});
|
176
|
+
};
|
177
|
+
|
178
|
+
const allValidationIssues = queryBuilderState.allValidationIssues;
|
179
|
+
|
180
|
+
const isSupportedQueryValid = allValidationIssues.length === 0;
|
181
|
+
|
182
|
+
const isQueryValid =
|
183
|
+
!queryBuilderState.isQuerySupported || isSupportedQueryValid;
|
184
|
+
|
185
|
+
const isQueryValidForAdvancedMode =
|
186
|
+
isQueryValid &&
|
187
|
+
queryBuilderState.fetchStructureState.implementation instanceof
|
188
|
+
QueryBuilderTDSState;
|
189
|
+
|
190
|
+
const runQuery = (): void => {
|
191
|
+
resultState.setSelectedCells([]);
|
192
|
+
resultState.pressedRunQuery.inProgress();
|
193
|
+
if (queryParametersState.parameterStates.length) {
|
194
|
+
queryParametersState.parameterValuesEditorState.open(
|
195
|
+
(): Promise<void> =>
|
196
|
+
flowResult(resultState.runQuery()).catch(
|
197
|
+
applicationStore.alertUnhandledError,
|
198
|
+
),
|
199
|
+
PARAMETER_SUBMIT_ACTION.RUN,
|
200
|
+
);
|
201
|
+
} else {
|
202
|
+
flowResult(resultState.runQuery()).catch(
|
203
|
+
applicationStore.alertUnhandledError,
|
204
|
+
);
|
205
|
+
}
|
206
|
+
resultState.pressedRunQuery.complete();
|
207
|
+
};
|
208
|
+
const cancelQuery = applicationStore.guardUnhandledError(() =>
|
209
|
+
flowResult(resultState.cancelQuery()),
|
210
|
+
);
|
211
|
+
|
212
|
+
const generatePlan = applicationStore.guardUnhandledError(() =>
|
213
|
+
flowResult(resultState.generatePlan(false)),
|
214
|
+
);
|
215
|
+
const debugPlanGeneration = applicationStore.guardUnhandledError(() =>
|
216
|
+
flowResult(resultState.generatePlan(true)),
|
217
|
+
);
|
218
|
+
|
219
|
+
const allowSettingPreviewLimit = queryBuilderState.isQuerySupported;
|
220
|
+
|
221
|
+
const allowSettingAdvancedMode =
|
222
|
+
queryBuilderState.isQuerySupported && isEnterpriseModeEnabled;
|
223
|
+
|
224
|
+
const copyExpression = (value: string): void => {
|
225
|
+
applicationStore.clipboardService
|
226
|
+
.copyTextToClipboard(value)
|
227
|
+
.then(() =>
|
228
|
+
applicationStore.notificationService.notifySuccess(
|
229
|
+
'SQL Query copied',
|
230
|
+
undefined,
|
231
|
+
2500,
|
232
|
+
),
|
233
|
+
)
|
234
|
+
.catch(applicationStore.alertUnhandledError);
|
235
|
+
};
|
236
|
+
|
237
|
+
const isRunQueryDisabled =
|
238
|
+
!isQueryValid ||
|
239
|
+
resultState.isGeneratingPlan ||
|
240
|
+
resultState.pressedRunQuery.isInProgress;
|
241
|
+
|
242
|
+
const getResultSetDescription = (
|
243
|
+
_executionResult: ExecutionResult,
|
244
|
+
): string | undefined => {
|
245
|
+
const queryDuration = resultState.executionDuration
|
246
|
+
? prettyDuration(resultState.executionDuration, {
|
247
|
+
ms: true,
|
248
|
+
})
|
249
|
+
: undefined;
|
250
|
+
if (_executionResult instanceof TDSExecutionResult) {
|
251
|
+
const rowLength = _executionResult.result.rows.length;
|
252
|
+
return `${rowLength} row(s)${
|
253
|
+
queryDuration ? ` in ${queryDuration}` : ''
|
254
|
+
}`;
|
255
|
+
}
|
256
|
+
if (!queryDuration) {
|
257
|
+
return undefined;
|
258
|
+
}
|
259
|
+
return `query ran in ${queryDuration}`;
|
260
|
+
};
|
261
|
+
const resultDescription = executionResult
|
262
|
+
? getResultSetDescription(executionResult)
|
263
|
+
: undefined;
|
264
|
+
|
265
|
+
const [previewLimitValue, setPreviewLimitValue] = useState(
|
266
|
+
resultState.previewLimit,
|
267
|
+
);
|
268
|
+
|
269
|
+
const changePreviewLimit: React.ChangeEventHandler<HTMLInputElement> = (
|
270
|
+
event,
|
271
|
+
) => {
|
272
|
+
setPreviewLimitValue(parseInt(event.target.value, 10));
|
273
|
+
};
|
274
|
+
|
275
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
276
|
+
|
277
|
+
const getPreviewLimit = (): void => {
|
278
|
+
if (isNaN(previewLimitValue) || previewLimitValue === 0) {
|
279
|
+
setPreviewLimitValue(1);
|
280
|
+
queryBuilderState.resultState.setPreviewLimit(1);
|
281
|
+
} else {
|
282
|
+
queryBuilderState.resultState.setPreviewLimit(previewLimitValue);
|
283
|
+
}
|
284
|
+
};
|
285
|
+
|
286
|
+
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
287
|
+
if (event.code === 'Enter') {
|
288
|
+
getPreviewLimit();
|
289
|
+
inputRef.current?.focus();
|
290
|
+
} else if (event.code === 'Escape') {
|
291
|
+
inputRef.current?.select();
|
292
|
+
}
|
293
|
+
};
|
294
|
+
|
295
|
+
const toggleIsAdvancedModeEnabled = (): void => {
|
296
|
+
resultState.setExecutionResult(undefined);
|
297
|
+
queryBuilderState.setIsAdvancedModeEnabled(
|
298
|
+
!queryBuilderState.isAdvancedModeEnabled,
|
299
|
+
);
|
300
|
+
};
|
301
|
+
|
302
|
+
return (
|
303
|
+
<div
|
304
|
+
data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_PANEL}
|
305
|
+
className="panel query-builder__result"
|
306
|
+
>
|
307
|
+
{showSqlModal && executedSql && (
|
308
|
+
<Dialog
|
309
|
+
open={Boolean(showSqlModal)}
|
310
|
+
onClose={() => setShowSqlModal(false)}
|
311
|
+
>
|
312
|
+
<Modal className="editor-modal" darkMode={true}>
|
313
|
+
<ModalHeader title="Executed SQL Query" />
|
314
|
+
<ModalBody className="query-builder__sql__modal">
|
315
|
+
<>
|
316
|
+
<CodeEditor
|
317
|
+
inputValue={tryToFormatSql(executedSql)}
|
318
|
+
isReadOnly={true}
|
319
|
+
language={CODE_EDITOR_LANGUAGE.SQL}
|
320
|
+
hideMinimap={true}
|
321
|
+
/>
|
322
|
+
|
323
|
+
<PanelDivider />
|
324
|
+
</>
|
325
|
+
</ModalBody>
|
326
|
+
<ModalFooter>
|
327
|
+
<ModalFooterButton
|
328
|
+
formatText={false}
|
329
|
+
onClick={() => copyExpression(executedSql)}
|
330
|
+
text="Copy SQL to Clipboard"
|
331
|
+
/>
|
332
|
+
|
333
|
+
<ModalFooterButton
|
334
|
+
onClick={() => setShowSqlModal(false)}
|
335
|
+
text="Close"
|
336
|
+
/>
|
337
|
+
</ModalFooter>
|
338
|
+
</Modal>
|
339
|
+
</Dialog>
|
340
|
+
)}
|
341
|
+
|
342
|
+
<div className="panel__header">
|
343
|
+
<div className="panel__header__title">
|
344
|
+
<div className="panel__header__title__label">result</div>
|
345
|
+
{executedSql && (
|
346
|
+
<Button
|
347
|
+
onClick={() => setShowSqlModal(true)}
|
348
|
+
title="Executed SQL"
|
349
|
+
className="query-builder__result__sql__actions"
|
350
|
+
>
|
351
|
+
<SQLIcon />
|
352
|
+
</Button>
|
353
|
+
)}
|
354
|
+
{resultState.pressedRunQuery.isInProgress && (
|
355
|
+
<div className="panel__header__title__label__status">
|
356
|
+
Running Query...
|
357
|
+
</div>
|
358
|
+
)}
|
359
|
+
|
360
|
+
<div
|
361
|
+
data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_ANALYTICS}
|
362
|
+
className="query-builder__result__analytics"
|
363
|
+
>
|
364
|
+
{resultDescription ?? ''}
|
365
|
+
</div>
|
366
|
+
{executionResult && resultState.checkForStaleResults && (
|
367
|
+
<div className="query-builder__result__stale-status">
|
368
|
+
<div className="query-builder__result__stale-status__icon">
|
369
|
+
<ExclamationTriangleIcon />
|
370
|
+
</div>
|
371
|
+
<div className="query-builder__result__stale-status__label">
|
372
|
+
Preview data might be stale
|
373
|
+
</div>
|
374
|
+
</div>
|
375
|
+
)}
|
376
|
+
</div>
|
377
|
+
<div className="panel__header__actions query-builder__result__header__actions">
|
378
|
+
{allowSettingAdvancedMode && (
|
379
|
+
<div className="query-builder__result__advanced__mode">
|
380
|
+
<div className="query-builder__result__advanced__mode__label">
|
381
|
+
Advanced Mode
|
382
|
+
<DocumentationLink
|
383
|
+
title="The grid in advanced mode performs all operations like grouping, sorting, filtering, etc after initial query execution locally withought reaching out to server. This limits the number of rows to smaller number so they can fit in memory"
|
384
|
+
documentationKey={
|
385
|
+
QUERY_BUILDER_DOCUMENTATION_KEY.QUESTION_HOW_TO_USE_ADVANCED_GRID_MODE
|
386
|
+
}
|
387
|
+
/>
|
388
|
+
</div>
|
389
|
+
<button
|
390
|
+
className={clsx(
|
391
|
+
'query-builder__result__advanced__mode__toggler__btn',
|
392
|
+
{
|
393
|
+
'query-builder__result__advanced__mode__toggler__btn--toggled':
|
394
|
+
queryBuilderState.isAdvancedModeEnabled,
|
395
|
+
},
|
396
|
+
)}
|
397
|
+
disabled={!isQueryValidForAdvancedMode}
|
398
|
+
onClick={toggleIsAdvancedModeEnabled}
|
399
|
+
tabIndex={-1}
|
400
|
+
>
|
401
|
+
{queryBuilderState.isAdvancedModeEnabled ? (
|
402
|
+
<CheckSquareIcon />
|
403
|
+
) : (
|
404
|
+
<SquareIcon />
|
405
|
+
)}
|
406
|
+
</button>
|
407
|
+
</div>
|
408
|
+
)}
|
409
|
+
|
410
|
+
{allowSettingPreviewLimit && (
|
411
|
+
<div className="query-builder__result__limit">
|
412
|
+
<div className="query-builder__result__limit__label">
|
413
|
+
preview limit
|
414
|
+
</div>
|
415
|
+
<input
|
416
|
+
ref={inputRef}
|
417
|
+
className="input--dark query-builder__result__limit__input"
|
418
|
+
spellCheck={false}
|
419
|
+
type="number"
|
420
|
+
value={previewLimitValue}
|
421
|
+
onChange={changePreviewLimit}
|
422
|
+
onBlur={getPreviewLimit}
|
423
|
+
onKeyDown={onKeyDown}
|
424
|
+
disabled={!isQueryValid}
|
425
|
+
/>
|
426
|
+
</div>
|
427
|
+
)}
|
428
|
+
|
429
|
+
<div className="query-builder__result__execute-btn btn__dropdown-combo btn__dropdown-combo--primary">
|
430
|
+
{resultState.isRunningQuery ? (
|
431
|
+
<button
|
432
|
+
className="btn__dropdown-combo__canceler"
|
433
|
+
onClick={cancelQuery}
|
434
|
+
tabIndex={-1}
|
435
|
+
disabled={!isQueryValid}
|
436
|
+
>
|
437
|
+
<div className="btn--dark btn--caution btn__dropdown-combo__canceler__label">
|
438
|
+
<PauseCircleIcon className="btn__dropdown-combo__canceler__label__icon" />
|
439
|
+
<div className="btn__dropdown-combo__canceler__label__title">
|
440
|
+
Stop
|
441
|
+
</div>
|
442
|
+
</div>
|
443
|
+
</button>
|
444
|
+
) : (
|
445
|
+
<>
|
446
|
+
<button
|
447
|
+
className="btn__dropdown-combo__label"
|
448
|
+
onClick={runQuery}
|
449
|
+
tabIndex={-1}
|
450
|
+
title={
|
451
|
+
allValidationIssues.length
|
452
|
+
? `Query is not valid:\n${allValidationIssues
|
453
|
+
.map((issue) => `\u2022 ${issue}`)
|
454
|
+
.join('\n')}`
|
455
|
+
: undefined
|
456
|
+
}
|
457
|
+
disabled={isRunQueryDisabled}
|
458
|
+
>
|
459
|
+
<PlayIcon className="btn__dropdown-combo__label__icon" />
|
460
|
+
<div className="btn__dropdown-combo__label__title">
|
461
|
+
Run Query
|
462
|
+
</div>
|
463
|
+
</button>
|
464
|
+
<DropdownMenu
|
465
|
+
className="btn__dropdown-combo__dropdown-btn"
|
466
|
+
disabled={isRunQueryDisabled}
|
467
|
+
content={
|
468
|
+
<MenuContent>
|
469
|
+
<MenuContentItem
|
470
|
+
className="btn__dropdown-combo__option"
|
471
|
+
onClick={generatePlan}
|
472
|
+
disabled={isRunQueryDisabled}
|
473
|
+
>
|
474
|
+
Generate Plan
|
475
|
+
</MenuContentItem>
|
476
|
+
<MenuContentItem
|
477
|
+
className="btn__dropdown-combo__option"
|
478
|
+
onClick={debugPlanGeneration}
|
479
|
+
disabled={isRunQueryDisabled}
|
480
|
+
>
|
481
|
+
Debug
|
482
|
+
</MenuContentItem>
|
483
|
+
</MenuContent>
|
484
|
+
}
|
485
|
+
menuProps={{
|
486
|
+
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
|
487
|
+
transformOrigin: { vertical: 'top', horizontal: 'right' },
|
488
|
+
}}
|
489
|
+
>
|
490
|
+
<CaretDownIcon />
|
491
|
+
</DropdownMenu>
|
492
|
+
</>
|
493
|
+
)}
|
494
|
+
</div>
|
495
|
+
<DropdownMenu
|
496
|
+
className="query-builder__result__export__dropdown"
|
497
|
+
title="Export"
|
498
|
+
disabled={!isQueryValid}
|
499
|
+
content={
|
500
|
+
<MenuContent>
|
501
|
+
{Object.values(
|
502
|
+
fetchStructureImplementation.exportDataFormatOptions,
|
503
|
+
).map((format) => (
|
504
|
+
<MenuContentItem
|
505
|
+
key={format}
|
506
|
+
className="query-builder__result__export__dropdown__menu__item"
|
507
|
+
onClick={(): void => confirmExport(format)}
|
508
|
+
>
|
509
|
+
{format}
|
510
|
+
</MenuContentItem>
|
511
|
+
))}
|
512
|
+
<MenuContentItem
|
513
|
+
className="query-builder__result__export__dropdown__menu__item"
|
514
|
+
onClick={(): void =>
|
515
|
+
resultState.setIsQueryUsageViewerOpened(true)
|
516
|
+
}
|
517
|
+
disabled={queryBuilderState.changeDetectionState.hasChanged}
|
518
|
+
>
|
519
|
+
View Query Usage...
|
520
|
+
</MenuContentItem>
|
521
|
+
</MenuContent>
|
522
|
+
}
|
523
|
+
menuProps={{
|
524
|
+
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
|
525
|
+
transformOrigin: { vertical: 'top', horizontal: 'right' },
|
526
|
+
elevation: 7,
|
527
|
+
}}
|
528
|
+
>
|
529
|
+
<div className="query-builder__result__export__dropdown__label">
|
530
|
+
Export
|
531
|
+
</div>
|
532
|
+
<div className="query-builder__result__export__dropdown__trigger">
|
533
|
+
<CaretDownIcon />
|
534
|
+
</div>
|
535
|
+
</DropdownMenu>
|
536
|
+
{resultState.isQueryUsageViewerOpened && (
|
537
|
+
<QueryUsageViewer resultState={resultState} />
|
538
|
+
)}
|
539
|
+
</div>
|
540
|
+
</div>
|
541
|
+
<PanelContent>
|
542
|
+
<PanelLoadingIndicator
|
543
|
+
isLoading={
|
544
|
+
resultState.isRunningQuery ||
|
545
|
+
resultState.isGeneratingPlan ||
|
546
|
+
resultState.exportDataState.isInProgress
|
547
|
+
}
|
548
|
+
/>
|
549
|
+
{!executionResult && (
|
550
|
+
<BlankPanelContent>
|
551
|
+
Build or load a valid query first
|
552
|
+
</BlankPanelContent>
|
553
|
+
)}
|
554
|
+
{executionResult && (
|
555
|
+
<div className="query-builder__result__values">
|
556
|
+
<QueryBuilderResultValues
|
557
|
+
executionResult={executionResult}
|
558
|
+
queryBuilderState={queryBuilderState}
|
559
|
+
/>
|
560
|
+
</div>
|
561
|
+
)}
|
562
|
+
</PanelContent>
|
563
|
+
<ExecutionPlanViewer
|
564
|
+
executionPlanState={resultState.executionPlanState}
|
565
|
+
/>
|
566
|
+
</div>
|
567
|
+
);
|
568
|
+
},
|
569
|
+
);
|