ydb-embedded-ui 3.5.0 → 4.1.0
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/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}
|