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 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 -e YDB_LOCAL_SURVIVE_RESTART=true -dp 2135:2135 -p 8765:8765 -v ~/projects/ydb-embedded-ui-opensource/build:/ydb_data/node_1/contentmonitoring cr.yandex/yc/yandex-docker-local-ydb:latest`
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 = tenantData?.CreateStep / 1000;
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, useDispatch} from 'react-redux';
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 { setShowPreview } from '../../../store/reducers/schema';
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
- path,
155
- executeQuery: {input},
156
- } = props;
157
- if (resultType === RESULT_TYPES.EXPLAIN) {
158
- props.getExplainQuery({query: input, database: path});
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
- }, [resultType]);
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
- dispatch(setShowPreview(false))
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
- dispatch(setShowPreview(false))
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, useMemo} from 'react';
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 { disableFullscreen } from '../../../../store/reducers/fullscreen';
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
- const content = (
192
- <div
193
- className={b('explain-canvas-container', {
194
- hidden: activeOption !== ExplainOptionIds.schema,
195
- })}
196
- style={{height: graphHeight, minHeight: graphHeight, width: '100%'}}
197
- >
198
- {graph}
199
- </div>
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],