ydb-embedded-ui 0.2.0 → 1.0.2
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 +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 = {
|