ydb-embedded-ui 2.4.3 → 2.4.4

Sign up to get free protection for your applications and to get access to all the features.
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);