ydb-embedded-ui 1.10.1 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +12 -9
  3. package/dist/components/InfoViewer/InfoViewer.scss +33 -9
  4. package/dist/components/InfoViewer/InfoViewer.tsx +43 -0
  5. package/dist/components/InfoViewer/index.ts +1 -0
  6. package/dist/components/InfoViewer/utils.ts +21 -11
  7. package/dist/components/Stack/Stack.scss +55 -0
  8. package/dist/components/Stack/Stack.tsx +35 -0
  9. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +2 -0
  10. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +5 -0
  11. package/dist/containers/Storage/Pdisk/Pdisk.scss +2 -19
  12. package/dist/containers/Storage/Pdisk/Pdisk.tsx +30 -33
  13. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +40 -0
  14. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +25 -3
  15. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +31 -7
  16. package/dist/containers/Storage/Vdisk/Vdisk.js +63 -64
  17. package/dist/containers/Storage/Vdisk/Vdisk.scss +9 -28
  18. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +163 -0
  19. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +15 -14
  20. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -2
  21. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +164 -42
  22. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +18 -0
  23. package/dist/services/api.js +0 -1
  24. package/dist/setupTests.js +8 -0
  25. package/dist/store/reducers/executeQuery.js +3 -2
  26. package/dist/store/reducers/settings.js +20 -13
  27. package/dist/types/api/schema.ts +117 -4
  28. package/dist/types/api/storage.ts +121 -0
  29. package/dist/types/index.ts +1 -0
  30. package/dist/utils/constants.js +4 -0
  31. package/dist/utils/index.js +28 -4
  32. package/dist/utils/pdisk.ts +2 -2
  33. package/package.json +28 -5
  34. package/dist/components/InfoViewer/InfoViewer.js +0 -47
  35. package/dist/index.test.js +0 -5
@@ -1,13 +1,13 @@
1
1
  import React, {useEffect, useState, useRef, useMemo} from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cn from 'bem-cn-lite';
4
- import _ from 'lodash';
5
- import {Popup} from '@yandex-cloud/uikit';
4
+ import {Label, Popup} from '@yandex-cloud/uikit';
6
5
 
7
6
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
8
7
  import routes, {createHref} from '../../../routes';
9
8
  import {stringifyVdiskId, getPDiskId} from '../../../utils';
10
9
  import {getPDiskType} from '../../../utils/pdisk';
10
+ import {InfoViewer} from '../../../components/InfoViewer';
11
11
  import DiskStateProgressBar, {
12
12
  diskProgressColors,
13
13
  } from '../DiskStateProgressBar/DiskStateProgressBar';
@@ -26,6 +26,9 @@ const propTypes = {
26
26
  FrontQueues: PropTypes.string,
27
27
  Replicated: PropTypes.bool,
28
28
  PoolName: PropTypes.string,
29
+ VDiskId: PropTypes.object,
30
+ DonorMode: PropTypes.bool,
31
+ nodes: PropTypes.object,
29
32
  };
30
33
 
