ydb-embedded-ui 0.2.0 → 1.0.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 +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;
|