ydb-embedded-ui 1.1.2 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ### [1.2.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.0...v1.2.1) (2022-04-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **Vdisk:** should not fail if no node id passed ([d66686d](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/d66686d0cbd9f61c4e106f6775db2fca226c922f))
9
+
10
+ ## [1.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.3...v1.2.0) (2022-04-26)
11
+
12
+
13
+ ### Features
14
+
15
+ * **Storage:** smoother loading state for storage table ([f7f38c4](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/f7f38c455dd9abc3f898048081e90af9b460f922))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * prevent ghost autofetch ([153d829](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/153d8291d315f1dab001a69981a12e30d3d2aea9))
21
+
22
+ ### [1.1.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.2...v1.1.3) (2022-04-20)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * should prepare internal link correctly ([3da36e2](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/3da36e22f6adbce6a1b14ac1afb0fb4aa46bb75f))
28
+
3
29
  ### [1.1.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.1...v1.1.2) (2022-04-19)
4
30
 
5
31
 
@@ -32,7 +32,7 @@ class FullNodeViewer extends React.Component {
32
32
  render() {
33
33
  const {node, className, additionalNodesInfo={}} = this.props;
34
34
  const nodeHref = additionalNodesInfo.getNodeRef
35
- ? additionalNodesInfo.getNodeRef(node)
35
+ ? additionalNodesInfo.getNodeRef(node) + 'internal'
36
36
  : undefined;
37
37
 
38
38
  const commonInfo = [
@@ -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
+ );
@@ -68,11 +68,11 @@ function getColumns({
68
68
  header: vDiskTableColumnsNames[VDiskTableColumnsIds.slotId],
69
69
  width: 100,
70
70
  render: ({value, row}) => {
71
- let vdiskInternalViewerLink: string | undefined;
71
+ let vdiskInternalViewerLink = '';
72
72
 
73
73
  if (nodeHref && value !== undefined) {
74
74
  vdiskInternalViewerLink +=
75
- nodeHref + '/actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(value);
75
+ nodeHref + 'actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(value);
76
76
  }
77
77
 
78
78
  return (
@@ -195,10 +195,10 @@ export function PDisk(props: PDiskProps) {
195
195
  SerialNumber,
196
196
  } = data;
197
197
 
198
- let pDiskInternalViewerLink: string | undefined;
198
+ let pDiskInternalViewerLink = '';
199
199
 
200
200
  if (nodeHref) {
201
- pDiskInternalViewerLink += nodeHref + '/actors/pdisks/pdisk' + pad9(PDiskId);
201
+ pDiskInternalViewerLink += nodeHref + 'actors/pdisks/pdisk' + pad9(PDiskId);
202
202
  }
203
203
 
204
204
  const pdiskInfo: any = [
@@ -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) {
@@ -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.2",
3
+ "version": "1.2.1",
4
4
  "files": [
5
5
  "dist"
6
6
  ],