ydb-embedded-ui 3.5.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +34 -0
- package/dist/components/ClusterInfo/ClusterInfo.tsx +3 -3
- package/dist/{containers/Nodes/NodesTable.scss → components/NodeHostWrapper/NodeHostWrapper.scss} +4 -6
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +60 -0
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -11
- package/dist/containers/Header/Header.tsx +1 -1
- package/dist/containers/Nodes/getNodesColumns.tsx +7 -46
- package/dist/containers/Storage/StorageNodes/StorageNodes.scss +0 -24
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +2 -39
- package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -3
- package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.scss +8 -0
- package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.tsx +21 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +58 -83
- package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -33
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +83 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.scss +57 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +84 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +23 -0
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +12 -23
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +4 -6
- package/dist/containers/Tenant/QueryEditor/i18n/en.json +3 -0
- package/dist/containers/Tenant/QueryEditor/i18n/index.ts +11 -0
- package/dist/containers/Tenant/QueryEditor/i18n/ru.json +3 -0
- package/dist/containers/Tenants/Tenants.js +1 -1
- package/dist/containers/UserSettings/UserSettings.tsx +30 -1
- package/dist/services/api.ts +383 -0
- package/dist/store/reducers/{cluster.js → cluster/cluster.ts} +9 -14
- package/dist/store/reducers/cluster/types.ts +13 -0
- package/dist/store/reducers/executeQuery.ts +12 -37
- package/dist/store/reducers/executeTopQueries.ts +2 -2
- package/dist/store/reducers/{explainQuery.js → explainQuery.ts} +44 -59
- package/dist/store/reducers/index.ts +5 -4
- package/dist/store/reducers/settings.js +19 -17
- package/dist/store/reducers/{tenants.js → tenants/tenants.ts} +14 -9
- package/dist/store/reducers/tenants/types.ts +17 -0
- package/dist/store/utils.ts +3 -2
- package/dist/types/api/acl.ts +25 -0
- package/dist/types/api/cluster.ts +3 -0
- package/dist/types/api/compute.ts +5 -3
- package/dist/types/api/error.ts +14 -0
- package/dist/types/api/netInfo.ts +48 -0
- package/dist/types/api/nodes.ts +5 -3
- package/dist/types/api/pdisk.ts +11 -2
- package/dist/types/api/query.ts +226 -117
- package/dist/types/api/storage.ts +5 -3
- package/dist/types/api/tenant.ts +18 -3
- package/dist/types/api/vdisk.ts +10 -2
- package/dist/types/api/whoami.ts +19 -0
- package/dist/types/store/executeQuery.ts +4 -8
- package/dist/types/store/explainQuery.ts +38 -0
- package/dist/types/store/query.ts +23 -3
- package/dist/types/window.d.ts +5 -0
- package/dist/utils/constants.ts +2 -1
- package/dist/utils/error.ts +25 -0
- package/dist/utils/hooks/useTypedSelector.ts +2 -2
- package/dist/utils/index.js +0 -49
- package/dist/utils/nodes.ts +3 -1
- package/dist/utils/prepareQueryExplain.ts +7 -24
- package/dist/utils/query.test.ts +153 -231
- package/dist/utils/query.ts +44 -78
- package/dist/utils/timeParsers/i18n/en.json +9 -9
- package/dist/utils/timeParsers/i18n/ru.json +9 -9
- package/dist/utils/timeParsers/parsers.ts +9 -0
- package/dist/utils/utils.js +1 -2
- package/package.json +1 -1
- package/dist/services/api.d.ts +0 -86
- package/dist/services/api.js +0 -278
@@ -4,41 +4,36 @@ import {connect} from 'react-redux';
|
|
4
4
|
import cn from 'bem-cn-lite';
|
5
5
|
import _ from 'lodash';
|
6
6
|
import MonacoEditor from 'react-monaco-editor';
|
7
|
-
import {Button, DropdownMenu} from '@gravity-ui/uikit';
|
8
7
|
|
9
8
|
import SplitPane from '../../../components/SplitPane';
|
10
9
|
import {QueryResultTable} from '../../../components/QueryResultTable';
|
11
10
|
|
12
|
-
import SaveQuery from './SaveQuery/SaveQuery';
|
13
11
|
import SavedQueries from './SavedQueries/SavedQueries';
|
14
|
-
import {Icon} from '../../../components/Icon';
|
15
12
|
import QueryResult from './QueryResult/QueryResult';
|
16
13
|
import QueryExplain from './QueryExplain/QueryExplain';
|
14
|
+
import {QueryEditorControls} from './QueryEditorControls/QueryEditorControls';
|
15
|
+
import {OldQueryEditorControls} from './QueryEditorControls/OldQueryEditorControls';
|
17
16
|
|
18
17
|
import {
|
19
|
-
|
18
|
+
sendExecuteQuery,
|
20
19
|
changeUserInput,
|
21
20
|
saveQueryToHistory,
|
22
21
|
goToPreviousQuery,
|
23
22
|
goToNextQuery,
|
24
|
-
selectRunAction,
|
25
|
-
RUN_ACTIONS_VALUES,
|
26
23
|
MONACO_HOT_KEY_ACTIONS,
|
27
24
|
setMonacoHotKey,
|
28
25
|
} from '../../../store/reducers/executeQuery';
|
29
26
|
import {getExplainQuery, getExplainQueryAst} from '../../../store/reducers/explainQuery';
|
30
|
-
import {
|
27
|
+
import {getParsedSettingValue, setSettingValue} from '../../../store/reducers/settings';
|
31
28
|
import {
|
32
29
|
DEFAULT_IS_QUERY_RESULT_COLLAPSED,
|
33
30
|
DEFAULT_SIZE_RESULT_PANE_KEY,
|
34
31
|
SAVED_QUERIES_KEY,
|
35
|
-
|
32
|
+
QUERY_INITIAL_MODE_KEY,
|
33
|
+
ENABLE_QUERY_MODES_FOR_EXPLAIN,
|
36
34
|
} from '../../../utils/constants';
|
37
35
|
|
38
|
-
import {parseJson} from '../../../utils/utils';
|
39
|
-
|
40
36
|
import './QueryEditor.scss';
|
41
|
-
import Divider from '../../../components/Divider/Divider';
|
42
37
|
import QueriesHistory from './QueriesHistory/QueriesHistory';
|
43
38
|
import {
|
44
39
|
PaneVisibilityActionTypes,
|
@@ -47,11 +42,6 @@ import {
|
|
47
42
|
import Preview from '../Preview/Preview';
|
48
43
|
import {setShowPreview} from '../../../store/reducers/schema';
|
49
44
|
|
50
|
-
export const RUN_ACTIONS = [
|
51
|
-
{value: RUN_ACTIONS_VALUES.script, content: 'Run Script'},
|
52
|
-
{value: RUN_ACTIONS_VALUES.scan, content: 'Run Scan'},
|
53
|
-
];
|
54
|
-
|
55
45
|
const TABLE_SETTINGS = {
|
56
46
|
sortable: false,
|
57
47
|
};
|
@@ -73,7 +63,7 @@ const RESULT_TYPES = {
|
|
73
63
|
const b = cn('query-editor');
|
74
64
|
|
75
65
|
const propTypes = {
|
76
|
-
|
66
|
+
sendExecuteQuery: PropTypes.func,
|
77
67
|
path: PropTypes.string,
|
78
68
|
response: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
|
79
69
|
executeQuery: PropTypes.object,
|
@@ -81,6 +71,7 @@ const propTypes = {
|
|
81
71
|
setMonacoHotKey: PropTypes.func,
|
82
72
|
theme: PropTypes.string,
|
83
73
|
type: PropTypes.string,
|
74
|
+
initialQueryMode: PropTypes.string,
|
84
75
|
};
|
85
76
|
|
86
77
|
const initialTenantCommonInfoState = {
|
@@ -92,6 +83,7 @@ function QueryEditor(props) {
|
|
92
83
|
const [resultType, setResultType] = useState(RESULT_TYPES.EXECUTE);
|
93
84
|
|
94
85
|
const [isResultLoaded, setIsResultLoaded] = useState(false);
|
86
|
+
const [queryMode, setQueryMode] = useState(props.initialQueryMode);
|
95
87
|
|
96
88
|
const [resultVisibilityState, dispatchResultVisibilityState] = useReducer(
|
97
89
|
paneVisibilityToggleReducerCreator(DEFAULT_IS_QUERY_RESULT_COLLAPSED),
|
@@ -155,7 +147,7 @@ function QueryEditor(props) {
|
|
155
147
|
setMonacoHotKey(null);
|
156
148
|
switch (monacoHotKey) {
|
157
149
|
case MONACO_HOT_KEY_ACTIONS.sendQuery: {
|
158
|
-
return
|
150
|
+
return handleSendExecuteClick(queryMode);
|
159
151
|
}
|
160
152
|
case MONACO_HOT_KEY_ACTIONS.goPrev: {
|
161
153
|
return handlePreviousHistoryClick();
|
@@ -246,17 +238,17 @@ function QueryEditor(props) {
|
|
246
238
|
props.changeUserInput({input: newValue});
|
247
239
|
};
|
248
240
|
|
249
|
-
const
|
241
|
+
const handleSendExecuteClick = (mode) => {
|
250
242
|
const {
|
251
243
|
path,
|
252
|
-
executeQuery: {input, history
|
253
|
-
|
244
|
+
executeQuery: {input, history},
|
245
|
+
sendExecuteQuery,
|
254
246
|
saveQueryToHistory,
|
255
247
|
setShowPreview,
|
256
248
|
} = props;
|
257
249
|
|
258
250
|
setResultType(RESULT_TYPES.EXECUTE);
|
259
|
-
|
251
|
+
sendExecuteQuery({query: input, database: path, mode});
|
260
252
|
setIsResultLoaded(true);
|
261
253
|
setShowPreview(false);
|
262
254
|
|
@@ -267,15 +259,20 @@ function QueryEditor(props) {
|
|
267
259
|
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
|
268
260
|
};
|
269
261
|
|
270
|
-
const handleGetExplainQueryClick = () => {
|
262
|
+
const handleGetExplainQueryClick = (mode) => {
|
271
263
|
const {
|
272
264
|
path,
|
273
265
|
executeQuery: {input},
|
274
266
|
getExplainQuery,
|
275
267
|
setShowPreview,
|
276
268
|
} = props;
|
269
|
+
|
277
270
|
setResultType(RESULT_TYPES.EXPLAIN);
|
278
|
-
getExplainQuery({
|
271
|
+
getExplainQuery({
|
272
|
+
query: input,
|
273
|
+
database: path,
|
274
|
+
mode: mode,
|
275
|
+
});
|
279
276
|
setIsResultLoaded(true);
|
280
277
|
setShowPreview(false);
|
281
278
|
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
|
@@ -513,65 +510,42 @@ function QueryEditor(props) {
|
|
513
510
|
setSettingValue(SAVED_QUERIES_KEY, JSON.stringify(newSavedQueries));
|
514
511
|
};
|
515
512
|
|
516
|
-
const
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
const runText = _.find(RUN_ACTIONS, {value: runAction}).content;
|
521
|
-
|
522
|
-
const menuItems = RUN_ACTIONS.map((action) => {
|
523
|
-
return {
|
524
|
-
text: action.content,
|
525
|
-
action: () => {
|
526
|
-
selectRunAction(action.value);
|
527
|
-
setSettingValue(QUERY_INITIAL_RUN_ACTION_KEY, action.value);
|
528
|
-
},
|
529
|
-
};
|
530
|
-
});
|
513
|
+
const onUpdateQueryMode = (mode) => {
|
514
|
+
setQueryMode(mode);
|
515
|
+
props.setSettingValue(QUERY_INITIAL_MODE_KEY, mode);
|
516
|
+
};
|
531
517
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
{runText}
|
544
|
-
</Button>
|
545
|
-
<DropdownMenu
|
546
|
-
items={menuItems}
|
547
|
-
popupClassName={b('select-query-action-popup')}
|
548
|
-
switcher={
|
549
|
-
<Button
|
550
|
-
view="action"
|
551
|
-
pin="brick-round"
|
552
|
-
disabled={runIsDisabled}
|
553
|
-
loading={executeQuery.loading}
|
554
|
-
className={b('select-query-action')}
|
555
|
-
>
|
556
|
-
<Icon name="chevron-down" width={16} height={16} />
|
557
|
-
</Button>
|
558
|
-
}
|
559
|
-
/>
|
560
|
-
</div>
|
561
|
-
<Button
|
562
|
-
onClick={handleGetExplainQueryClick}
|
563
|
-
disabled={!executeQuery.input}
|
564
|
-
loading={explainQuery.loading}
|
565
|
-
>
|
566
|
-
Explain
|
567
|
-
</Button>
|
568
|
-
<Divider />
|
569
|
-
<SaveQuery
|
518
|
+
const renderControls = () => {
|
519
|
+
const {executeQuery, explainQuery, savedQueries, enableQueryModesForExplain} = props;
|
520
|
+
|
521
|
+
if (enableQueryModesForExplain) {
|
522
|
+
return (
|
523
|
+
<QueryEditorControls
|
524
|
+
onRunButtonClick={handleSendExecuteClick}
|
525
|
+
runIsLoading={executeQuery.loading}
|
526
|
+
onExplainButtonClick={handleGetExplainQueryClick}
|
527
|
+
explainIsLoading={explainQuery.loading}
|
528
|
+
onSaveQueryClick={onSaveQueryHandler}
|
570
529
|
savedQueries={savedQueries}
|
571
|
-
|
572
|
-
|
530
|
+
disabled={!executeQuery.input}
|
531
|
+
onUpdateQueryMode={onUpdateQueryMode}
|
532
|
+
queryMode={queryMode}
|
573
533
|
/>
|
574
|
-
|
534
|
+
);
|
535
|
+
}
|
536
|
+
|
537
|
+
return (
|
538
|
+
<OldQueryEditorControls
|
539
|
+
onRunButtonClick={handleSendExecuteClick}
|
540
|
+
runIsLoading={executeQuery.loading}
|
541
|
+
onExplainButtonClick={handleGetExplainQueryClick}
|
542
|
+
explainIsLoading={explainQuery.loading}
|
543
|
+
onSaveQueryClick={onSaveQueryHandler}
|
544
|
+
savedQueries={savedQueries}
|
545
|
+
disabled={!executeQuery.input}
|
546
|
+
onUpdateQueryMode={onUpdateQueryMode}
|
547
|
+
queryMode={queryMode}
|
548
|
+
/>
|
575
549
|
);
|
576
550
|
};
|
577
551
|
|
@@ -632,7 +606,9 @@ const mapStateToProps = (state) => {
|
|
632
606
|
return {
|
633
607
|
executeQuery: state.executeQuery,
|
634
608
|
explainQuery: state.explainQuery,
|
635
|
-
savedQueries:
|
609
|
+
savedQueries: getParsedSettingValue(state, SAVED_QUERIES_KEY),
|
610
|
+
initialQueryMode: getParsedSettingValue(state, QUERY_INITIAL_MODE_KEY),
|
611
|
+
enableQueryModesForExplain: getParsedSettingValue(state, ENABLE_QUERY_MODES_FOR_EXPLAIN),
|
636
612
|
showPreview: state.schema.showPreview,
|
637
613
|
currentSchema: state.schema.currentSchema,
|
638
614
|
monacoHotKey: state.executeQuery?.monacoHotKey,
|
@@ -640,7 +616,7 @@ const mapStateToProps = (state) => {
|
|
640
616
|
};
|
641
617
|
|
642
618
|
const mapDispatchToProps = {
|
643
|
-
|
619
|
+
sendExecuteQuery,
|
644
620
|
changeUserInput,
|
645
621
|
saveQueryToHistory,
|
646
622
|
goToPreviousQuery,
|
@@ -648,7 +624,6 @@ const mapDispatchToProps = {
|
|
648
624
|
getExplainQuery,
|
649
625
|
getExplainQueryAst,
|
650
626
|
setSettingValue,
|
651
|
-
selectRunAction,
|
652
627
|
setShowPreview,
|
653
628
|
setMonacoHotKey,
|
654
629
|
};
|
@@ -54,35 +54,6 @@
|
|
54
54
|
align-items: center;
|
55
55
|
}
|
56
56
|
|
57
|
-
&__controls {
|
58
|
-
display: flex;
|
59
|
-
flex: 0 0 40px;
|
60
|
-
align-items: flex-end;
|
61
|
-
|
62
|
-
min-height: 40px;
|
63
|
-
padding: 5px 20px;
|
64
|
-
|
65
|
-
border-top: 1px solid var(--yc-color-line-generic);
|
66
|
-
border-bottom: 1px solid var(--yc-color-line-generic);
|
67
|
-
background-color: var(--yc-color-base-background);
|
68
|
-
gap: 12px;
|
69
|
-
}
|
70
|
-
|
71
|
-
&__control-run {
|
72
|
-
display: flex;
|
73
|
-
align-items: center;
|
74
|
-
.yc-select__option-text {
|
75
|
-
display: none;
|
76
|
-
}
|
77
|
-
|
78
|
-
.yc-button__text {
|
79
|
-
display: flex;
|
80
|
-
justify-content: center;
|
81
|
-
align-items: center;
|
82
|
-
gap: 8px;
|
83
|
-
}
|
84
|
-
}
|
85
|
-
|
86
57
|
&__history-controls {
|
87
58
|
display: flex;
|
88
59
|
align-items: center;
|
@@ -93,8 +64,4 @@
|
|
93
64
|
|
94
65
|
color: var(--yc-color-text-secondary);
|
95
66
|
}
|
96
|
-
|
97
|
-
&__select-query-action {
|
98
|
-
margin-left: 2px;
|
99
|
-
}
|
100
67
|
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import {Button, DropdownMenu} from '@gravity-ui/uikit';
|
2
|
+
import {useMemo} from 'react';
|
3
|
+
|
4
|
+
import {QueryModes} from '../../../../types/store/query';
|
5
|
+
import {Icon} from '../../../../components/Icon';
|
6
|
+
|
7
|
+
import SaveQuery from '../SaveQuery/SaveQuery';
|
8
|
+
|
9
|
+
import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
|
10
|
+
|
11
|
+
import './QueryEditorControls.scss';
|
12
|
+
|
13
|
+
export const OldQueryEditorControls = ({
|
14
|
+
onRunButtonClick,
|
15
|
+
runIsLoading,
|
16
|
+
onExplainButtonClick,
|
17
|
+
explainIsLoading,
|
18
|
+
onSaveQueryClick,
|
19
|
+
savedQueries,
|
20
|
+
disabled,
|
21
|
+
onUpdateQueryMode,
|
22
|
+
queryMode,
|
23
|
+
}: QueryEditorControlsProps) => {
|
24
|
+
const runModeSelectorMenuItems = useMemo(() => {
|
25
|
+
return Object.entries(QueryModeSelectorTitles).map(([mode, title]) => {
|
26
|
+
return {
|
27
|
+
text: `Run ${title}`,
|
28
|
+
action: () => {
|
29
|
+
onUpdateQueryMode(mode as QueryModes);
|
30
|
+
},
|
31
|
+
};
|
32
|
+
});
|
33
|
+
}, [onUpdateQueryMode]);
|
34
|
+
|
35
|
+
return (
|
36
|
+
<div className={b()}>
|
37
|
+
<div className={b('left')}>
|
38
|
+
<div className={b('run')}>
|
39
|
+
<Button
|
40
|
+
onClick={() => onRunButtonClick(queryMode)}
|
41
|
+
view="action"
|
42
|
+
pin="round-brick"
|
43
|
+
disabled={disabled}
|
44
|
+
loading={runIsLoading}
|
45
|
+
>
|
46
|
+
<Icon name="startPlay" viewBox="0 0 16 16" width={16} height={16} />
|
47
|
+
{`Run ${QueryModeSelectorTitles[queryMode]}`}
|
48
|
+
</Button>
|
49
|
+
<DropdownMenu
|
50
|
+
items={runModeSelectorMenuItems}
|
51
|
+
popupClassName={b('select-query-action-popup')}
|
52
|
+
switcher={
|
53
|
+
<Button
|
54
|
+
view="action"
|
55
|
+
pin="brick-round"
|
56
|
+
disabled={disabled}
|
57
|
+
loading={runIsLoading}
|
58
|
+
className={b('select-query-action')}
|
59
|
+
>
|
60
|
+
<Icon name="chevron-down" width={16} height={16} />
|
61
|
+
</Button>
|
62
|
+
}
|
63
|
+
/>
|
64
|
+
</div>
|
65
|
+
<Button
|
66
|
+
onClick={() => {
|
67
|
+
// Without defined query mode it sends 'explain' action
|
68
|
+
onExplainButtonClick();
|
69
|
+
}}
|
70
|
+
disabled={disabled}
|
71
|
+
loading={explainIsLoading}
|
72
|
+
>
|
73
|
+
Explain
|
74
|
+
</Button>
|
75
|
+
</div>
|
76
|
+
<SaveQuery
|
77
|
+
savedQueries={savedQueries}
|
78
|
+
onSaveQuery={onSaveQueryClick}
|
79
|
+
saveButtonDisabled={disabled}
|
80
|
+
/>
|
81
|
+
</div>
|
82
|
+
);
|
83
|
+
};
|
@@ -0,0 +1,57 @@
|
|
1
|
+
.ydb-query-editor-controls {
|
2
|
+
display: flex;
|
3
|
+
flex: 0 0 40px;
|
4
|
+
justify-content: space-between;
|
5
|
+
align-items: flex-end;
|
6
|
+
|
7
|
+
min-height: 40px;
|
8
|
+
padding: 5px 20px;
|
9
|
+
|
10
|
+
border-top: 1px solid var(--yc-color-line-generic);
|
11
|
+
border-bottom: 1px solid var(--yc-color-line-generic);
|
12
|
+
background-color: var(--yc-color-base-background);
|
13
|
+
gap: 24px;
|
14
|
+
|
15
|
+
&__left {
|
16
|
+
display: flex;
|
17
|
+
gap: 12px;
|
18
|
+
}
|
19
|
+
|
20
|
+
&__run {
|
21
|
+
display: flex;
|
22
|
+
align-items: center;
|
23
|
+
.yc-select__option-text {
|
24
|
+
display: none;
|
25
|
+
}
|
26
|
+
|
27
|
+
.yc-button__text {
|
28
|
+
display: flex;
|
29
|
+
justify-content: center;
|
30
|
+
align-items: center;
|
31
|
+
gap: 8px;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
&__select-query-action {
|
36
|
+
margin-left: 2px;
|
37
|
+
}
|
38
|
+
|
39
|
+
&__mode-selector {
|
40
|
+
&__popup {
|
41
|
+
width: 120px;
|
42
|
+
}
|
43
|
+
|
44
|
+
&__button {
|
45
|
+
width: 120px;
|
46
|
+
margin-left: 2px;
|
47
|
+
}
|
48
|
+
|
49
|
+
&__button-content {
|
50
|
+
display: flex;
|
51
|
+
justify-content: space-between;
|
52
|
+
align-items: center;
|
53
|
+
|
54
|
+
width: 100px;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import {Button, DropdownMenu} from '@gravity-ui/uikit';
|
2
|
+
import {useMemo} from 'react';
|
3
|
+
|
4
|
+
import {QueryModes} from '../../../../types/store/query';
|
5
|
+
import {Icon} from '../../../../components/Icon';
|
6
|
+
|
7
|
+
import SaveQuery from '../SaveQuery/SaveQuery';
|
8
|
+
|
9
|
+
import i18n from '../i18n';
|
10
|
+
|
11
|
+
import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
|
12
|
+
|
13
|
+
import './QueryEditorControls.scss';
|
14
|
+
|
15
|
+
export const QueryEditorControls = ({
|
16
|
+
onRunButtonClick,
|
17
|
+
runIsLoading,
|
18
|
+
onExplainButtonClick,
|
19
|
+
explainIsLoading,
|
20
|
+
onSaveQueryClick,
|
21
|
+
savedQueries,
|
22
|
+
disabled,
|
23
|
+
onUpdateQueryMode,
|
24
|
+
queryMode,
|
25
|
+
}: QueryEditorControlsProps) => {
|
26
|
+
const querySelectorMenuItems = useMemo(() => {
|
27
|
+
return Object.entries(QueryModeSelectorTitles).map(([mode, title]) => {
|
28
|
+
return {
|
29
|
+
text: title,
|
30
|
+
action: () => {
|
31
|
+
onUpdateQueryMode(mode as QueryModes);
|
32
|
+
},
|
33
|
+
};
|
34
|
+
});
|
35
|
+
}, [onUpdateQueryMode]);
|
36
|
+
|
37
|
+
return (
|
38
|
+
<div className={b()}>
|
39
|
+
<div className={b('left')}>
|
40
|
+
<div className={b('run')}>
|
41
|
+
<Button
|
42
|
+
onClick={() => {
|
43
|
+
onRunButtonClick(queryMode);
|
44
|
+
}}
|
45
|
+
view="action"
|
46
|
+
disabled={disabled}
|
47
|
+
loading={runIsLoading}
|
48
|
+
>
|
49
|
+
<Icon name="startPlay" viewBox="0 0 16 16" width={16} height={16} />
|
50
|
+
{'Run'}
|
51
|
+
</Button>
|
52
|
+
</div>
|
53
|
+
<Button
|
54
|
+
onClick={() => {
|
55
|
+
onExplainButtonClick(queryMode);
|
56
|
+
}}
|
57
|
+
disabled={disabled}
|
58
|
+
loading={explainIsLoading}
|
59
|
+
>
|
60
|
+
Explain
|
61
|
+
</Button>
|
62
|
+
<DropdownMenu
|
63
|
+
items={querySelectorMenuItems}
|
64
|
+
popupClassName={b('mode-selector__popup')}
|
65
|
+
switcher={
|
66
|
+
<Button className={b('mode-selector__button')}>
|
67
|
+
<span className={b('mode-selector__button-content')}>
|
68
|
+
{`${i18n('controls.query-mode-selector_type')} ${
|
69
|
+
QueryModeSelectorTitles[queryMode]
|
70
|
+
}`}
|
71
|
+
<Icon name="chevron-down" width={16} height={16} />
|
72
|
+
</span>
|
73
|
+
</Button>
|
74
|
+
}
|
75
|
+
/>
|
76
|
+
</div>
|
77
|
+
<SaveQuery
|
78
|
+
savedQueries={savedQueries}
|
79
|
+
onSaveQuery={onSaveQueryClick}
|
80
|
+
saveButtonDisabled={disabled}
|
81
|
+
/>
|
82
|
+
</div>
|
83
|
+
);
|
84
|
+
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import block from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import {QueryModes} from '../../../../types/store/query';
|
4
|
+
|
5
|
+
export const b = block('ydb-query-editor-controls');
|
6
|
+
|
7
|
+
export const QueryModeSelectorTitles = {
|
8
|
+
[QueryModes.script]: 'Script',
|
9
|
+
[QueryModes.scan]: 'Scan',
|
10
|
+
} as const;
|
11
|
+
|
12
|
+
export interface QueryEditorControlsProps {
|
13
|
+
onRunButtonClick: (mode?: QueryModes) => void;
|
14
|
+
runIsLoading: boolean;
|
15
|
+
onExplainButtonClick: (mode?: QueryModes) => void;
|
16
|
+
explainIsLoading: boolean;
|
17
|
+
onSaveQueryClick: (queryName: string) => void;
|
18
|
+
savedQueries: unknown;
|
19
|
+
disabled: boolean;
|
20
|
+
onUpdateQueryMode: (mode: QueryModes) => void;
|
21
|
+
queryMode: QueryModes;
|
22
|
+
enableQueryModesForExplain: boolean;
|
23
|
+
}
|
@@ -5,12 +5,7 @@ import MonacoEditor from 'react-monaco-editor';
|
|
5
5
|
import JSONTree from 'react-json-inspector';
|
6
6
|
import 'react-json-inspector/json-inspector.css';
|
7
7
|
|
8
|
-
import {
|
9
|
-
TextOverflow,
|
10
|
-
getYdbPlanNodeShape,
|
11
|
-
getCompactTopology,
|
12
|
-
getTopology,
|
13
|
-
} from '@gravity-ui/paranoid';
|
8
|
+
import {TextOverflow, getYdbPlanNodeShape, getTopology} from '@gravity-ui/paranoid';
|
14
9
|
import {Loader, RadioButton} from '@gravity-ui/uikit';
|
15
10
|
|
16
11
|
import Divider from '../../../../components/Divider/Divider';
|
@@ -55,7 +50,7 @@ const explainOptions = [
|
|
55
50
|
function GraphRoot(props) {
|
56
51
|
const paranoid = useRef();
|
57
52
|
|
58
|
-
const {data, opts, shapes,
|
53
|
+
const {data, opts, shapes, theme} = props;
|
59
54
|
|
60
55
|
const [componentTheme, updateComponentTheme] = useState(theme);
|
61
56
|
|
@@ -64,13 +59,8 @@ function GraphRoot(props) {
|
|
64
59
|
}, [theme]);
|
65
60
|
|
66
61
|
const render = () => {
|
67
|
-
|
68
|
-
|
69
|
-
paranoid.current.render();
|
70
|
-
} else if (version === explainVersions.v1) {
|
71
|
-
paranoid.current = getCompactTopology('graphRoot', data, opts);
|
72
|
-
paranoid.current.renderCompactTopology();
|
73
|
-
}
|
62
|
+
paranoid.current = getTopology('graphRoot', data, opts, shapes);
|
63
|
+
paranoid.current.render();
|
74
64
|
};
|
75
65
|
|
76
66
|
useEffect(() => {
|
@@ -112,12 +102,6 @@ function QueryExplain(props) {
|
|
112
102
|
};
|
113
103
|
}, []);
|
114
104
|
|
115
|
-
useEffect(() => {
|
116
|
-
if (!props.ast && activeOption === ExplainOptionIds.ast) {
|
117
|
-
props.astQuery();
|
118
|
-
}
|
119
|
-
}, [activeOption]);
|
120
|
-
|
121
105
|
const onSelectOption = (tabId) => {
|
122
106
|
setActiveOption(tabId);
|
123
107
|
};
|
@@ -131,7 +115,9 @@ function QueryExplain(props) {
|
|
131
115
|
};
|
132
116
|
|
133
117
|
const renderStub = () => {
|
134
|
-
return
|
118
|
+
return (
|
119
|
+
<div className={b('text-message')}>{`There is no ${activeOption} for the request`}</div>
|
120
|
+
);
|
135
121
|
};
|
136
122
|
|
137
123
|
const hasContent = () => {
|
@@ -189,8 +175,12 @@ function QueryExplain(props) {
|
|
189
175
|
const renderGraph = () => {
|
190
176
|
const {explain = {}, theme} = props;
|
191
177
|
const {links, nodes, version} = explain;
|
178
|
+
|
179
|
+
const isSupportedVersion = version === explainVersions.v2;
|
180
|
+
const isEnoughDataForGraph = links && nodes && nodes.length;
|
181
|
+
|
192
182
|
const content =
|
193
|
-
|
183
|
+
isSupportedVersion && isEnoughDataForGraph ? (
|
194
184
|
<div
|
195
185
|
className={b('explain-canvas-container', {
|
196
186
|
hidden: activeOption !== ExplainOptionIds.schema,
|
@@ -198,7 +188,6 @@ function QueryExplain(props) {
|
|
198
188
|
>
|
199
189
|
<GraphRoot
|
200
190
|
theme={theme}
|
201
|
-
version={version}
|
202
191
|
data={{links, nodes}}
|
203
192
|
opts={{
|
204
193
|
renderNodeTitle: renderExplainNode,
|
@@ -18,6 +18,7 @@ import {prepareQueryError} from '../../../../utils/query';
|
|
18
18
|
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
|
19
19
|
|
20
20
|
import ResultIssues from '../Issues/Issues';
|
21
|
+
import {QueryDuration} from '../QueryDuration/QueryDuration';
|
21
22
|
|
22
23
|
import './QueryResult.scss';
|
23
24
|
|
@@ -116,15 +117,11 @@ function QueryResult(props) {
|
|
116
117
|
</Fullscreen>
|
117
118
|
)}
|
118
119
|
</React.Fragment>
|
119
|
-
)
|
120
|
+
);
|
120
121
|
}
|
121
122
|
|
122
123
|
if (error) {
|
123
|
-
return (
|
124
|
-
<div className={b('error')}>
|
125
|
-
{prepareQueryError(error)}
|
126
|
-
</div>
|
127
|
-
);
|
124
|
+
return <div className={b('error')}>{prepareQueryError(error)}</div>;
|
128
125
|
}
|
129
126
|
};
|
130
127
|
|
@@ -136,6 +133,7 @@ function QueryResult(props) {
|
|
136
133
|
|
137
134
|
{props.stats && !props.error && (
|
138
135
|
<React.Fragment>
|
136
|
+
<QueryDuration duration={props.stats?.DurationUs} />
|
139
137
|
<Divider />
|
140
138
|
<RadioButton
|
141
139
|
options={resultOptions}
|