Skip to content

Commit 57d2092

Browse files
feat: add developer ui links to disks popups (#1512)
1 parent b537f54 commit 57d2092

File tree

5 files changed

+109
-31
lines changed

5 files changed

+109
-31
lines changed

src/components/HoverPopup/HoverPopup.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22

3+
import type {PopupProps} from '@gravity-ui/uikit';
34
import {Popup} from '@gravity-ui/uikit';
45
import debounce from 'lodash/debounce';
56

@@ -11,15 +12,15 @@ const b = cn('hover-popup');
1112

1213
const DEBOUNCE_TIMEOUT = 100;
1314

14-
interface HoverPopupProps {
15+
type HoverPopupProps = {
1516
children: React.ReactNode;
1617
popupContent: React.ReactNode;
1718
showPopup?: boolean;
1819
offset?: [number, number];
1920
anchorRef?: React.RefObject<HTMLElement>;
2021
onShowPopup?: VoidFunction;
2122
onHidePopup?: VoidFunction;
22-
}
23+
} & Pick<PopupProps, 'placement' | 'contentClassName'>;
2324

2425
export const HoverPopup = ({
2526
children,
@@ -29,6 +30,8 @@ export const HoverPopup = ({
2930
anchorRef,
3031
onShowPopup,
3132
onHidePopup,
33+
placement = ['top', 'bottom'],
34+
contentClassName,
3235
}: HoverPopupProps) => {
3336
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
3437
const anchor = React.useRef<HTMLDivElement>(null);
@@ -88,18 +91,18 @@ export const HoverPopup = ({
8891

8992
return (
9093
<React.Fragment>
91-
<div ref={anchor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
94+
<span ref={anchor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
9295
{children}
93-
</div>
96+
</span>
9497
<Popup
95-
contentClassName={b()}
98+
contentClassName={b(null, contentClassName)}
9699
anchorRef={anchorRef || anchor}
97100
open={open}
98101
onMouseEnter={onPopupMouseEnter}
99102
onMouseLeave={onPopupMouseLeave}
100103
onEscapeKeyDown={onPopupEscapeKeyDown}
101104
onBlur={onPopupBlur}
102-
placement={['top', 'bottom']}
105+
placement={placement}
103106
hasArrow
104107
// bigger offset for easier switching to neighbour nodes
105108
// matches the default offset for popup with arrow out of a sense of beauty

src/components/PDiskPopup/PDiskPopup.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import React from 'react';
22

3+
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
34
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
45
import {EFlag} from '../../types/api/enums';
56
import {valueIsDefined} from '../../utils';
67
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
8+
import {createPDiskDeveloperUILink} from '../../utils/developerUI/developerUI';
79
import {getPDiskId} from '../../utils/disks/helpers';
810
import type {PreparedPDisk} from '../../utils/disks/types';
911
import {useTypedSelector} from '../../utils/hooks';
1012
import {bytesToGB} from '../../utils/utils';
1113
import {InfoViewer} from '../InfoViewer';
1214
import type {InfoViewerItem} from '../InfoViewer';
15+
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
1316

1417
const errorColors = [EFlag.Orange, EFlag.Red, EFlag.Yellow];
1518

16-
export const preparePDiskData = (data: PreparedPDisk, nodeHost?: string) => {
19+
export const preparePDiskData = (
20+
data: PreparedPDisk,
21+
nodeHost?: string,
22+
withDeveloperUILink?: boolean,
23+
) => {
1724
const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Type, Device} = data;
1825

1926
const pdiskData: InfoViewerItem[] = [
@@ -50,6 +57,18 @@ export const preparePDiskData = (data: PreparedPDisk, nodeHost?: string) => {
5057
pdiskData.push({label: 'Device', value: Device});
5158
}
5259

60+
if (withDeveloperUILink && valueIsDefined(NodeId) && valueIsDefined(PDiskId)) {
61+
const pDiskInternalViewerPath = createPDiskDeveloperUILink({
62+
nodeId: NodeId,
63+
pDiskId: PDiskId,
64+
});
65+
66+
pdiskData.push({
67+
label: 'Links',
68+
value: <LinkWithIcon title={'Developer UI'} url={pDiskInternalViewerPath} />,
69+
});
70+
}
71+
5372
return pdiskData;
5473
};
5574

@@ -58,9 +77,13 @@ interface PDiskPopupProps {
5877
}
5978

6079
export const PDiskPopup = ({data}: PDiskPopupProps) => {
80+
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
6181
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
6282
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
63-
const info = React.useMemo(() => preparePDiskData(data, nodeHost), [data, nodeHost]);
83+
const info = React.useMemo(
84+
() => preparePDiskData(data, nodeHost, isUserAllowedToMakeChanges),
85+
[data, nodeHost, isUserAllowedToMakeChanges],
86+
);
6487

6588
return <InfoViewer title="PDisk" info={info} size="s" />;
6689
};

src/components/VDiskPopup/VDiskPopup.tsx

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@ import React from 'react';
22

33
import {Label} from '@gravity-ui/uikit';
44

5+
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
56
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
67
import {EFlag} from '../../types/api/enums';
78
import {valueIsDefined} from '../../utils';
89
import {cn} from '../../utils/cn';
910
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
1011
import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters';
12+
import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI';
1113
import {isFullVDiskData} from '../../utils/disks/helpers';
1214
import type {PreparedVDisk, UnavailableDonor} from '../../utils/disks/types';
1315
import {useTypedSelector} from '../../utils/hooks';
1416
import {bytesToGB, bytesToSpeed} from '../../utils/utils';
1517
import type {InfoViewerItem} from '../InfoViewer';
1618
import {InfoViewer} from '../InfoViewer';
1719
import {InternalLink} from '../InternalLink';
20+
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
1821
import {preparePDiskData} from '../PDiskPopup/PDiskPopup';
1922
import {getVDiskLink} from '../VDisk/utils';
2023

2124
import './VDiskPopup.scss';
2225

2326
const b = cn('vdisk-storage-popup');
2427

25-
const prepareUnavailableVDiskData = (data: UnavailableDonor) => {
28+
const prepareUnavailableVDiskData = (data: UnavailableDonor, withDeveloperUILink?: boolean) => {
2629
const {NodeId, PDiskId, VSlotId, StoragePoolName} = data;
2730

2831
const vdiskData: InfoViewerItem[] = [{label: 'State', value: 'not available'}];
@@ -37,11 +40,33 @@ const prepareUnavailableVDiskData = (data: UnavailableDonor) => {
3740
{label: 'VSlotId', value: VSlotId ?? EMPTY_DATA_PLACEHOLDER},
3841
);
3942

43+
if (
44+
withDeveloperUILink &&
45+
valueIsDefined(NodeId) &&
46+
valueIsDefined(PDiskId) &&
47+
valueIsDefined(VSlotId)
48+
) {
49+
const vDiskInternalViewerPath = createVDiskDeveloperUILink({
50+
nodeId: NodeId,
51+
pDiskId: PDiskId,
52+
vDiskSlotId: VSlotId,
53+
});
54+
55+
vdiskData.push({
56+
label: 'Links',
57+
value: <LinkWithIcon title={'Developer UI'} url={vDiskInternalViewerPath} />,
58+
});
59+
}
60+
4061
return vdiskData;
4162
};
4263

43-
const prepareVDiskData = (data: PreparedVDisk) => {
64+
// eslint-disable-next-line complexity
65+
const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => {
4466
const {
67+
NodeId,
68+
PDiskId,
69+
VDiskSlotId,
4570
StringifiedId,
4671
VDiskState,
4772
SatisfactionRank,
@@ -126,6 +151,24 @@ const prepareVDiskData = (data: PreparedVDisk) => {
126151
});
127152
}
128153

154+
if (
155+
withDeveloperUILink &&
156+
valueIsDefined(NodeId) &&
157+
valueIsDefined(PDiskId) &&
158+
valueIsDefined(VDiskSlotId)
159+
) {
160+
const vDiskInternalViewerPath = createVDiskDeveloperUILink({
161+
nodeId: NodeId,
162+
pDiskId: PDiskId,
163+
vDiskSlotId: VDiskSlotId,
164+
});
165+
166+
vdiskData.push({
167+
label: 'Links',
168+
value: <LinkWithIcon title={'Developer UI'} url={vDiskInternalViewerPath} />,
169+
});
170+
}
171+
129172
return vdiskData;
130173
};
131174

@@ -136,16 +179,24 @@ interface VDiskPopupProps {
136179
export const VDiskPopup = ({data}: VDiskPopupProps) => {
137180
const isFullData = isFullVDiskData(data);
138181

182+
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
183+
139184
const vdiskInfo = React.useMemo(
140-
() => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)),
141-
[data, isFullData],
185+
() =>
186+
isFullData
187+
? prepareVDiskData(data, isUserAllowedToMakeChanges)
188+
: prepareUnavailableVDiskData(data, isUserAllowedToMakeChanges),
189+
[data, isFullData, isUserAllowedToMakeChanges],
142190
);
143191

144192
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
145193
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
146194
const pdiskInfo = React.useMemo(
147-
() => isFullData && data.PDisk && preparePDiskData(data.PDisk, nodeHost),
148-
[data, nodeHost, isFullData],
195+
() =>
196+
isFullData &&
197+
data.PDisk &&
198+
preparePDiskData(data.PDisk, nodeHost, isUserAllowedToMakeChanges),
199+
[data, nodeHost, isFullData, isUserAllowedToMakeChanges],
149200
);
150201

151202
const donorsInfo: InfoViewerItem[] = [];

src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup';
21
import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar';
2+
import {HoverPopup} from '../../../components/HoverPopup/HoverPopup';
33
import type {InfoViewerItem} from '../../../components/InfoViewer';
44
import {InfoViewer} from '../../../components/InfoViewer';
55
import {InternalLink} from '../../../components/InternalLink';
@@ -85,8 +85,8 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
8585
: undefined;
8686

8787
return (
88-
<ContentWithPopup
89-
content={<VDiskInfo data={item.SlotData} withTitle />}
88+
<HoverPopup
89+
popupContent={<VDiskInfo data={item.SlotData} withTitle />}
9090
contentClassName={b('vdisk-popup')}
9191
placement={['right', 'top']}
9292
>
@@ -105,13 +105,13 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
105105
}
106106
/>
107107
</InternalLink>
108-
</ContentWithPopup>
108+
</HoverPopup>
109109
);
110110
}
111111
if (isLogSlot(item)) {
112112
return (
113-
<ContentWithPopup
114-
content={<LogInfo data={item.SlotData} />}
113+
<HoverPopup
114+
popupContent={<LogInfo data={item.SlotData} />}
115115
contentClassName={b('vdisk-popup')}
116116
placement={['right', 'top']}
117117
>
@@ -127,14 +127,14 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
127127
/>
128128
}
129129
/>
130-
</ContentWithPopup>
130+
</HoverPopup>
131131
);
132132
}
133133

134134
if (isEmptySlot(item)) {
135135
return (
136-
<ContentWithPopup
137-
content={<EmptySlotInfo data={item.SlotData} />}
136+
<HoverPopup
137+
popupContent={<EmptySlotInfo data={item.SlotData} />}
138138
contentClassName={b('vdisk-popup')}
139139
placement={['right', 'top']}
140140
>
@@ -150,7 +150,7 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
150150
/>
151151
}
152152
/>
153-
</ContentWithPopup>
153+
</HoverPopup>
154154
);
155155
}
156156

src/store/reducers/pdisk/utils.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
1010
TPDiskInfoResponse,
1111
TEvSystemStateResponse,
1212
]): PDiskData {
13+
const rawNode = nodeResponse.SystemStateInfo?.[0];
14+
const preparedNode = prepareNodeSystemState(rawNode);
15+
1316
const {BSC = {}, Whiteboard = {}} = pdiskResponse || {};
1417

1518
const {PDisk: WhiteboardPDiskData = {}, VDisks: WhiteboardVDisksData = []} = Whiteboard;
1619
const {PDisk: BSCPDiskData = {}} = BSC;
1720

1821
const preparedPDisk = preparePDiskData(WhiteboardPDiskData, BSCPDiskData);
1922

23+
const NodeId = preparedPDisk.NodeId ?? preparedNode.NodeId;
24+
2025
const {
2126
LogUsedSize,
2227
LogTotalSize,
@@ -43,9 +48,8 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
4348
};
4449
}
4550

46-
const preparedVDisks = WhiteboardVDisksData.map(prepareVDiskData).sort(
47-
(disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId),
48-
);
51+
const preparedVDisks = WhiteboardVDisksData.map((disk) => prepareVDiskData({...disk, NodeId}));
52+
preparedVDisks.sort((disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId));
4953

5054
const vdisksSlots: SlotItem<'vDisk'>[] = preparedVDisks.map((preparedVDisk) => {
5155
return {
@@ -94,12 +98,9 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
9498
diskSlots.unshift(logSlot);
9599
}
96100

97-
const rawNode = nodeResponse.SystemStateInfo?.[0];
98-
const preparedNode = prepareNodeSystemState(rawNode);
99-
100101
return {
101102
...preparedPDisk,
102-
NodeId: preparedPDisk.NodeId ?? preparedNode.NodeId,
103+
NodeId,
103104
NodeHost: preparedNode.Host,
104105
NodeType: preparedNode.Roles?.[0],
105106
NodeDC: preparedNode.DC,

0 commit comments

Comments
 (0)