ydb-embedded-ui 1.1.3 → 1.2.2

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,32 @@
1
1
  # Changelog
2
2
 
3
+ ### [1.2.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.1...v1.2.2) (2022-05-04)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * code-review ([288fda3](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/288fda3cd207908e9b5c0486c4d486c6f2e17dd4))
9
+ * reducer clusterInfo should not be used ([1cafcbf](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/1cafcbfb15f668b100cf6628b540b7cd234f6024))
10
+
11
+ ### [1.2.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.0...v1.2.1) (2022-04-27)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **Vdisk:** should not fail if no node id passed ([d66686d](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/d66686d0cbd9f61c4e106f6775db2fca226c922f))
17
+
18
+ ## [1.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.3...v1.2.0) (2022-04-26)
19
+
20
+
21
+ ### Features
22
+
23
+ * **Storage:** smoother loading state for storage table ([f7f38c4](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/f7f38c455dd9abc3f898048081e90af9b460f922))
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * prevent ghost autofetch ([153d829](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/153d8291d315f1dab001a69981a12e30d3d2aea9))
29
+
3
30
  ### [1.1.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.2...v1.1.3) (2022-04-20)
4
31
 
5
32
 
@@ -0,0 +1,38 @@
1
+ .table-skeleton {
2
+ width: 100%;
3
+
4
+ &__row {
5
+ display: flex;
6
+ align-items: center;
7
+
8
+ height: var(--data-table-row-height);
9
+
10
+ .yc-skeleton {
11
+ height: var(--yc-text-body2-line-height);
12
+ }
13
+ }
14
+
15
+ &__col-1 {
16
+ width: 10%;
17
+ margin-right: 5%;
18
+ }
19
+
20
+ &__col-2 {
21
+ width: 7%;
22
+ margin-right: 5%;
23
+ }
24
+
25
+ &__col-3,
26
+ &__col-4 {
27
+ width: 5%;
28
+ margin-right: 5%;
29
+ }
30
+
31
+ &__col-5 {
32
+ width: 20%;
33
+ }
34
+
35
+ &__col-full {
36
+ width: 100%;
37
+ }
38
+ }
@@ -0,0 +1,29 @@
1
+ import { FC } from 'react';
2
+ import block from 'bem-cn-lite';
3
+ import { Skeleton } from '@yandex-cloud/uikit';
4
+
5
+ import './TableSkeleton.scss';
6
+
7
+ const b = block('table-skeleton');
8
+
9
+ interface TableSkeletonProps {
10
+ className?: string;
11
+ rows?: number;
12
+ }
13
+
14
+ export const TableSkeleton: FC<TableSkeletonProps> = ({ rows = 2, className }) => (
15
+ <div className={b(null, className)}>
16
+ <div className={b('row')}>
17
+ <Skeleton className={b('col-1')} />
18
+ <Skeleton className={b('col-2')} />
19
+ <Skeleton className={b('col-3')} />
20
+ <Skeleton className={b('col-4')} />
21
+ <Skeleton className={b('col-5')} />
22
+ </div>
23
+ {[...new Array(rows)].map((_, index) => (
24
+ <div className={b('row')} key={`skeleton-row-${index}`}>
25
+ <Skeleton className={b('col-full')} />
26
+ </div>
27
+ ))}
28
+ </div>
29
+ );
@@ -17,6 +17,8 @@ registerLanguages();
17
17
  class App extends React.Component {
18
18
  static propTypes = {
19
19
  isAuthenticated: PropTypes.bool,
20
+ singleClusterMode: PropTypes.bool,
21
+ clusterName: PropTypes.string,
20
22
  children: PropTypes.node,
21
23
  };
22
24
 
@@ -40,9 +42,10 @@ class App extends React.Component {
40
42
  }
41
43
 
42
44
  renderContentWithNavigation() {
45
+ const {singleClusterMode, clusterName} = this.props;
43
46
  return (
44
47
  <AsideNavigation>
45
- <Content singleClusterMode={this.props.singleClusterMode} />
48
+ <Content singleClusterMode={singleClusterMode} clusterName={clusterName} />
46
49
  <div id="fullscreen-root"></div>
47
50
  </AsideNavigation>
48
51
  );
@@ -58,6 +61,7 @@ function mapStateToProps(state) {
58
61
  isAuthenticated: state.authentication.isAuthenticated,
59
62
  internalUser: state.authentication.user,
60
63
  singleClusterMode: state.singleClusterMode,
64
+ clusterName: state.cluster.data?.Name,
61
65
  };
62
66
  }
63
67
 
@@ -59,7 +59,7 @@ export function Content(props) {
59
59
  };
60
60
  return (
61
61
  <React.Fragment>
62
- {!isClustersPage && <Header />}
62
+ {!isClustersPage && <Header clusterName={props.clusterName} />}
63
63
  <main className={b('main')}>{renderRoute()}</main>
64
64
  <ReduxTooltip />
65
65
  <AppIcons />
@@ -70,6 +70,7 @@ export function Content(props) {
70
70
  Content.propTypes = {
71
71
  singleClusterMode: PropTypes.bool,
72
72
  children: PropTypes.node,
73
+ clusterName: PropTypes.string,
73
74
  };
74
75
 
75
76
  function ContentWrapper(props) {
@@ -26,16 +26,16 @@ function ClusterName({name}: {name: string}) {
26
26
  );
27
27
  }
28
28
 
29
- function Header() {
29
+ interface HeaderProps {
30
+ clusterName: string
31
+ }
32
+
33
+ function Header({clusterName}: HeaderProps) {
30
34
  const dispatch = useDispatch();
31
35
  const {data: host}: {data: {ClusterName?: string}} = useSelector((state: any) => state.host);
32
36
  const {singleClusterMode, header}: {singleClusterMode: boolean; header: HeaderItemType[]} =
33
37
  useSelector((state: any) => state);
34
38
 
35
- const clusterName: string = useSelector(
36
- (state: any) => state.cluster.data?.Name || state.clusterInfo.title,
37
- );
38
-
39
39
  const location = useLocation();
40
40
  const history = useHistory();
41
41
 
@@ -3,10 +3,11 @@ import PropTypes from 'prop-types';
3
3
  import {connect} from 'react-redux';
4
4
  import cn from 'bem-cn-lite';
5
5
  import DataTable from '@yandex-cloud/react-data-table';
6
- import {Loader, RadioButton, Label} from '@yandex-cloud/uikit';
6
+ import {RadioButton, Label} from '@yandex-cloud/uikit';
7
7
 
8
8
  import StorageFilter from './StorageFilter/StorageFilter';
9
9
  import {AutoFetcher} from '../../utils/autofetcher';
10
+ import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton';
10
11
 
11
12
  import {
12
13
  getStorageInfo,
@@ -68,17 +69,14 @@ class Storage extends React.Component {
68
69
  storageType,
69
70
  setHeader,
70
71
  getNodesList,
71
- getStorageInfo,
72
72
  } = this.props;
73
73
 
74
74
  this.autofetcher = new AutoFetcher();
75
75
  getNodesList();
76
76
  if (tenant || nodeId) {
77
77
  setVisibleEntities(VisibleEntities.All);
78
- getStorageInfo({
79
- tenant,
78
+ this.getStorageInfo({
80
79
  filter: FILTER_OPTIONS.All,
81
- nodeId,
82
80
  type: storageType,
83
81
  });
84
82
  } else {
@@ -88,16 +86,12 @@ class Storage extends React.Component {
88
86
  link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.storage.id}),
89
87
  },
90
88
  ]);
91
- getStorageInfo({
92
- tenant,
93
- nodeId,
89
+ this.getStorageInfo({
94
90
  filter: FILTER_OPTIONS.Missing,
95
91
  type: storageType,
96
92
  });
97
93
  this.autofetcher.fetch(() =>
98
- getStorageInfo({
99
- tenant,
100
- nodeId,
94
+ this.getStorageInfo({
101
95
  filter: FILTER_OPTIONS.Missing,
102
96
  type: storageType,
103
97
  }),
@@ -107,30 +101,23 @@ class Storage extends React.Component {
107
101
 
108
102
  componentDidUpdate(prevProps) {
109
103
  const {
110
- tenant,
111
104
  visibleEntities,
112
- getStorageInfo,
113
- nodeId,
114
105
  storageType,
115
106
  autorefresh,
116
107
  database,
117
108
  } = this.props;
118
109
 
119
110
  const startFetch = () => {
120
- getStorageInfo({
121
- tenant,
111
+ this.getStorageInfo({
122
112
  filter: FILTER_OPTIONS[visibleEntities],
123
- nodeId,
124
113
  type: storageType,
125
114
  });
126
115
 
127
116
  this.autofetcher.stop();
128
117
  this.autofetcher.start();
129
118
  this.autofetcher.fetch(() =>
130
- getStorageInfo({
131
- tenant,
119
+ this.getStorageInfo({
132
120
  filter: FILTER_OPTIONS[visibleEntities],
133
- nodeId,
134
121
  type: storageType,
135
122
  }),
136
123
  );
@@ -157,11 +144,25 @@ class Storage extends React.Component {
157
144
  this.props.setInitialState();
158
145
  }
159
146
 
147
+ getStorageInfo(data) {
148
+ const {
149
+ tenant,
150
+ nodeId,
151
+ getStorageInfo,
152
+ } = this.props;
153
+
154
+ getStorageInfo({
155
+ tenant,
156
+ nodeId,
157
+ ...data,
158
+ }, {
159
+ concurrentId: 'getStorageInfo',
160
+ });
161
+ }
162
+
160
163
  renderLoader() {
161
164
  return (
162
- <div className={b('loader')}>
163
- <Loader size="m" />
164
- </div>
165
+ <TableSkeleton className={b('loader')}/>
165
166
  );
166
167
  }
167
168
 
@@ -200,8 +201,9 @@ class Storage extends React.Component {
200
201
  };
201
202
 
202
203
  renderControls() {
203
- const {setStorageFilter, visibleEntities, storageType, flatListStorageEntities} =
204
+ const {setStorageFilter, visibleEntities, storageType, flatListStorageEntities, loading, wasLoaded} =
204
205
  this.props;
206
+ const showLoader = loading && !wasLoaded;
205
207
  return (
206
208
  <div className={b('controls')}>
207
209
  <div className={b('search')}>
@@ -232,25 +234,28 @@ class Storage extends React.Component {
232
234
  </RadioButton>
233
235
  <Label theme="info" size="m">{`${
234
236
  storageType === StorageTypes.groups ? 'Groups' : 'Nodes'
235
- }: ${flatListStorageEntities.length}`}</Label>
237
+ }: ${(showLoader) ? '...' : flatListStorageEntities.length}`}</Label>
236
238
  </div>
237
239
  );
238
240
  }
239
241
 
240
242
  render() {
241
243
  const {loading, wasLoaded, error} = this.props;
242
- if (loading && !wasLoaded) {
243
- return this.renderLoader();
244
- } else if (error) {
245
- return <div>{error.statusText}</div>;
246
- } else {
247
- return (
248
- <div className={b()}>
249
- {this.renderControls()}
250
- {this.renderDataTable()}
251
- </div>
252
- );
253
- }
244
+ const showLoader = loading && !wasLoaded;
245
+
246
+ return (
247
+ <div className={b()}>
248
+ {this.renderControls()}
249
+ {error && (
250
+ <div>{error.statusText}</div>
251
+ )}
252
+ {showLoader ? (
253
+ this.renderLoader()
254
+ ) : (
255
+ this.renderDataTable()
256
+ )}
257
+ </div>
258
+ );
254
259
  }
255
260
  }
256
261
 
@@ -43,12 +43,6 @@
43
43
  }
44
44
  }
45
45
 
46
- &__loader {
47
- display: flex;
48
- justify-content: center;
49
-
50
- margin-top: 100px;
51
- }
52
46
  .entity-status {
53
47
  justify-content: center;
54
48
  }
@@ -235,11 +235,18 @@ function Vdisk(props) {
235
235
  <DiskStateProgressBar
236
236
  diskAllocatedPercent={vdiskAllocatedPercent}
237
237
  severity={severity}
238
- href={createHref(
239
- routes.node,
240
- {id: props.NodeId, activeTab: STRUCTURE},
241
- {pdiskId: props.PDisk?.PDiskId, vdiskId: stringifyVdiskId(props.VDiskId)},
242
- )}
238
+ href={
239
+ props.NodeId
240
+ ? createHref(
241
+ routes.node,
242
+ {id: props.NodeId, activeTab: STRUCTURE},
243
+ {
244
+ pdiskId: props.PDisk?.PDiskId,
245
+ vdiskId: stringifyVdiskId(props.VDiskId),
246
+ },
247
+ )
248
+ : undefined
249
+ }
243
250
  />
244
251
  </div>
245
252
  </React.Fragment>
@@ -35,7 +35,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
35
35
  storage: true,
36
36
  });
37
37
  }
38
- getStorageInfo({tenant, filter, nodeId, type}) {
38
+ getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}) {
39
39
  return this.get(
40
40
  this.getPath(
41
41
  `/viewer/json/${type === StorageTypes.nodes ? 'nodes' : 'storage'}?enums=true`,
@@ -45,6 +45,9 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
45
45
  node_id: nodeId,
46
46
  with: filter,
47
47
  },
48
+ {
49
+ concurrentId,
50
+ },
48
51
  );
49
52
  }
50
53
  getPdiskInfo(nodeId, pdiskId) {
@@ -20,7 +20,6 @@ import tablet from './tablet';
20
20
  import executeQuery from './executeQuery';
21
21
  import explainQuery from './explainQuery';
22
22
  import tabletsFilters from './tabletsFilters';
23
- import clusterInfo from './clusterInfo';
24
23
  import settings from './settings';
25
24
  import preview from './preview';
26
25
  import nodesList from './clusterNodes';
@@ -63,7 +62,6 @@ export const rootReducer = {
63
62
  explainQuery,
64
63
  tabletsFilters,
65
64
  heatmap,
66
- clusterInfo,
67
65
  settings,
68
66
  preview,
69
67
  nodesList,
@@ -74,6 +74,8 @@ const storage = function z(state = initialState, action) {
74
74
  return {
75
75
  ...state,
76
76
  visible: action.data,
77
+ wasLoaded: false,
78
+ error: undefined,
77
79
  };
78
80
  }
79
81
  case SET_STORAGE_TYPE: {
@@ -81,6 +83,7 @@ const storage = function z(state = initialState, action) {
81
83
  ...state,
82
84
  type: action.data,
83
85
  wasLoaded: false,
86
+ error: undefined,
84
87
  };
85
88
  }
86
89
  default:
@@ -94,9 +97,9 @@ export function setInitialState() {
94
97
  };
95
98
  }
96
99
 
97
- export function getStorageInfo({tenant, filter, nodeId, type}) {
100
+ export function getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}) {
98
101
  return createApiRequest({
99
- request: window.api.getStorageInfo({tenant, filter, nodeId, type}),
102
+ request: window.api.getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}),
100
103
  actions: FETCH_STORAGE,
101
104
  });
102
105
  }
@@ -3,6 +3,7 @@ export class AutoFetcher {
3
3
  this.timeout = AutoFetcher.DEFAULT_TIMEOUT;
4
4
  this.active = true;
5
5
  this.timer = undefined;
6
+ this.launchCounter = 0;
6
7
  }
7
8
 
8
9
  wait(ms: number) {
@@ -16,6 +17,8 @@ export class AutoFetcher {
16
17
  return;
17
18
  }
18
19
 
20
+ const currentLaunch = this.launchCounter;
21
+
19
22
  await this.wait(this.timeout);
20
23
 
21
24
  if (this.active) {
@@ -23,6 +26,12 @@ export class AutoFetcher {
23
26
  await request();
24
27
  const finishTs = Date.now();
25
28
 
29
+ if (currentLaunch !== this.launchCounter) {
30
+ // autofetcher was restarted while request was in progress
31
+ // stop further fetches, we are in deprecated thread
32
+ return;
33
+ }
34
+
26
35
  const responseTime = finishTs - startTs;
27
36
  const nextTimeout =
28
37
  responseTime > AutoFetcher.MIN_TIMEOUT ? responseTime : AutoFetcher.MIN_TIMEOUT;
@@ -40,6 +49,7 @@ export class AutoFetcher {
40
49
  this.active = false;
41
50
  }
42
51
  start() {
52
+ this.launchCounter++;
43
53
  this.active = true;
44
54
  }
45
55
 
@@ -48,4 +58,5 @@ export class AutoFetcher {
48
58
  timeout: number;
49
59
  active: boolean;
50
60
  timer: undefined | ReturnType<typeof setTimeout>;
61
+ launchCounter: number;
51
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.1.3",
3
+ "version": "1.2.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -1,48 +0,0 @@
1
- import {createRequestActionTypes, createApiRequest} from '../utils';
2
- import '../../services/api';
3
-
4
- const FETCH_CLUSTER = createRequestActionTypes('cluster', 'FETCH_CLUSTER');
5
-
6
- const initialState = {loading: false};
7
-
8
- const clusterInfo = function (state = initialState, action) {
9
- switch (action.type) {
10
- case FETCH_CLUSTER.REQUEST: {
11
- return {
12
- ...state,
13
- loading: true,
14
- };
15
- }
16
- case FETCH_CLUSTER.SUCCESS: {
17
- const {data = {}} = action;
18
-
19
- return {
20
- ...state,
21
- ...data,
22
- loading: false,
23
- error: undefined,
24
- };
25
- }
26
- case FETCH_CLUSTER.FAILURE: {
27
- return {
28
- ...state,
29
- error: action.error,
30
- loading: false,
31
- };
32
- }
33
- default:
34
- return state;
35
- }
36
- };
37
-
38
- export function getCluster(name) {
39
- return createApiRequest({
40
- request: window.api.getClustersList(),
41
- actions: FETCH_CLUSTER,
42
- dataHandler: ({clusters = []}) => {
43
- return clusters.filter((item) => item.name === name)[0];
44
- },
45
- });
46
- }
47
-
48
- export default clusterInfo;