ydb-embedded-ui 2.4.3 → 2.4.4

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.4](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.4.3...v2.4.4) (2022-11-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **api:** update getDescribe and getSchema requests params ([d70ba54](https://github.com/ydb-platform/ydb-embedded-ui/commit/d70ba54b90b9c86a393bd3f7845183114e5afbf1))
9
+ * **describe:** cancel concurrent requests ([2f39ad0](https://github.com/ydb-platform/ydb-embedded-ui/commit/2f39ad0f736d44c3749d9523f5024151c51fcf6f))
10
+ * **Describe:** render loader on path change ([baf552a](https://github.com/ydb-platform/ydb-embedded-ui/commit/baf552af8bb67046baa36e9115064b4b192cb015))
11
+ * **QueryExplain:** fix colors on theme change ([cc0a2d6](https://github.com/ydb-platform/ydb-embedded-ui/commit/cc0a2d67139457748089c6bf1fb1045b0a6b0b93))
12
+ * **SchemaTree:** remove unneeded fetches ([c7c0489](https://github.com/ydb-platform/ydb-embedded-ui/commit/c7c048937c5ae9e5e243d6e538aab8c2e2921df5))
13
+ * **SchemaTree:** remove unneeded getDescribe ([1146f13](https://github.com/ydb-platform/ydb-embedded-ui/commit/1146f13a7a5a277a292b3789d45a0872dda0c487))
14
+ * **Tenant:** make tenant fetch schema only on tenant change ([ccefbff](https://github.com/ydb-platform/ydb-embedded-ui/commit/ccefbffea08fc8f248a3dd1135e82de6db9f0645))
15
+
3
16
  ## [2.4.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.4.2...v2.4.3) (2022-11-14)
4
17
 
5
18
 
@@ -0,0 +1,91 @@
1
+ import {useCallback} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import cn from 'bem-cn-lite';
4
+ // @ts-ignore
5
+ import JSONTree from 'react-json-inspector';
6
+ import 'react-json-inspector/json-inspector.css';
7
+
8
+ import {Loader} from '@gravity-ui/uikit';
9
+
10
+ import {prepareQueryError} from '../../../../utils/query';
11
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
12
+ import {
13
+ getDescribe,
14
+ setDataWasNotLoaded,
15
+ setCurrentDescribePath,
16
+ } from '../../../../store/reducers/describe';
17
+
18
+ import './Describe.scss';
19
+
20
+ const b = cn('kv-describe');
21
+
22
+ const expandMap = new Map();
23
+
24
+ interface IDescribeProps {
25
+ tenant: string;
26
+ }
27
+
28
+ const Describe = ({tenant}: IDescribeProps) => {
29
+ const dispatch = useDispatch();
30
+
31
+ const {currentDescribe, error, loading, wasLoaded} = useTypedSelector(
32
+ (state) => state.describe,
33
+ );
34
+
35
+ const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
36
+
37
+ const fetchData = useCallback(
38
+ (isBackground: boolean) => {
39
+ if (!isBackground) {
40
+ dispatch(setDataWasNotLoaded());
41
+ }
42
+
43
+ const path = currentSchemaPath || tenant;
44
+
45
+ dispatch(setCurrentDescribePath(path));
46
+ dispatch(getDescribe({path}));
47
+ },
48
+ [currentSchemaPath, tenant, dispatch],
49
+ );
50
+
51
+ useAutofetcher(fetchData, [fetchData], autorefresh);
52
+
53
+ if (loading && !wasLoaded) {
54
+ return (
55
+ <div className={b('loader-container')}>
56
+ <Loader size="m" />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ if (error) {
62
+ return <div className={b('message-container', 'error')}>{prepareQueryError(error)}</div>;
63
+ }
64
+
65
+ if (!loading && !currentDescribe) {
66
+ return <div className={b('message-container')}>Empty</div>;
67
+ }
68
+
69
+ return (
70
+ <div className={b()}>
71
+ <div className={b('result')}>
72
+ <JSONTree
73
+ data={currentDescribe}
74
+ className={b('tree')}
75
+ onClick={({path}: {path: string}) => {
76
+ const newValue = !(expandMap.get(path) || false);
77
+ expandMap.set(path, newValue);
78
+ }}
79
+ searchOptions={{
80
+ debounceTime: 300,
81
+ }}
82
+ isExpanded={(keypath: string) => {
83
+ return expandMap.get(keypath) || false;
84
+ }}
85
+ />
86
+ </div>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ export default Describe;
@@ -1,4 +1,4 @@
1
- import React, {useEffect, useRef, useState} from 'react';
1
+ import React, {useCallback, useEffect, useRef, 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 '@gravity-ui/uikit';
@@ -25,20 +25,6 @@ import {disableFullscreen} from '../../../../store/reducers/fullscreen';
25
25
 
26
26
  const b = cn('kv-query-explain');
27
27
 
28
- const DARK_COLORS = {
29
- success: 'rgba(59,201,53,0.75)',
30
- error: '#bf3230',
31
- warning: '#cc6810',
32
- mute: 'rgba(255,255,255,0.15)',
33
- stroke: 'rgba(255,255,255,0.17)',
34
- fill: '#313037',
35
- nodeFill: '#3b3a41',
36
- nodeShadow: 'rgba(0,0,0,0.2)',
37
- titleColor: 'rgba(255,255,255,0.7)',
38
- textColor: 'rgba(255,255,255,0.55)',
39
- buttonBorderColor: 'rgba(255,255,255,0.07)',
40
- };
41
-
42
28
  const EDITOR_OPTIONS = {
43
29
  automaticLayout: true,
44
30
  selectOnLineNumbers: true,
@@ -64,9 +50,15 @@ const explainOptions = [
64
50
  function GraphRoot(props) {
65
51
  const paranoid = useRef();
66
52
 
67
- const render = () => {
68
- const {data, opts, shapes, version} = props;
53
+ const {data, opts, shapes, version, theme} = props;
54
+
55
+ const [componentTheme, updateComponentTheme] = useState(theme);
69
56
 
57
+ useEffect(() => {
58
+ updateComponentTheme(theme);
59
+ }, [theme]);
60
+
61
+ const render = useCallback(() => {
70
62
  if (version === explainVersions.v2) {
71
63
  paranoid.current = getTopology('graphRoot', data, opts, shapes);
72
64
  paranoid.current.render();
@@ -74,7 +66,7 @@ function GraphRoot(props) {
74
66
  paranoid.current = getCompactTopology('graphRoot', data, opts);
75
67
  paranoid.current.renderCompactTopology();
76
68
  }
77
- }
69
+ }, [data, opts, shapes, version]);
78
70
 
79
71
  useEffect(() => {
80
72
  render();
@@ -94,7 +86,7 @@ function GraphRoot(props) {
94
86
  graphRoot.innerHTML = '';
95
87
 
96
88
  render();
97
- }, [props.opts.colors]);
89
+ }, [componentTheme, render]);
98
90
 
99
91
  useEffect(() => {
100
92
  paranoid.current?.updateData?.(props.data);
@@ -134,11 +126,7 @@ function QueryExplain(props) {
134
126
  };
135
127
 
136
128
  const renderStub = () => {
137
- return (
138
- <div className={b('text-message')}>
139
- There is no explanation for the request
140
- </div>
141
- );
129
+ return <div className={b('text-message')}>There is no explanation for the request</div>;
142
130
  };
143
131
 
144
132
  const hasContent = () => {
@@ -204,12 +192,12 @@ function QueryExplain(props) {
204
192
  })}
205
193
  >
206
194
  <GraphRoot
195
+ theme={theme}
207
196
  version={version}
208
197
  data={{links, nodes}}
209
198
  opts={{
210
199
  renderNodeTitle: renderExplainNode,
211
200
  textOverflow: TextOverflow.Normal,
212
- colors: theme === 'dark' ? DARK_COLORS : {},
213
201
  initialZoomFitsCanvas: true,
214
202
  }}
215
203
  shapes={{
@@ -237,11 +225,7 @@ function QueryExplain(props) {
237
225
  message = error;
238
226
  }
239
227
 
240
- return (
241
- <div className={b('text-message')}>
242
- {message}
243
- </div>
244
- );
228
+ return <div className={b('text-message')}>{message}</div>;
245
229
  };
246
230
 
247
231
  const renderContent = () => {
@@ -292,7 +276,9 @@ function QueryExplain(props) {
292
276
  )}
293
277
  </div>
294
278
  <div className={b('controls-left')}>
295
- <EnableFullscreenButton disabled={Boolean(props.error) || !hasContent()} />
279
+ <EnableFullscreenButton
280
+ disabled={Boolean(props.error) || !hasContent()}
281
+ />
296
282
  <PaneVisibilityToggleButtons
297
283
  onCollapse={props.onCollapseResults}
298
284
  onExpand={props.onExpandResults}
@@ -3,9 +3,7 @@ import {useDispatch} from 'react-redux';
3
3
 
4
4
  import {NavigationTree} from 'ydb-ui-components';
5
5
 
6
- import {setCurrentSchemaPath, getSchema, preloadSchemas} from '../../../../store/reducers/schema';
7
- import {getDescribe} from '../../../../store/reducers/describe';
8
- import {getSchemaAcl} from '../../../../store/reducers/schemaAcl';
6
+ import {setCurrentSchemaPath, preloadSchemas} from '../../../../store/reducers/schema';
9
7
  import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
10
8
 
11
9
  import {mapPathTypeToNavigationTreeType} from '../../utils/schema';
@@ -30,7 +28,7 @@ export function SchemaTree(props: SchemaTreeProps) {
30
28
  const {PathDescription: {Children = []} = {}} = data;
31
29
 
32
30
  const preloadedData: Record<string, TEvDescribeSchemeResult> = {
33
- [path]: data
31
+ [path]: data,
34
32
  };
35
33
 
36
34
  const childItems = Children.map((childData) => {
@@ -55,9 +53,6 @@ export function SchemaTree(props: SchemaTreeProps) {
55
53
 
56
54
  const handleActivePathUpdate = (activePath: string) => {
57
55
  dispatch(setCurrentSchemaPath(activePath));
58
- dispatch(getSchema({path: activePath}));
59
- dispatch(getDescribe({path: activePath}));
60
- dispatch(getSchemaAcl({path: activePath}));
61
56
  };
62
57
 
63
58
  useEffect(() => {
@@ -78,12 +78,15 @@ function Tenant(props: TenantProps) {
78
78
  const tenantName = name as string;
79
79
 
80
80
  useEffect(() => {
81
- const schemaPath = currentSchemaPath || tenantName;
82
- dispatch(resetLoadingState());
83
81
  dispatch(getSchema({path: tenantName}));
84
- dispatch(getSchema({path: schemaPath}));
85
- dispatch(getSchemaAcl({path: schemaPath}));
86
- }, [tenantName, currentSchemaPath, dispatch]);
82
+ dispatch(getSchemaAcl({path: tenantName}));
83
+ }, [tenantName, dispatch]);
84
+
85
+ useEffect(() => {
86
+ dispatch(resetLoadingState());
87
+ dispatch(getSchema({path: currentSchemaPath}));
88
+ dispatch(getSchemaAcl({path: currentSchemaPath}));
89
+ }, [currentSchemaPath, dispatch]);
87
90
 
88
91
  useEffect(() => {
89
92
  dispatch(disableAutorefresh());
@@ -83,17 +83,26 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
83
83
  path,
84
84
  enums: true,
85
85
  backup: false,
86
+ private: true,
87
+ partition_config: false,
86
88
  partition_stats: false,
87
89
  partitioning_info: false,
90
+ subs: 1,
88
91
  },
89
92
  {concurrentId: concurrentId || `getSchema|${path}`},
90
93
  );
91
94
  }
92
- getDescribe({path}) {
93
- return this.get(this.getPath('/viewer/json/describe'), {
94
- path,
95
- enums: true,
96
- });
95
+ getDescribe({path}, {concurrentId} = {}) {
96
+ return this.get(
97
+ this.getPath('/viewer/json/describe'),
98
+ {
99
+ path,
100
+ enums: true,
101
+ partition_stats: true,
102
+ subs: 0,
103
+ },
104
+ {concurrentId: concurrentId || `getDescribe|${path}`},
105
+ );
97
106
  }
98
107
  getSchemaAcl({path}) {
99
108
  return this.get(
@@ -2,24 +2,23 @@ import {createSelector, Selector} from 'reselect';
2
2
  import {Reducer} from 'redux';
3
3
 
4
4
  import '../../services/api';
5
- import {TEvDescribeSchemeResult} from '../../types/api/schema';
6
5
  import {IConsumer} from '../../types/api/consumers';
7
- import {IDescribeRootStateSlice, IDescribeState} from '../../types/store/describe';
8
- import {IResponseError} from '../../types/api/error';
9
- import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
6
+ import {IDescribeRootStateSlice, IDescribeState, IDescribeAction} from '../../types/store/describe';
7
+ import {createRequestActionTypes, createApiRequest} from '../utils';
10
8
 
11
- const FETCH_DESCRIBE = createRequestActionTypes('describe', 'FETCH_DESCRIBE');
9
+ export const FETCH_DESCRIBE = createRequestActionTypes('describe', 'FETCH_DESCRIBE');
10
+ const SET_CURRENT_DESCRIBE_PATH = 'describe/SET_CURRENT_DESCRIBE_PATH';
11
+ const SET_DATA_WAS_NOT_LOADED = 'describe/SET_DATA_WAS_NOT_LOADED';
12
12
 
13
13
  const initialState = {
14
14
  loading: false,
15
15
  wasLoaded: false,
16
16
  data: {},
17
+ currentDescribe: undefined,
18
+ currentDescribePath: undefined,
17
19
  };
18
20
 
19
- const describe: Reducer<
20
- IDescribeState,
21
- ApiRequestAction<typeof FETCH_DESCRIBE, TEvDescribeSchemeResult, IResponseError>
22
- > = (state = initialState, action) => {
21
+ const describe: Reducer<IDescribeState, IDescribeAction> = (state = initialState, action) => {
23
22
  switch (action.type) {
24
23
  case FETCH_DESCRIBE.REQUEST: {
25
24
  return {
@@ -28,52 +27,83 @@ const describe: Reducer<
28
27
  };
29
28
  }
30
29
  case FETCH_DESCRIBE.SUCCESS: {
31
- let newData;
30
+ const data = action.data;
32
31
 
33
- if (action.data.Path) {
32
+ const isCurrentDescribePath = data.Path === state.currentDescribePath;
33
+
34
+ let newData = state.data;
35
+
36
+ if (data.Path) {
34
37
  newData = JSON.parse(JSON.stringify(state.data));
35
- newData[action.data.Path] = action.data;
36
- } else {
37
- newData = state.data;
38
+ newData[data.Path] = data;
39
+ }
40
+
41
+ if (!isCurrentDescribePath) {
42
+ return {
43
+ ...state,
44
+ data: newData,
45
+ };
38
46
  }
39
47
 
40
48
  return {
41
49
  ...state,
42
50
  data: newData,
43
- currentDescribe: action.data,
51
+ currentDescribe: data,
44
52
  loading: false,
45
53
  wasLoaded: true,
46
54
  error: undefined,
47
55
  };
48
56
  }
57
+
49
58
  case FETCH_DESCRIBE.FAILURE: {
59
+ if (action.error?.isCancelled) {
60
+ return state;
61
+ }
62
+
50
63
  return {
51
64
  ...state,
52
65
  error: action.error,
53
66
  loading: false,
54
67
  };
55
68
  }
69
+ case SET_CURRENT_DESCRIBE_PATH: {
70
+ return {
71
+ ...state,
72
+ currentDescribePath: action.data,
73
+ };
74
+ }
75
+ case SET_DATA_WAS_NOT_LOADED: {
76
+ return {
77
+ ...state,
78
+ wasLoaded: false,
79
+ };
80
+ }
56
81
  default:
57
82
  return state;
58
83
  }
59
84
  };
60
85
 
86
+ export const setCurrentDescribePath = (path: string) => {
87
+ return {
88
+ type: SET_CURRENT_DESCRIBE_PATH,
89
+ data: path,
90
+ } as const;
91
+ };
92
+
93
+ export const setDataWasNotLoaded = () => {
94
+ return {
95
+ type: SET_DATA_WAS_NOT_LOADED,
96
+ } as const;
97
+ };
98
+
61
99
  // Consumers selectors
62
- const selectConsumersNames: Selector<IDescribeRootStateSlice, string[] | undefined, [string]> = (
63
- state,
64
- path,
65
- ) => state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules;
66
-
67
- export const selectConsumers: Selector<IDescribeRootStateSlice, IConsumer[]> = createSelector(
68
- selectConsumersNames,
69
- (names = []) => {
70
- const consumers = names.map((name) => {
71
- return {name};
72
- });
73
-
74
- return consumers;
75
- },
76
- );
100
+ const selectConsumersNames = (state: IDescribeRootStateSlice, path: string | undefined) =>
101
+ path
102
+ ? state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules
103
+ : undefined;
104
+
105
+ export const selectConsumers: Selector<IDescribeRootStateSlice, IConsumer[], [string | undefined]> =
106
+ createSelector(selectConsumersNames, (names = []) => names.map((name) => ({name})));
77
107
 
78
108
  export function getDescribe({path}: {path: string}) {
79
109
  return createApiRequest({
@@ -1,4 +1,6 @@
1
1
  export interface IResponseError {
2
- status: number;
3
- statusText: string;
2
+ data?: unknown;
3
+ status?: number;
4
+ statusText?: string;
5
+ isCancelled?: boolean;
4
6
  }
@@ -1,14 +1,34 @@
1
+ import {
2
+ FETCH_DESCRIBE,
3
+ setCurrentDescribePath,
4
+ setDataWasNotLoaded,
5
+ } from '../../store/reducers/describe';
6
+ import {ApiRequestAction} from '../../store/utils';
1
7
  import {IResponseError} from '../api/error';
2
8
  import {TEvDescribeSchemeResult} from '../api/schema';
3
9
 
10
+ export type IDescribeData = Record<string, TEvDescribeSchemeResult>;
11
+
4
12
  export interface IDescribeState {
5
13
  loading: boolean;
6
14
  wasLoaded: boolean;
7
- data: Record<string, TEvDescribeSchemeResult>;
15
+ data: IDescribeData;
8
16
  currentDescribe?: TEvDescribeSchemeResult;
17
+ currentDescribePath?: string;
9
18
  error?: IResponseError;
10
19
  }
11
20
 
21
+ type IDescribeApiRequestAction = ApiRequestAction<
22
+ typeof FETCH_DESCRIBE,
23
+ TEvDescribeSchemeResult,
24
+ IResponseError
25
+ >;
26
+
27
+ export type IDescribeAction =
28
+ | IDescribeApiRequestAction
29
+ | ReturnType<typeof setCurrentDescribePath>
30
+ | ReturnType<typeof setDataWasNotLoaded>;
31
+
12
32
  export interface IDescribeRootStateSlice {
13
33
  describe: IDescribeState;
14
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -1,130 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import cn from 'bem-cn-lite';
4
- import {connect} from 'react-redux';
5
- import {Loader} from '@gravity-ui/uikit';
6
- import JSONTree from 'react-json-inspector';
7
-
8
- import {getDescribe} from '../../../../store/reducers/describe';
9
-
10
- import './Describe.scss';
11
- import 'react-json-inspector/json-inspector.css';
12
- import {AutoFetcher} from '../../../../utils/autofetcher';
13
-
14
- const b = cn('kv-describe');
15
-
16
- const expandMap = new Map();
17
-
18
- class Describe extends React.Component {
19
- static propTypes = {
20
- error: PropTypes.string,
21
- data: PropTypes.array,
22
- loading: PropTypes.bool,
23
- wasLoaded: PropTypes.bool,
24
- autorefresh: PropTypes.bool,
25
- tenant: PropTypes.string,
26
- };
27
-
28
- autofetcher;
29
-
30
- componentDidMount() {
31
- const {autorefresh} = this.props;
32
- this.autofetcher = new AutoFetcher();
33
- this.fetchData();
34
- if (autorefresh) {
35
- this.autofetcher.start();
36
- this.autofetcher.fetch(() => this.fetchData);
37
- }
38
- }
39
-
40
- componentDidUpdate(prevProps) {
41
- const {autorefresh} = this.props;
42
-
43
- if (autorefresh && !prevProps.autorefresh) {
44
- this.fetchData();
45
- this.autofetcher.stop();
46
- this.autofetcher.start();
47
- this.autofetcher.fetch(() => this.fetchData());
48
- }
49
- if (!autorefresh && prevProps.autorefresh) {
50
- this.autofetcher.stop();
51
- }
52
- }
53
-
54
- componentWillUnmount() {
55
- this.autofetcher.stop();
56
- }
57
-
58
- fetchData() {
59
- const {getDescribe, tenant, currentSchemaPath} = this.props;
60
- const path = currentSchemaPath || tenant;
61
- getDescribe({path});
62
- }
63
-
64
- renderDescribeJson = () => {
65
- const {data} = this.props;
66
-
67
- return (
68
- <JSONTree
69
- data={data}
70
- className={b('tree')}
71
- onClick={({path}) => {
72
- const newValue = !(expandMap.get(path) || false);
73
- expandMap.set(path, newValue);
74
- }}
75
- searchOptions={{
76
- debounceTime: 300,
77
- }}
78
- isExpanded={(keypath) => {
79
- return expandMap.get(keypath) || false;
80
- }}
81
- />
82
- );
83
- };
84
-
85
- render() {
86
- const {error, loading, data, wasLoaded} = this.props;
87
-
88
- if (loading && !wasLoaded) {
89
- return (
90
- <div className={b('loader-container')}>
91
- <Loader size="m" />
92
- </div>
93
- );
94
- }
95
-
96
- if (error) {
97
- return <div className={b('message-container')}>{error.data || error}</div>;
98
- }
99
-
100
- if (!loading && !data) {
101
- return <div className={b('message-container')}>Empty</div>;
102
- }
103
-
104
- return (
105
- <div className={b()}>
106
- <div className={b('result')}>{this.renderDescribeJson()}</div>
107
- </div>
108
- );
109
- }
110
- }
111
-
112
- const mapStateToProps = (state) => {
113
- const {loading, error, currentDescribe, wasLoaded} = state.describe;
114
- const {autorefresh, currentSchemaPath} = state.schema;
115
-
116
- return {
117
- data: currentDescribe,
118
- loading,
119
- error,
120
- wasLoaded,
121
- autorefresh,
122
- currentSchemaPath,
123
- };
124
- };
125
-
126
- const mapDispatchToProps = {
127
- getDescribe,
128
- };
129
-
130
- export default connect(mapStateToProps, mapDispatchToProps)(Describe);