ydb-embedded-ui 0.2.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +24 -0
- package/README.md +1 -1
- package/dist/containers/Header/Header.scss +12 -0
- package/dist/containers/Header/Header.tsx +28 -6
- package/dist/containers/Tenant/Acl/Acl.scss +4 -4
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +10 -0
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +14 -5
- package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -2
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +49 -25
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +69 -54
- package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +1 -1
- package/dist/store/reducers/executeQuery.js +36 -2
- package/dist/utils/constants.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### [1.0.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.0.1...v1.0.2) (2022-03-11)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **Header:** add link to internal viewer ([64af24f](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/64af24f8d78cf0d34466ac129be10c0764cce3d4))
|
9
|
+
|
10
|
+
### [1.0.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.0.0...v1.0.1) (2022-03-05)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* **QueriesHistory:** should save history to local storage ([#8](https://www.github.com/ydb-platform/ydb-embedded-ui/issues/8)) ([57031ab](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/57031ab16900e9d1112bbf506d5c777f94f883bb))
|
16
|
+
|
17
|
+
## [1.0.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v0.2.0...v1.0.0) (2022-03-01)
|
18
|
+
|
19
|
+
|
20
|
+
### Bug Fixes
|
21
|
+
|
22
|
+
* **ObjectSummary:** start time should be taken from current schema object ([e7511be](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/e7511be61e5c8d2052ad3a2247a713f55049d3e6))
|
23
|
+
* **QueryEditor:** key bindings should work properly ([ebe59b3](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/ebe59b3c838889ee81e308232e4c8d2ba23a1a3a))
|
24
|
+
* **QueryExplain:** should render graph in fullscreen view properly ([da511da](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/da511da2fc1a36282ad99f20d5d6fd0b5b4ea05b))
|
25
|
+
* **README:** ui path should be correct ([e2668ef](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/e2668ef329de4cf31fd31061dfe3b4ac091e0121))
|
26
|
+
|
3
27
|
## [0.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v0.1.0...v0.2.0) (2022-02-24)
|
4
28
|
|
5
29
|
|
package/README.md
CHANGED
@@ -24,5 +24,5 @@ The build is minified and the filenames include the hashes.
|
|
24
24
|
|
25
25
|
To test production bundle with latest YDB backend release, do the following:
|
26
26
|
1) Build a production bundle with a few tweaks for embedded version: `npm run build:embedded`.
|
27
|
-
2) Invoke `docker run -it --hostname localhost -
|
27
|
+
2) Invoke `docker run -it --hostname localhost -dp 2135:2135 -p 8765:8765 -v ~/projects/ydb-embedded-ui/build:/ydb_data/node_1/contentmonitoring cr.yandex/yc/yandex-docker-local-ydb:latest`
|
28
28
|
3) Open [embedded YDB UI](http://localhost:8765/monitoring) to view it in the browser.
|
@@ -1,15 +1,19 @@
|
|
1
|
-
import {useEffect} from 'react';
|
1
|
+
import React, {useEffect} from 'react';
|
2
2
|
import {useDispatch, useSelector} from 'react-redux';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import {useHistory, useLocation} from 'react-router';
|
5
|
+
import {Breadcrumbs, BreadcrumbsItem, Link} from '@yandex-cloud/uikit';
|
5
6
|
|
6
|
-
import
|
7
|
+
import Divider from '../../components/Divider/Divider';
|
8
|
+
//@ts-ignore
|
9
|
+
import Icon from '../../components/Icon/Icon';
|
10
|
+
|
11
|
+
import {clusterName as clusterNameLocation, backend, customBackend} from '../../store';
|
7
12
|
import {getClusterInfo} from '../../store/reducers/cluster';
|
8
13
|
import {getHostInfo} from '../../store/reducers/host';
|
14
|
+
import {HeaderItemType} from '../../store/reducers/header';
|
9
15
|
|
10
16
|
import './Header.scss';
|
11
|
-
import {Breadcrumbs, BreadcrumbsItem} from '@yandex-cloud/uikit';
|
12
|
-
import {HeaderItemType} from '../../store/reducers/header';
|
13
17
|
|
14
18
|
const b = cn('header');
|
15
19
|
|
@@ -49,6 +53,12 @@ function Header() {
|
|
49
53
|
const renderHeader = () => {
|
50
54
|
const clusterNameFinal = singleClusterMode ? host.ClusterName : clusterName;
|
51
55
|
|
56
|
+
let link = backend + '/internal';
|
57
|
+
|
58
|
+
if (singleClusterMode && !customBackend) {
|
59
|
+
link = `/internal`;
|
60
|
+
}
|
61
|
+
|
52
62
|
const breadcrumbItems = header.reduce((acc, el) => {
|
53
63
|
acc.push({text: el.text, action: () => history.push(el.link)});
|
54
64
|
return acc;
|
@@ -63,8 +73,20 @@ function Header() {
|
|
63
73
|
firstDisplayedItemsCount={1}
|
64
74
|
/>
|
65
75
|
</div>
|
66
|
-
|
67
|
-
|
76
|
+
|
77
|
+
<div className={b('cluster-name-wrapper')}>
|
78
|
+
<Link href={link} target="_blank">
|
79
|
+
Internal viewer{' '}
|
80
|
+
<Icon name="external" viewBox={'0 0 16 16'} width={16} height={16} />
|
81
|
+
</Link>
|
82
|
+
{clusterNameFinal && (
|
83
|
+
<React.Fragment>
|
84
|
+
<div className={b('divider')}>
|
85
|
+
<Divider />
|
86
|
+
</div>
|
87
|
+
<ClusterName name={clusterNameFinal} />
|
88
|
+
</React.Fragment>
|
89
|
+
)}
|
68
90
|
</div>
|
69
91
|
</header>
|
70
92
|
);
|
@@ -1,6 +1,10 @@
|
|
1
1
|
@import '../../../styles/mixins.scss';
|
2
2
|
|
3
3
|
.kv-acl {
|
4
|
+
display: flex;
|
5
|
+
overflow: auto;
|
6
|
+
flex-grow: 1;
|
7
|
+
|
4
8
|
padding: 0 12px 16px;
|
5
9
|
@include query-data-table;
|
6
10
|
&__message-container {
|
@@ -38,8 +42,4 @@
|
|
38
42
|
color: var(--yc-color-text-danger);
|
39
43
|
}
|
40
44
|
}
|
41
|
-
|
42
|
-
&__result {
|
43
|
-
overflow: auto;
|
44
|
-
}
|
45
45
|
}
|
@@ -13,7 +13,9 @@
|
|
13
13
|
max-height: 100%;
|
14
14
|
|
15
15
|
&__overview-wrapper {
|
16
|
+
display: flex;
|
16
17
|
overflow: auto;
|
18
|
+
flex-grow: 1;
|
17
19
|
|
18
20
|
padding: 0 12px 16px;
|
19
21
|
}
|
@@ -91,7 +93,15 @@
|
|
91
93
|
}
|
92
94
|
|
93
95
|
&__info {
|
96
|
+
display: flex;
|
94
97
|
overflow: hidden;
|
98
|
+
flex-direction: column;
|
99
|
+
}
|
100
|
+
|
101
|
+
&__schema {
|
102
|
+
display: flex;
|
103
|
+
overflow: auto;
|
104
|
+
flex-grow: 1;
|
95
105
|
}
|
96
106
|
|
97
107
|
&__info-controls {
|
@@ -78,7 +78,7 @@ interface ObjectSummaryProps {
|
|
78
78
|
onCollapseSummary: VoidFunction;
|
79
79
|
onExpandSummary: VoidFunction;
|
80
80
|
isCollapsed: boolean;
|
81
|
-
additionalTenantInfo?: any
|
81
|
+
additionalTenantInfo?: any;
|
82
82
|
}
|
83
83
|
|
84
84
|
function ObjectSummary(props: ObjectSummaryProps) {
|
@@ -104,6 +104,7 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
104
104
|
|
105
105
|
const {name: tenantName, info: infoTab} = queryParams;
|
106
106
|
const tenantData = _.get(data[tenantName as string], 'PathDescription.Self');
|
107
|
+
const currentSchemaData = _.get(data[currentSchemaPath], 'PathDescription.Self');
|
107
108
|
|
108
109
|
const tableSchema =
|
109
110
|
currentItem?.PathDescription?.Table || currentItem?.PathDescription?.OlapTableDescription;
|
@@ -152,7 +153,7 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
152
153
|
};
|
153
154
|
|
154
155
|
const renderObjectOverview = () => {
|
155
|
-
const startTimeInMilliseconds =
|
156
|
+
const startTimeInMilliseconds = currentSchemaData?.CreateStep / 1000;
|
156
157
|
let createTime = 'Unknown';
|
157
158
|
if (startTimeInMilliseconds) {
|
158
159
|
createTime = new Date(startTimeInMilliseconds).toUTCString();
|
@@ -167,10 +168,16 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
167
168
|
const renderTabContent = () => {
|
168
169
|
switch (infoTab) {
|
169
170
|
case TenantInfoTabsIds.acl: {
|
170
|
-
return <Acl additionalTenantInfo={props.additionalTenantInfo}/>;
|
171
|
+
return <Acl additionalTenantInfo={props.additionalTenantInfo} />;
|
171
172
|
}
|
172
173
|
case TenantInfoTabsIds.schema: {
|
173
|
-
return loadingSchema ?
|
174
|
+
return loadingSchema ? (
|
175
|
+
renderLoader()
|
176
|
+
) : (
|
177
|
+
<div className={b('schema')}>
|
178
|
+
<SchemaViewer data={schema} />
|
179
|
+
</div>
|
180
|
+
);
|
174
181
|
}
|
175
182
|
default: {
|
176
183
|
return renderObjectOverview();
|
@@ -193,7 +200,9 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
193
200
|
<div className={b('tree-title')}>Navigation</div>
|
194
201
|
</div>
|
195
202
|
<div className={b('tree')}>
|
196
|
-
{tenantData &&
|
203
|
+
{tenantData && (
|
204
|
+
<SchemaNode fullPath={tenantName as string} data={tenantData} isRoot />
|
205
|
+
)}
|
197
206
|
</div>
|
198
207
|
</div>
|
199
208
|
);
|
@@ -38,6 +38,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
|
|
38
38
|
};
|
39
39
|
|
40
40
|
const renderSavedQueries = () => {
|
41
|
+
const reversedHistory = ([] as string[]).concat(history).reverse();
|
41
42
|
return (
|
42
43
|
<Popup
|
43
44
|
className={b('popup-wrapper')}
|
@@ -47,7 +48,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
|
|
47
48
|
onClose={onCloseHistory}
|
48
49
|
>
|
49
50
|
<div className={b()}>
|
50
|
-
{
|
51
|
+
{reversedHistory.length === 0 ? (
|
51
52
|
<div className={b('empty')}>History is empty</div>
|
52
53
|
) : (
|
53
54
|
<React.Fragment>
|
@@ -57,7 +58,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
|
|
57
58
|
</div>
|
58
59
|
</div>
|
59
60
|
<div>
|
60
|
-
{
|
61
|
+
{reversedHistory.map((query, index) => {
|
61
62
|
return (
|
62
63
|
<div
|
63
64
|
className={b('saved-queries-row')}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import {useEffect, useReducer, useRef, useState} from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
-
import {connect
|
3
|
+
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';
|
@@ -23,6 +23,8 @@ import {
|
|
23
23
|
goToNextQuery,
|
24
24
|
selectRunAction,
|
25
25
|
RUN_ACTIONS_VALUES,
|
26
|
+
MONACO_HOT_KEY_ACTIONS,
|
27
|
+
setMonacoHotKey,
|
26
28
|
} from '../../../store/reducers/executeQuery';
|
27
29
|
import {getExplainQuery, getExplainQueryAst} from '../../../store/reducers/explainQuery';
|
28
30
|
import {showTooltip} from '../../../store/reducers/tooltip';
|
@@ -45,7 +47,7 @@ import {
|
|
45
47
|
paneVisibilityToggleReducerCreator,
|
46
48
|
} from '../utils/paneVisibilityToggleHelpers';
|
47
49
|
import Preview from '../Preview/Preview';
|
48
|
-
import {
|
50
|
+
import {setShowPreview} from '../../../store/reducers/schema';
|
49
51
|
|
50
52
|
export const RUN_ACTIONS = [
|
51
53
|
{value: RUN_ACTIONS_VALUES.script, content: 'Run Script'},
|
@@ -83,6 +85,7 @@ const propTypes = {
|
|
83
85
|
executeQuery: PropTypes.object,
|
84
86
|
explainQuery: PropTypes.object,
|
85
87
|
showTooltip: PropTypes.func,
|
88
|
+
setMonacoHotKey: PropTypes.func,
|
86
89
|
theme: PropTypes.string,
|
87
90
|
type: PropTypes.string,
|
88
91
|
};
|
@@ -95,8 +98,6 @@ const initialTenantCommonInfoState = {
|
|
95
98
|
function QueryEditor(props) {
|
96
99
|
const [resultType, setResultType] = useState(RESULT_TYPES.EXECUTE);
|
97
100
|
|
98
|
-
const dispatch = useDispatch()
|
99
|
-
|
100
101
|
const [resultVisibilityState, dispatchResultVisibilityState] = useReducer(
|
101
102
|
paneVisibilityToggleReducerCreator(DEFAULT_IS_QUERY_RESULT_COLLAPSED),
|
102
103
|
initialTenantCommonInfoState,
|
@@ -114,6 +115,7 @@ function QueryEditor(props) {
|
|
114
115
|
window.removeEventListener('resize', onChangeWindow);
|
115
116
|
window.removeEventListener('storage', storageEventHandler);
|
116
117
|
window.onbeforeunload = undefined;
|
118
|
+
props.setMonacoHotKey(null);
|
117
119
|
};
|
118
120
|
}, []);
|
119
121
|
|
@@ -150,14 +152,29 @@ function QueryEditor(props) {
|
|
150
152
|
}, [props.executeQuery, props.executeQuery]);
|
151
153
|
|
152
154
|
useEffect(() => {
|
153
|
-
const {
|
154
|
-
|
155
|
-
|
156
|
-
}
|
157
|
-
|
158
|
-
|
155
|
+
const {monacoHotKey, setMonacoHotKey} = props;
|
156
|
+
if (monacoHotKey === null) {
|
157
|
+
return
|
158
|
+
}
|
159
|
+
setMonacoHotKey(null);
|
160
|
+
switch (monacoHotKey) {
|
161
|
+
case MONACO_HOT_KEY_ACTIONS.sendQuery: {
|
162
|
+
return handleSendClick();
|
163
|
+
}
|
164
|
+
case MONACO_HOT_KEY_ACTIONS.goPrev: {
|
165
|
+
return handlePreviousHistoryClick();
|
166
|
+
}
|
167
|
+
case MONACO_HOT_KEY_ACTIONS.goNext: {
|
168
|
+
return handleNextHistoryClick();
|
169
|
+
}
|
170
|
+
case MONACO_HOT_KEY_ACTIONS.getExplain: {
|
171
|
+
return handleGetExplainQueryClick();
|
172
|
+
}
|
173
|
+
default: {
|
174
|
+
return;
|
175
|
+
}
|
159
176
|
}
|
160
|
-
}, [
|
177
|
+
}, [props.monacoHotKey]);
|
161
178
|
|
162
179
|
const checkIfHasUnsavedInput = (e) => {
|
163
180
|
e.preventDefault();
|
@@ -165,6 +182,10 @@ function QueryEditor(props) {
|
|
165
182
|
e.returnValue = '';
|
166
183
|
};
|
167
184
|
|
185
|
+
const handleKeyBinding = (value) => {
|
186
|
+
return () => props.setMonacoHotKey(value);
|
187
|
+
};
|
188
|
+
|
168
189
|
const editorDidMount = (editor, monaco) => {
|
169
190
|
editorRef.current = editor;
|
170
191
|
editor.focus();
|
@@ -183,9 +204,7 @@ function QueryEditor(props) {
|
|
183
204
|
contextMenuOrder: 1,
|
184
205
|
// Method that will be executed when the action is triggered.
|
185
206
|
// @param editor The editor instance is passed in as a convinience
|
186
|
-
run: ()
|
187
|
-
handleSendClick();
|
188
|
-
},
|
207
|
+
run: handleKeyBinding(MONACO_HOT_KEY_ACTIONS.sendQuery),
|
189
208
|
});
|
190
209
|
|
191
210
|
editor.addAction({
|
@@ -197,9 +216,7 @@ function QueryEditor(props) {
|
|
197
216
|
],
|
198
217
|
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
|
199
218
|
contextMenuOrder: 2,
|
200
|
-
run: ()
|
201
|
-
handlePreviousHistoryClick();
|
202
|
-
},
|
219
|
+
run: handleKeyBinding(MONACO_HOT_KEY_ACTIONS.goPrev),
|
203
220
|
});
|
204
221
|
editor.addAction({
|
205
222
|
id: 'next-query',
|
@@ -210,9 +227,7 @@ function QueryEditor(props) {
|
|
210
227
|
],
|
211
228
|
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
|
212
229
|
contextMenuOrder: 3,
|
213
|
-
run: ()
|
214
|
-
handleNextHistoryClick();
|
215
|
-
},
|
230
|
+
run: handleKeyBinding(MONACO_HOT_KEY_ACTIONS.goNext),
|
216
231
|
});
|
217
232
|
|
218
233
|
editor.addAction({
|
@@ -228,9 +243,7 @@ function QueryEditor(props) {
|
|
228
243
|
keybindingContext: null,
|
229
244
|
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
|
230
245
|
contextMenuOrder: 4,
|
231
|
-
run: ()
|
232
|
-
handleGetExplainQueryClick();
|
233
|
-
},
|
246
|
+
run: handleKeyBinding(MONACO_HOT_KEY_ACTIONS.getExplain),
|
234
247
|
});
|
235
248
|
};
|
236
249
|
const onChange = (newValue) => {
|
@@ -243,11 +256,12 @@ function QueryEditor(props) {
|
|
243
256
|
executeQuery: {input, history, runAction},
|
244
257
|
sendQuery,
|
245
258
|
saveQueryToHistory,
|
259
|
+
setShowPreview,
|
246
260
|
} = props;
|
247
261
|
|
248
262
|
setResultType(RESULT_TYPES.EXECUTE);
|
249
263
|
sendQuery({query: input, database: path, action: runAction});
|
250
|
-
|
264
|
+
setShowPreview(false);
|
251
265
|
|
252
266
|
const {queries, currentIndex} = history;
|
253
267
|
if (input !== queries[currentIndex]) {
|
@@ -257,8 +271,15 @@ function QueryEditor(props) {
|
|
257
271
|
};
|
258
272
|
|
259
273
|
const handleGetExplainQueryClick = () => {
|
274
|
+
const {
|
275
|
+
path,
|
276
|
+
executeQuery: {input},
|
277
|
+
getExplainQuery,
|
278
|
+
setShowPreview,
|
279
|
+
} = props;
|
260
280
|
setResultType(RESULT_TYPES.EXPLAIN);
|
261
|
-
|
281
|
+
getExplainQuery({query: input, database: path});
|
282
|
+
setShowPreview(false);
|
262
283
|
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
|
263
284
|
};
|
264
285
|
|
@@ -655,6 +676,7 @@ const mapStateToProps = (state) => {
|
|
655
676
|
savedQueries: parseJson(getSettingValue(state, SAVED_QUERIES_KEY)),
|
656
677
|
showPreview: state.schema.showPreview,
|
657
678
|
currentSchema: state.schema.currentSchema,
|
679
|
+
monacoHotKey: state.executeQuery?.monacoHotKey,
|
658
680
|
};
|
659
681
|
};
|
660
682
|
|
@@ -669,6 +691,8 @@ const mapDispatchToProps = {
|
|
669
691
|
getExplainQueryAst,
|
670
692
|
setSettingValue,
|
671
693
|
selectRunAction,
|
694
|
+
setShowPreview,
|
695
|
+
setMonacoHotKey,
|
672
696
|
};
|
673
697
|
|
674
698
|
QueryEditor.propTypes = propTypes;
|
@@ -1,14 +1,14 @@
|
|
1
|
-
import React, {useEffect, useState
|
1
|
+
import React, {useEffect, useState} from 'react';
|
2
2
|
import cn from 'bem-cn-lite';
|
3
3
|
import MonacoEditor from 'react-monaco-editor';
|
4
4
|
import {Loader, RadioButton} from '@yandex-cloud/uikit';
|
5
5
|
import JSONTree from 'react-json-inspector';
|
6
6
|
import {LANGUAGE_S_EXPRESSION_ID} from '../../../../utils/monaco';
|
7
7
|
import {
|
8
|
-
TopologyWrapper,
|
9
|
-
CompactTopologyWrapper,
|
10
8
|
TextOverflow,
|
11
9
|
getYdbPlanNodeShape,
|
10
|
+
getCompactTopology,
|
11
|
+
getTopology,
|
12
12
|
} from '@yandex-cloud/paranoid';
|
13
13
|
import {renderExplainNode} from '../../../../utils';
|
14
14
|
import {explainVersions} from '../../../../store/reducers/explainQuery';
|
@@ -21,7 +21,7 @@ import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
|
|
21
21
|
import 'react-json-inspector/json-inspector.css';
|
22
22
|
import './QueryExplain.scss';
|
23
23
|
import {useDispatch, useSelector} from 'react-redux';
|
24
|
-
import {
|
24
|
+
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
|
25
25
|
|
26
26
|
const b = cn('kv-query-explain');
|
27
27
|
|
@@ -61,8 +61,42 @@ const explainOptions = [
|
|
61
61
|
{value: ExplainOptionIds.ast, content: 'AST'},
|
62
62
|
];
|
63
63
|
|
64
|
+
function GraphRoot(props) {
|
65
|
+
let paranoid;
|
66
|
+
useEffect(() => {
|
67
|
+
const {data, opts, shapes, version} = props;
|
68
|
+
if (version === explainVersions.v2) {
|
69
|
+
paranoid = getTopology('graphRoot', props.data, opts, shapes);
|
70
|
+
paranoid.render();
|
71
|
+
} else if (version === explainVersions.v1) {
|
72
|
+
paranoid = getCompactTopology('graphRoot', data, opts);
|
73
|
+
paranoid.renderCompactTopology();
|
74
|
+
}
|
75
|
+
}, []);
|
76
|
+
|
77
|
+
useEffect(() => {
|
78
|
+
const graphRoot = document.getElementById('graphRoot');
|
79
|
+
|
80
|
+
if (!graphRoot) {
|
81
|
+
throw new Error("Can't find element with id #graphRoot");
|
82
|
+
}
|
83
|
+
|
84
|
+
graphRoot.innerHTML = '';
|
85
|
+
|
86
|
+
const {data, opts} = props;
|
87
|
+
paranoid = getCompactTopology('graphRoot', data, opts);
|
88
|
+
paranoid.renderCompactTopology();
|
89
|
+
}, [props.opts.colors]);
|
90
|
+
|
91
|
+
useEffect(() => {
|
92
|
+
paranoid.updateData(props.data);
|
93
|
+
}, [props.data]);
|
94
|
+
|
95
|
+
return <div id="graphRoot" style={{height: '100vh'}} />;
|
96
|
+
}
|
97
|
+
|
64
98
|
function QueryExplain(props) {
|
65
|
-
const dispatch = useDispatch()
|
99
|
+
const dispatch = useDispatch();
|
66
100
|
const [activeOption, setActiveOption] = useState(ExplainOptionIds.schema);
|
67
101
|
|
68
102
|
const isFullscreen = useSelector((state) => state.fullscreen);
|
@@ -76,41 +110,6 @@ function QueryExplain(props) {
|
|
76
110
|
const {explain = {}, theme} = props;
|
77
111
|
const {links, nodes, version, graphDepth} = explain;
|
78
112
|
|
79
|
-
const graph = useMemo(() => {
|
80
|
-
if (links && nodes) {
|
81
|
-
if (version === explainVersions.v2) {
|
82
|
-
return (
|
83
|
-
<TopologyWrapper
|
84
|
-
data={{links, nodes}}
|
85
|
-
opts={{
|
86
|
-
renderNodeTitle: renderExplainNode,
|
87
|
-
textOverflow: TextOverflow.Normal,
|
88
|
-
colors: theme === 'dark' ? DARK_COLORS : {},
|
89
|
-
initialZoomFitsCanvas: true,
|
90
|
-
}}
|
91
|
-
shapes={{
|
92
|
-
node: getYdbPlanNodeShape,
|
93
|
-
}}
|
94
|
-
/>
|
95
|
-
);
|
96
|
-
} else if (version === explainVersions.v1) {
|
97
|
-
return (
|
98
|
-
<CompactTopologyWrapper
|
99
|
-
data={{links, nodes}}
|
100
|
-
opts={{
|
101
|
-
renderNodeTitle: renderExplainNode,
|
102
|
-
textOverflow: TextOverflow.Normal,
|
103
|
-
colors: theme === 'dark' ? DARK_COLORS : {},
|
104
|
-
initialZoomFitsCanvas: true,
|
105
|
-
}}
|
106
|
-
/>
|
107
|
-
);
|
108
|
-
}
|
109
|
-
return 'The explanation format of the query is not supported';
|
110
|
-
}
|
111
|
-
return null;
|
112
|
-
}, [links, nodes, theme, version]);
|
113
|
-
|
114
113
|
useEffect(() => {
|
115
114
|
if (!props.ast && activeOption === ExplainOptionIds.ast) {
|
116
115
|
props.astQuery();
|
@@ -188,19 +187,37 @@ function QueryExplain(props) {
|
|
188
187
|
|
189
188
|
const renderGraph = () => {
|
190
189
|
const graphHeight = `${Math.max(graphDepth * 100, 200)}px`;
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
190
|
+
|
191
|
+
const content =
|
192
|
+
links && nodes && nodes.length ? (
|
193
|
+
<div
|
194
|
+
className={b('explain-canvas-container', {
|
195
|
+
hidden: activeOption !== ExplainOptionIds.schema,
|
196
|
+
})}
|
197
|
+
style={{
|
198
|
+
height: isFullscreen ? '100%' : graphHeight,
|
199
|
+
minHeight: graphHeight,
|
200
|
+
width: '100%',
|
201
|
+
}}
|
202
|
+
>
|
203
|
+
<GraphRoot
|
204
|
+
version={version}
|
205
|
+
data={{links, nodes}}
|
206
|
+
opts={{
|
207
|
+
renderNodeTitle: renderExplainNode,
|
208
|
+
textOverflow: TextOverflow.Normal,
|
209
|
+
colors: theme === 'dark' ? DARK_COLORS : {},
|
210
|
+
initialZoomFitsCanvas: true,
|
211
|
+
}}
|
212
|
+
shapes={{
|
213
|
+
node: getYdbPlanNodeShape,
|
214
|
+
}}
|
215
|
+
/>
|
216
|
+
</div>
|
217
|
+
) : null;
|
201
218
|
return (
|
202
219
|
<React.Fragment>
|
203
|
-
{content}
|
220
|
+
{!isFullscreen && content}
|
204
221
|
{isFullscreen && <Fullscreen>{content}</Fullscreen>}
|
205
222
|
{renderStub()}
|
206
223
|
</React.Fragment>
|
@@ -249,9 +266,7 @@ function QueryExplain(props) {
|
|
249
266
|
)}
|
250
267
|
</div>
|
251
268
|
<div className={b('controls-left')}>
|
252
|
-
<EnableFullscreenButton
|
253
|
-
disabled={Boolean(props.error) || activeOption === ExplainOptionIds.schema}
|
254
|
-
/>
|
269
|
+
<EnableFullscreenButton disabled={Boolean(props.error)} />
|
255
270
|
<PaneVisibilityToggleButtons
|
256
271
|
onCollapse={props.onCollapseResults}
|
257
272
|
onExpand={props.onExpandResults}
|
@@ -67,7 +67,7 @@ class SchemaViewer extends React.Component {
|
|
67
67
|
theme="yandex-cloud"
|
68
68
|
data={tableData}
|
69
69
|
columns={columns}
|
70
|
-
settings={
|
70
|
+
settings={DEFAULT_TABLE_SETTINGS}
|
71
71
|
dynamicRender={true}
|
72
72
|
initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.DESCENDING}}
|
73
73
|
/>
|
@@ -1,5 +1,9 @@
|
|
1
1
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
2
2
|
import '../../services/api';
|
3
|
+
import {getValueFromLS, parseJson} from '../../utils/utils';
|
4
|
+
import {QUERIES_HISTORY_KEY} from '../../utils/constants';
|
5
|
+
|
6
|
+
const MAXIMUM_QUERIES_IN_HISTORY = 20;
|
3
7
|
|
4
8
|
const SEND_QUERY = createRequestActionTypes('query', 'SEND_QUERY');
|
5
9
|
const CHANGE_USER_INPUT = 'query/CHANGE_USER_INPUT';
|
@@ -7,20 +11,33 @@ const SAVE_QUERY_TO_HISTORY = 'query/SAVE_QUERY_TO_HISTORY';
|
|
7
11
|
const GO_TO_PREVIOUS_QUERY = 'query/GO_TO_PREVIOUS_QUERY';
|
8
12
|
const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
|
9
13
|
const SELECT_RUN_ACTION = 'query/SELECT_RUN_ACTION';
|
14
|
+
const MONACO_HOT_KEY = 'query/MONACO_HOT_KEY';
|
15
|
+
|
16
|
+
const queriesHistoryInitial = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, []));
|
17
|
+
|
18
|
+
const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY;
|
10
19
|
|
11
20
|
export const RUN_ACTIONS_VALUES = {
|
12
21
|
script: 'execute-script',
|
13
22
|
scan: 'execute-scan',
|
14
23
|
};
|
15
24
|
|
25
|
+
export const MONACO_HOT_KEY_ACTIONS = {
|
26
|
+
sendQuery: 'sendQuery',
|
27
|
+
goPrev: 'goPrev',
|
28
|
+
goNext: 'goNext',
|
29
|
+
getExplain: 'getExplain',
|
30
|
+
};
|
31
|
+
|
16
32
|
const initialState = {
|
17
33
|
loading: false,
|
18
34
|
input: '',
|
19
35
|
history: {
|
20
|
-
queries:
|
36
|
+
queries: queriesHistoryInitial.slice(sliceLimit < 0 ? 0 : sliceLimit),
|
21
37
|
currentIndex: -1,
|
22
38
|
},
|
23
39
|
runAction: RUN_ACTIONS_VALUES.script,
|
40
|
+
monacoHotKey: null,
|
24
41
|
};
|
25
42
|
|
26
43
|
const executeQuery = (state = initialState, action) => {
|
@@ -67,7 +84,10 @@ const executeQuery = (state = initialState, action) => {
|
|
67
84
|
|
68
85
|
case SAVE_QUERY_TO_HISTORY: {
|
69
86
|
const query = action.data;
|
70
|
-
const newQueries = [...state.history.queries, query]
|
87
|
+
const newQueries = [...state.history.queries, query].slice(
|
88
|
+
state.history.queries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0,
|
89
|
+
);
|
90
|
+
window.localStorage.setItem(QUERIES_HISTORY_KEY, JSON.stringify(newQueries));
|
71
91
|
const currentIndex = newQueries.length - 1;
|
72
92
|
|
73
93
|
return {
|
@@ -104,6 +124,13 @@ const executeQuery = (state = initialState, action) => {
|
|
104
124
|
};
|
105
125
|
}
|
106
126
|
|
127
|
+
case MONACO_HOT_KEY: {
|
128
|
+
return {
|
129
|
+
...state,
|
130
|
+
monacoHotKey: action.data,
|
131
|
+
};
|
132
|
+
}
|
133
|
+
|
107
134
|
default:
|
108
135
|
return state;
|
109
136
|
}
|
@@ -155,4 +182,11 @@ export const changeUserInput = ({input}) => {
|
|
155
182
|
};
|
156
183
|
};
|
157
184
|
|
185
|
+
export const setMonacoHotKey = (value) => {
|
186
|
+
return {
|
187
|
+
type: MONACO_HOT_KEY,
|
188
|
+
data: value,
|
189
|
+
};
|
190
|
+
};
|
191
|
+
|
158
192
|
export default executeQuery;
|
package/dist/utils/constants.js
CHANGED
@@ -120,6 +120,7 @@ export const PROBLEMS = 'With problems';
|
|
120
120
|
|
121
121
|
export const THEME_KEY = 'theme';
|
122
122
|
export const SAVED_QUERIES_KEY = 'saved_queries';
|
123
|
+
export const QUERIES_HISTORY_KEY = 'queries_history';
|
123
124
|
export const DATA_QA_TUNE_COLUMNS_POPUP = 'tune-columns-popup';
|
124
125
|
|
125
126
|
export const defaultUserSettings = {
|