ydb-embedded-ui 1.10.1 → 1.11.0

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.
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