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 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
  ],