ydb-embedded-ui 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +2 -1
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +49 -25
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +69 -54
- package/dist/store/reducers/executeQuery.js +23 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.0.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v0.2.0...v1.0.0) (2022-03-01)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **ObjectSummary:** start time should be taken from current schema object ([e7511be](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/e7511be61e5c8d2052ad3a2247a713f55049d3e6))
|
9
|
+
* **QueryEditor:** key bindings should work properly ([ebe59b3](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/ebe59b3c838889ee81e308232e4c8d2ba23a1a3a))
|
10
|
+
* **QueryExplain:** should render graph in fullscreen view properly ([da511da](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/da511da2fc1a36282ad99f20d5d6fd0b5b4ea05b))
|
11
|
+
* **README:** ui path should be correct ([e2668ef](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/e2668ef329de4cf31fd31061dfe3b4ac091e0121))
|
12
|
+
|
3
13
|
## [0.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v0.1.0...v0.2.0) (2022-02-24)
|
4
14
|
|
5
15
|
|
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.
|
@@ -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();
|
@@ -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}
|
@@ -7,12 +7,20 @@ const SAVE_QUERY_TO_HISTORY = 'query/SAVE_QUERY_TO_HISTORY';
|
|
7
7
|
const GO_TO_PREVIOUS_QUERY = 'query/GO_TO_PREVIOUS_QUERY';
|
8
8
|
const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
|
9
9
|
const SELECT_RUN_ACTION = 'query/SELECT_RUN_ACTION';
|
10
|
+
const MONACO_HOT_KEY = 'query/MONACO_HOT_KEY';
|
10
11
|
|
11
12
|
export const RUN_ACTIONS_VALUES = {
|
12
13
|
script: 'execute-script',
|
13
14
|
scan: 'execute-scan',
|
14
15
|
};
|
15
16
|
|
17
|
+
export const MONACO_HOT_KEY_ACTIONS = {
|
18
|
+
sendQuery: 'sendQuery',
|
19
|
+
goPrev: 'goPrev',
|
20
|
+
goNext: 'goNext',
|
21
|
+
getExplain: 'getExplain',
|
22
|
+
};
|
23
|
+
|
16
24
|
const initialState = {
|
17
25
|
loading: false,
|
18
26
|
input: '',
|
@@ -21,6 +29,7 @@ const initialState = {
|
|
21
29
|
currentIndex: -1,
|
22
30
|
},
|
23
31
|
runAction: RUN_ACTIONS_VALUES.script,
|
32
|
+
monacoHotKey: null,
|
24
33
|
};
|
25
34
|
|
26
35
|
const executeQuery = (state = initialState, action) => {
|
@@ -104,6 +113,13 @@ const executeQuery = (state = initialState, action) => {
|
|
104
113
|
};
|
105
114
|
}
|
106
115
|
|
116
|
+
case MONACO_HOT_KEY: {
|
117
|
+
return {
|
118
|
+
...state,
|
119
|
+
monacoHotKey: action.data,
|
120
|
+
};
|
121
|
+
}
|
122
|
+
|
107
123
|
default:
|
108
124
|
return state;
|
109
125
|
}
|
@@ -155,4 +171,11 @@ export const changeUserInput = ({input}) => {
|
|
155
171
|
};
|
156
172
|
};
|
157
173
|
|
174
|
+
export const setMonacoHotKey = (value) => {
|
175
|
+
return {
|
176
|
+
type: MONACO_HOT_KEY,
|
177
|
+
data: value,
|
178
|
+
};
|
179
|
+
};
|
180
|
+
|
158
181
|
export default executeQuery;
|