31
34
  const stateSeverity = {
@@ -53,7 +56,7 @@ function Vdisk(props) {
53
56
 
54
57
  // determine disk status severity
55
58
  useEffect(() => {
56
- const {DiskSpace, VDiskState, FrontQueues, Replicated} = props;
59
+ const {DiskSpace, VDiskState, FrontQueues, Replicated, DonorMode} = props;
57
60
 
58
61
  // if the disk is not available, this determines its status severity regardless of other features
59
62
  if (!VDiskState) {
@@ -66,7 +69,10 @@ function Vdisk(props) {
66
69
  const FrontQueuesSeverity = Math.min(colorSeverity.Orange, getColorSeverity(FrontQueues));
67
70
 
68
71
  let newSeverity = Math.max(DiskSpaceSeverity, VDiskSpaceSeverity, FrontQueuesSeverity);
69
- if (!Replicated && newSeverity === colorSeverity.Green) {
72
+
73
+ // donors are always in the not replicated state since they are leftovers
74
+ // painting them blue is useless
75
+ if (!Replicated && !DonorMode && newSeverity === colorSeverity.Green) {
70
76
  newSeverity = colorSeverity.Blue;
71
77
  }
72
78
 
@@ -95,62 +101,62 @@ function Vdisk(props) {
95
101
  ReadThroughput,
96
102
  WriteThroughput,
97
103
  } = props;
98
- const vdiskData = [{property: 'VDisk', value: stringifyVdiskId(VDiskId)}];
99
- vdiskData.push({property: 'State', value: VDiskState ?? 'not available'});
100
- PoolName && vdiskData.push({property: 'StoragePool', value: PoolName});
104
+ const vdiskData = [{label: 'VDisk', value: stringifyVdiskId(VDiskId)}];
105
+ vdiskData.push({label: 'State', value: VDiskState ?? 'not available'});
106
+ PoolName && vdiskData.push({label: 'StoragePool', value: PoolName});
101
107
 
102
108
  SatisfactionRank &&
103
109
  SatisfactionRank.FreshRank?.Flag !== diskProgressColors[colorSeverity.Green] &&
104
110
  vdiskData.push({
105
- property: 'Fresh',
111
+ label: 'Fresh',
106
112
  value: SatisfactionRank.FreshRank.Flag,
107
113
  });
108
114
 
109
115
  SatisfactionRank &&
110
116
  SatisfactionRank.LevelRank?.Flag !== diskProgressColors[colorSeverity.Green] &&
111
117
  vdiskData.push({
112
- property: 'Level',
118
+ label: 'Level',
113
119
  value: SatisfactionRank.LevelRank.Flag,
114
120
  });
115
121
 
116
122
  SatisfactionRank &&
117
123
  SatisfactionRank.FreshRank?.RankPercent &&
118
124
  vdiskData.push({
119
- property: 'Fresh',
125
+ label: 'Fresh',
120
126
  value: SatisfactionRank.FreshRank.RankPercent,
121
127
  });
122
128
 
123
129
  SatisfactionRank &&
124
130
  SatisfactionRank.LevelRank?.RankPercent &&
125
131
  vdiskData.push({
126
- property: 'Level',
132
+ label: 'Level',
127
133
  value: SatisfactionRank.LevelRank.RankPercent,
128
134
  });
129
135
 
130
136
  DiskSpace &&
131
137
  DiskSpace !== diskProgressColors[colorSeverity.Green] &&
132
- vdiskData.push({property: 'Space', value: DiskSpace});
138
+ vdiskData.push({label: 'Space', value: DiskSpace});
133
139
 
134
140
  FrontQueues &&
135
141
  FrontQueues !== diskProgressColors[colorSeverity.Green] &&
136
- vdiskData.push({property: 'FrontQueues', value: FrontQueues});
142
+ vdiskData.push({label: 'FrontQueues', value: FrontQueues});
137
143
 
138
- !Replicated && vdiskData.push({property: 'Replicated', value: 'NO'});
144
+ !Replicated && vdiskData.push({label: 'Replicated', value: 'NO'});
139
145
 
140
- UnsyncedVDisks && vdiskData.push({property: 'UnsyncVDisks', value: UnsyncedVDisks});
146
+ UnsyncedVDisks && vdiskData.push({label: 'UnsyncVDisks', value: UnsyncedVDisks});
141
147
 
142
148
  Boolean(Number(AllocatedSize)) &&
143
149
  vdiskData.push({
144
- property: 'Allocated',
150
+ label: 'Allocated',
145
151
  value: bytesToGB(AllocatedSize),
146
152
  });
147
153
 
148
154
  Boolean(Number(ReadThroughput)) &&
149
- vdiskData.push({property: 'Read', value: bytesToSpeed(ReadThroughput)});
155
+ vdiskData.push({label: 'Read', value: bytesToSpeed(ReadThroughput)});
150
156
 
151
157
  Boolean(Number(WriteThroughput)) &&
152
158
  vdiskData.push({
153
- property: 'Write',
159
+ label: 'Write',
154
160
  value: bytesToSpeed(WriteThroughput),
155
161
  });
156
162
 
@@ -165,61 +171,54 @@ function Vdisk(props) {
165
171
  diskProgressColors[colorSeverity.Yellow],
166
172
  ];
167
173
  if (PDisk && nodes) {
168
- const pdiskData = [{property: 'PDisk', value: getPDiskId(PDisk)}];
174
+ const pdiskData = [{label: 'PDisk', value: getPDiskId(PDisk)}];
169
175
  pdiskData.push({
170
- property: 'State',
176
+ label: 'State',
171
177
  value: PDisk.State || 'not available',
172
178
  });
173
- pdiskData.push({property: 'Type', value: getPDiskType(PDisk) || 'unknown'});
174
- PDisk.NodeId && pdiskData.push({property: 'Node Id', value: PDisk.NodeId});
179
+ pdiskData.push({label: 'Type', value: getPDiskType(PDisk) || 'unknown'});
180
+ PDisk.NodeId && pdiskData.push({label: 'Node Id', value: PDisk.NodeId});
175
181
  PDisk.NodeId &&
176
182
  nodes[PDisk.NodeId] &&
177
- pdiskData.push({property: 'Host', value: nodes[PDisk.NodeId]});
178
- PDisk.Path && pdiskData.push({property: 'Path', value: PDisk.Path});
183
+ pdiskData.push({label: 'Host', value: nodes[PDisk.NodeId]});
184
+ PDisk.Path && pdiskData.push({label: 'Path', value: PDisk.Path});
179
185
  pdiskData.push({
180
- property: 'Available',
186
+ label: 'Available',
181
187
  value: `${bytesToGB(PDisk.AvailableSize)} of ${bytesToGB(PDisk.TotalSize)}`,
182
188
  });
183
189
  errorColors.includes(PDisk.Realtime) &&
184
- pdiskData.push({property: 'Realtime', value: PDisk.Realtime});
190
+ pdiskData.push({label: 'Realtime', value: PDisk.Realtime});
185
191
  errorColors.includes(PDisk.Device) &&
186
- pdiskData.push({property: 'Device', value: PDisk.Device});
192
+ pdiskData.push({label: 'Device', value: PDisk.Device});
187
193
  return pdiskData;
188
194
  }
189
195
  return null;
190
196
  };
191
197
  /* eslint-enable */
192
198
 
193
- const renderPopup = () => {
194
- const vdiskData = prepareVdiskData();
195
- const pdiskData = preparePdiskData();
196
- return (
197
- <Popup
198
- className={b('popup-wrapper')}
199
- anchorRef={anchor}
200
- open={isPopupVisible}
201
- placement={['top', 'bottom']}
202
- hasArrow
203
- >
204
- <div className={b('popup-content')}>
205
- <div className={b('popup-section-name')}>VDisk</div>
206
- {_.map(vdiskData, (row) => (
207
- <React.Fragment key={row.property}>
208
- <div className={b('property')}>{row.property}</div>
209
- <div className={b('value')}>{row.value}</div>
210
- </React.Fragment>
211
- ))}
212
- <div className={b('popup-section-name')}>PDisk</div>
213
- {_.map(pdiskData, (row) => (
214
- <React.Fragment key={row.property}>
215
- <div className={b('property')}>{row.property}</div>
216
- <div className={b('value')}>{row.value}</div>
217
- </React.Fragment>
218
- ))}
219
- </div>
220
- </Popup>
221
- );
222
- };
199
+ const renderPopup = () => (
200
+ <Popup
201
+ className={b('popup-wrapper')}
202
+ anchorRef={anchor}
203
+ open={isPopupVisible}
204
+ placement={['top', 'bottom']}
205
+ // bigger offset for easier switching to neighbour nodes
206
+ // matches the default offset for popup with arrow out of a sense of beauty
207
+ offset={[0, 12]}
208
+ >
209
+ {props.DonorMode && <Label className={b('donor-label')}>Donor</Label>}
210
+ <InfoViewer
211
+ title="VDisk"
212
+ info={prepareVdiskData()}
213
+ size="s"
214
+ />
215
+ <InfoViewer
216
+ title="PDisk"
217
+ info={preparePdiskData()}
218
+ size="s"
219
+ />
220
+ </Popup>
221
+ );
223
222
 
224
223
  const vdiskAllocatedPercent = useMemo(() => {
225
224
  const {AvailableSize, AllocatedSize, PDisk} = props;
@@ -243,13 +242,13 @@ function Vdisk(props) {
243
242
  href={
244
243
  props.NodeId
245
244
  ? createHref(
246
- routes.node,
247
- {id: props.NodeId, activeTab: STRUCTURE},
248
- {
249
- pdiskId: props.PDisk?.PDiskId,
250
- vdiskId: stringifyVdiskId(props.VDiskId),
251
- },
252
- )
245
+ routes.node,
246
+ {id: props.NodeId, activeTab: STRUCTURE},
247
+ {
248
+ pdiskId: props.PDisk?.PDiskId,
249
+ vdiskId: stringifyVdiskId(props.VDiskId),
250
+ },
251
+ )
253
252
  : undefined
254
253
  }
255
254
  />
@@ -1,35 +1,16 @@
1
1
  .vdisk-storage {
2
- display: flex;
3
- flex-grow: 1;
4
- align-items: center;
5
-
6
- max-width: 200px;
7
- margin-right: 10px;
8
-
9
- cursor: pointer;
10
-
11
- &:last-child {
12
- margin-right: 0px;
13
- }
14
2
  &__popup-wrapper {
15
- padding: 5px 10px;
16
- }
17
- &__popup-content {
18
- display: grid;
19
- justify-items: stretch;
20
- column-gap: 5px;
21
- }
22
- &__popup-section-name {
23
- grid-column: 1 / 3;
3
+ padding: 12px;
24
4
 
25
- margin: 5px 0;
5
+ .info-viewer + .info-viewer {
6
+ margin-top: 8px;
7
+ padding-top: 8px;
26
8
 
27
- font-weight: 500;
28
- text-align: center;
29
-
30
- border-bottom: 1px solid var(--yc-color-line-generic);
9
+ border-top: 1px solid var(--yc-color-line-generic);
10
+ }
31
11
  }
32
- &__property {
33
- text-align: right;
12
+
13
+ &__donor-label {
14
+ margin-bottom: 8px;
34
15
  }
35
16
  }
@@ -0,0 +1,163 @@
1
+ import {render} from '@testing-library/react'
2
+
3
+ import VDisk from '../Vdisk'
4
+
5
+ describe('VDisk state', () => {
6
+ it('Should determine severity based on the highest value among VDiskState, DiskSpace and FrontQueues', () => {
7
+ const {getAllByRole} = render(
8
+ <>
9
+ <VDisk
10
+ VDiskId={{Domain: 1}}
11
+ VDiskState="OK" // severity 1, green
12
+ DiskSpace="Yellow" // severity 3, yellow
13
+ FrontQueues="Green" // severity 1, green
14
+ />
15
+ <VDisk
16
+ VDiskId={{Domain: 2}}
17
+ VDiskState="PDiskError" // severity 5, red
18
+ DiskSpace="Yellow" // severity 3, yellow
19
+ FrontQueues="Green" // severity 1, green
20
+ />
21
+ <VDisk
22
+ VDiskId={{Domain: 3}}
23
+ VDiskState="OK" // severity 1, green
24
+ DiskSpace="Yellow" // severity 3, yellow
25
+ FrontQueues="Orange" // severity 4, orange
26
+ />
27
+ </>
28
+ );
29
+
30
+ const [disk1, disk2, disk3] = getAllByRole('meter');
31
+
32
+ expect(disk1.className).toMatch(/_yellow\b/i);
33
+ expect(disk2.className).toMatch(/_red\b/i);
34
+ expect(disk3.className).toMatch(/_orange\b/i);
35
+ });
36
+
37
+ it('Should not pick the highest severity based on FrontQueues value', () => {
38
+ const {getAllByRole} = render(
39
+ <>
40
+ <VDisk
41
+ VDiskId={{Domain: 1}}
42
+ VDiskState="OK" // severity 1, green
43
+ DiskSpace="Green" // severity 1, green
44
+ FrontQueues="Red" // severity 5, red
45
+ />
46
+ <VDisk
47
+ VDiskId={{Domain: 2}}
48
+ VDiskState="OK" // severity 1, green
49
+ DiskSpace="Red" // severity 5, red
50
+ FrontQueues="Red" // severity 5, red
51
+ />
52
+ </>
53
+ );
54
+
55
+ const [disk1, disk2] = getAllByRole('meter');
56
+
57
+ expect(disk1.className).not.toMatch(/_red\b/i);
58
+ expect(disk2.className).toMatch(/_red\b/i);
59
+ });
60
+
61
+ it('Should display as unavailable when no VDiskState is provided', () => {
62
+ const {getAllByRole} = render(
63
+ <>
64
+ <VDisk VDiskId={{Domain: 1}} />
65
+ <VDisk VDiskId={{Domain: 2}} VDiskState="OK" />
66
+ <VDisk VDiskId={{Domain: 3}} DiskSpace="Green" />
67
+ <VDisk VDiskId={{Domain: 4}} FrontQueues="Green" />
68
+ <VDisk VDiskId={{Domain: 5}} VDiskState="OK" DiskSpace="Green" />
69
+ <VDisk VDiskId={{Domain: 6}} VDiskState="OK" FrontQueues="Green" />
70
+ <VDisk VDiskId={{Domain: 7}} DiskSpace="Green" FrontQueues="Green" />
71
+ <VDisk VDiskId={{Domain: 8}} VDiskState="OK" DiskSpace="Green" FrontQueues="Green" />
72
+ </>
73
+ );
74
+
75
+ const [disk1, disk2, disk3, disk4, disk5, disk6, disk7, disk8] = getAllByRole('meter');
76
+
77
+ // unavailable disks display with the highest severity
78
+ expect(disk1.className).toMatch(/_red\b/i);
79
+ expect(disk2.className).not.toMatch(/_red\b/i);
80
+ expect(disk3.className).toMatch(/_red\b/i);
81
+ expect(disk4.className).toMatch(/_red\b/i);
82
+ expect(disk5.className).not.toMatch(/_red\b/i);
83
+ expect(disk6.className).not.toMatch(/_red\b/i);
84
+ expect(disk7.className).toMatch(/_red\b/i);
85
+ expect(disk8.className).not.toMatch(/_red\b/i);
86
+ });
87
+
88
+ it('Should display replicating VDisks in OK state with a distinct color', () => {
89
+ const {getAllByRole} = render(
90
+ <>
91
+ <VDisk
92
+ VDiskId={{Domain: 1}}
93
+ VDiskState="OK" // severity 1, green
94
+ Replicated={false}
95
+ />
96
+ <VDisk
97
+ VDiskId={{Domain: 2}}
98
+ VDiskState="OK" // severity 1, green
99
+ Replicated={true}
100
+ />
101
+ </>
102
+ );
103
+
104
+ const [disk1, disk2] = getAllByRole('meter');
105
+
106
+ expect(disk1.className).toMatch(/_blue\b/i);
107
+ expect(disk2.className).not.toMatch(/_blue\b/i);
108
+ });
109
+
110
+ it('Should display replicating VDisks in a not-OK state with a regular color', () => {
111
+ const {getAllByRole} = render(
112
+ <>
113
+ <VDisk
114
+ VDiskId={{Domain: 1}}
115
+ VDiskState="Initial" // severity 3, yellow
116
+ Replicated={false}
117
+ />
118
+ <VDisk
119
+ VDiskId={{Domain: 2}}
120
+ VDiskState="PDiskError" // severity 5, red
121
+ Replicated={false}
122
+ />
123
+ </>
124
+ );
125
+
126
+ const [disk1, disk2] = getAllByRole('meter');
127
+
128
+ expect(disk1.className).toMatch(/_yellow\b/i);
129
+ expect(disk2.className).toMatch(/_red\b/i);
130
+ });
131
+
132
+ it('Should always display donor VDisks with a regular color', () => {
133
+ const {getAllByRole} = render(
134
+ <>
135
+ <VDisk
136
+ VDiskId={{Domain: 1}}
137
+ VDiskState="OK" // severity 1, green
138
+ Replicated={false} // donors are always in the not replicated state since they are leftovers
139
+ DonorMode
140
+ />
141
+ <VDisk
142
+ VDiskId={{Domain: 2}}
143
+ VDiskState="Initial" // severity 3, yellow
144
+ Replicated={false}
145
+ DonorMode
146
+ />
147
+ <VDisk
148
+ VDiskId={{Domain: 3}}
149
+ VDiskState="PDiskError" // severity 5, red
150
+ Replicated={false}
151
+ DonorMode
152
+ />
153
+ </>
154
+ );
155
+
156
+ const [disk1, disk2, disk3] = getAllByRole('meter');
157
+
158
+ expect(disk1.className).not.toMatch(/_blue\b/i);
159
+ expect(disk1.className).toMatch(/_green\b/i);
160
+ expect(disk2.className).toMatch(/_yellow\b/i);
161
+ expect(disk3.className).toMatch(/_red\b/i);
162
+ });
163
+ });
@@ -60,20 +60,21 @@ function DetailedOverview(props: DetailedOverviewProps) {
60
60
  const isTenant = tenantName === currentSchemaPath;
61
61
  return (
62
62
  <div className={b()}>
63
- <div className={b('section')}>
64
- {!isTenant && (
65
- <Overview type={type} tenantName={tenantName} />
66
- )}
67
- {isTenant && <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo}/>}
68
- </div>
69
- {isTenant && (
70
- <div className={b('section')}>
71
- <Healthcheck
72
- tenant={tenantName}
73
- preview={true}
74
- showMoreHandler={openModalHandler}
75
- />
76
- </div>
63
+ {isTenant ? (
64
+ <>
65
+ <div className={b('section')}>
66
+ <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo} />
67
+ </div>
68
+ <div className={b('section')}>
69
+ <Healthcheck
70
+ tenant={tenantName}
71
+ preview={true}
72
+ showMoreHandler={openModalHandler}
73
+ />
74
+ </div>
75
+ </>
76
+ ) : (
77
+ <Overview type={type} tenantName={tenantName} />
77
78
  )}
78
79
  </div>
79
80
  );
@@ -33,6 +33,7 @@ import {
33
33
  DEFAULT_SIZE_RESULT_PANE_KEY,
34
34
  DEFAULT_TABLE_SETTINGS,
35
35
  SAVED_QUERIES_KEY,
36
+ QUERY_INITIAL_RUN_ACTION_KEY,
36
37
  } from '../../../utils/constants';
37
38
  import {prepareQueryResponse} from '../../../utils/index';
38
39
 
@@ -538,7 +539,13 @@ function QueryEditor(props) {
538
539
  };
539
540
 
540
541
  const renderControls = () => {
541
- const {executeQuery, explainQuery, savedQueries, selectRunAction} = props;
542
+ const {
543
+ executeQuery,
544
+ explainQuery,
545
+ savedQueries,
546
+ selectRunAction,
547
+ setSettingValue,
548
+ } = props;
542
549
  const {runAction} = executeQuery;
543
550
  const runIsDisabled = !executeQuery.input || executeQuery.loading;
544
551
  const runText = _.find(RUN_ACTIONS, {value: runAction}).content;
@@ -546,7 +553,10 @@ function QueryEditor(props) {
546
553
  const menuItems = RUN_ACTIONS.map((action) => {
547
554
  return {
548
555
  text: action.content,
549
- action: () => selectRunAction(action.value),
556
+ action: () => {
557
+ selectRunAction(action.value);
558
+ setSettingValue(QUERY_INITIAL_RUN_ACTION_KEY, action.value);
559
+ },
550
560
  };
551
561
  });
552
562