Skip to content

feat: add developer ui links to disks popups #1512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/components/HoverPopup/HoverPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import type {PopupProps} from '@gravity-ui/uikit';
import {Popup} from '@gravity-ui/uikit';
import debounce from 'lodash/debounce';

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

const DEBOUNCE_TIMEOUT = 100;

interface HoverPopupProps {
type HoverPopupProps = {
children: React.ReactNode;
popupContent: React.ReactNode;
showPopup?: boolean;
offset?: [number, number];
anchorRef?: React.RefObject<HTMLElement>;
onShowPopup?: VoidFunction;
onHidePopup?: VoidFunction;
}
} & Pick<PopupProps, 'placement' | 'contentClassName'>;

export const HoverPopup = ({
children,
Expand All @@ -29,6 +30,8 @@ export const HoverPopup = ({
anchorRef,
onShowPopup,
onHidePopup,
placement = ['top', 'bottom'],
contentClassName,
}: HoverPopupProps) => {
const [isPopupVisible, setIsPopupVisible] = React.useState(false);
const anchor = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -88,18 +91,18 @@ export const HoverPopup = ({

return (
<React.Fragment>
<div ref={anchor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<span ref={anchor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

div without additional styling doesn't work well for PDiskSpaceDistribution

{children}
</div>
</span>
<Popup
contentClassName={b()}
contentClassName={b(null, contentClassName)}
anchorRef={anchorRef || anchor}
open={open}
onMouseEnter={onPopupMouseEnter}
onMouseLeave={onPopupMouseLeave}
onEscapeKeyDown={onPopupEscapeKeyDown}
onBlur={onPopupBlur}
placement={['top', 'bottom']}
placement={placement}
hasArrow
// bigger offset for easier switching to neighbour nodes
// matches the default offset for popup with arrow out of a sense of beauty
Expand Down
27 changes: 25 additions & 2 deletions src/components/PDiskPopup/PDiskPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import React from 'react';

import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
import {EFlag} from '../../types/api/enums';
import {valueIsDefined} from '../../utils';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {createPDiskDeveloperUILink} from '../../utils/developerUI/developerUI';
import {getPDiskId} from '../../utils/disks/helpers';
import type {PreparedPDisk} from '../../utils/disks/types';
import {useTypedSelector} from '../../utils/hooks';
import {bytesToGB} from '../../utils/utils';
import {InfoViewer} from '../InfoViewer';
import type {InfoViewerItem} from '../InfoViewer';
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';

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

export const preparePDiskData = (data: PreparedPDisk, nodeHost?: string) => {
export const preparePDiskData = (
data: PreparedPDisk,
nodeHost?: string,
withDeveloperUILink?: boolean,
) => {
const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Type, Device} = data;

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

if (withDeveloperUILink && valueIsDefined(NodeId) && valueIsDefined(PDiskId)) {
const pDiskInternalViewerPath = createPDiskDeveloperUILink({
nodeId: NodeId,
pDiskId: PDiskId,
});

pdiskData.push({
label: 'Links',
Copy link
Member Author

@artemmufazalov artemmufazalov Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no i18n for this files, I don't want mix this chores with this feature (since it creates big diffs)

value: <LinkWithIcon title={'Developer UI'} url={pDiskInternalViewerPath} />,
});
}

return pdiskData;
};

Expand All @@ -58,9 +77,13 @@ interface PDiskPopupProps {
}

export const PDiskPopup = ({data}: PDiskPopupProps) => {
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
const info = React.useMemo(() => preparePDiskData(data, nodeHost), [data, nodeHost]);
const info = React.useMemo(
() => preparePDiskData(data, nodeHost, isUserAllowedToMakeChanges),
[data, nodeHost, isUserAllowedToMakeChanges],
);

return <InfoViewer title="PDisk" info={info} size="s" />;
};
63 changes: 57 additions & 6 deletions src/components/VDiskPopup/VDiskPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@ import React from 'react';

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

import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
import {EFlag} from '../../types/api/enums';
import {valueIsDefined} from '../../utils';
import {cn} from '../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters';
import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI';
import {isFullVDiskData} from '../../utils/disks/helpers';
import type {PreparedVDisk, UnavailableDonor} from '../../utils/disks/types';
import {useTypedSelector} from '../../utils/hooks';
import {bytesToGB, bytesToSpeed} from '../../utils/utils';
import type {InfoViewerItem} from '../InfoViewer';
import {InfoViewer} from '../InfoViewer';
import {InternalLink} from '../InternalLink';
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
import {preparePDiskData} from '../PDiskPopup/PDiskPopup';
import {getVDiskLink} from '../VDisk/utils';

import './VDiskPopup.scss';

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

const prepareUnavailableVDiskData = (data: UnavailableDonor) => {
const prepareUnavailableVDiskData = (data: UnavailableDonor, withDeveloperUILink?: boolean) => {
const {NodeId, PDiskId, VSlotId, StoragePoolName} = data;

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

if (
withDeveloperUILink &&
valueIsDefined(NodeId) &&
valueIsDefined(PDiskId) &&
valueIsDefined(VSlotId)
) {
const vDiskInternalViewerPath = createVDiskDeveloperUILink({
nodeId: NodeId,
pDiskId: PDiskId,
vDiskSlotId: VSlotId,
});

vdiskData.push({
label: 'Links',
value: <LinkWithIcon title={'Developer UI'} url={vDiskInternalViewerPath} />,
});
}

return vdiskData;
};

const prepareVDiskData = (data: PreparedVDisk) => {
// eslint-disable-next-line complexity
const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => {
const {
NodeId,
PDiskId,
VDiskSlotId,
StringifiedId,
VDiskState,
SatisfactionRank,
Expand Down Expand Up @@ -126,6 +151,24 @@ const prepareVDiskData = (data: PreparedVDisk) => {
});
}

if (
withDeveloperUILink &&
valueIsDefined(NodeId) &&
valueIsDefined(PDiskId) &&
valueIsDefined(VDiskSlotId)
) {
const vDiskInternalViewerPath = createVDiskDeveloperUILink({
nodeId: NodeId,
pDiskId: PDiskId,
vDiskSlotId: VDiskSlotId,
});

vdiskData.push({
label: 'Links',
value: <LinkWithIcon title={'Developer UI'} url={vDiskInternalViewerPath} />,
});
}

return vdiskData;
};

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

const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);

const vdiskInfo = React.useMemo(
() => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)),
[data, isFullData],
() =>
isFullData
? prepareVDiskData(data, isUserAllowedToMakeChanges)
: prepareUnavailableVDiskData(data, isUserAllowedToMakeChanges),
[data, isFullData, isUserAllowedToMakeChanges],
);

const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
const pdiskInfo = React.useMemo(
() => isFullData && data.PDisk && preparePDiskData(data.PDisk, nodeHost),
[data, nodeHost, isFullData],
() =>
isFullData &&
data.PDisk &&
preparePDiskData(data.PDisk, nodeHost, isUserAllowedToMakeChanges),
[data, nodeHost, isFullData, isUserAllowedToMakeChanges],
);

const donorsInfo: InfoViewerItem[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup';
import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar';
import {HoverPopup} from '../../../components/HoverPopup/HoverPopup';
import type {InfoViewerItem} from '../../../components/InfoViewer';
import {InfoViewer} from '../../../components/InfoViewer';
import {InternalLink} from '../../../components/InternalLink';
Expand Down Expand Up @@ -85,8 +85,8 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
: undefined;

return (
<ContentWithPopup
content={<VDiskInfo data={item.SlotData} withTitle />}
<HoverPopup
popupContent={<VDiskInfo data={item.SlotData} withTitle />}
contentClassName={b('vdisk-popup')}
placement={['right', 'top']}
>
Expand All @@ -105,13 +105,13 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
}
/>
</InternalLink>
</ContentWithPopup>
</HoverPopup>
);
}
if (isLogSlot(item)) {
return (
<ContentWithPopup
content={<LogInfo data={item.SlotData} />}
<HoverPopup
popupContent={<LogInfo data={item.SlotData} />}
contentClassName={b('vdisk-popup')}
placement={['right', 'top']}
>
Expand All @@ -127,14 +127,14 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
/>
}
/>
</ContentWithPopup>
</HoverPopup>
);
}

if (isEmptySlot(item)) {
return (
<ContentWithPopup
content={<EmptySlotInfo data={item.SlotData} />}
<HoverPopup
popupContent={<EmptySlotInfo data={item.SlotData} />}
contentClassName={b('vdisk-popup')}
placement={['right', 'top']}
>
Expand All @@ -150,7 +150,7 @@ function Slot<T extends SlotItemType>({item, pDiskId, nodeId}: SlotProps<T>) {
/>
}
/>
</ContentWithPopup>
</HoverPopup>
);
}

Expand Down
15 changes: 8 additions & 7 deletions src/store/reducers/pdisk/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
TPDiskInfoResponse,
TEvSystemStateResponse,
]): PDiskData {
const rawNode = nodeResponse.SystemStateInfo?.[0];
const preparedNode = prepareNodeSystemState(rawNode);

const {BSC = {}, Whiteboard = {}} = pdiskResponse || {};

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

const preparedPDisk = preparePDiskData(WhiteboardPDiskData, BSCPDiskData);

const NodeId = preparedPDisk.NodeId ?? preparedNode.NodeId;

const {
LogUsedSize,
LogTotalSize,
Expand All @@ -43,9 +48,8 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
};
}

const preparedVDisks = WhiteboardVDisksData.map(prepareVDiskData).sort(
(disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId),
);
const preparedVDisks = WhiteboardVDisksData.map((disk) => prepareVDiskData({...disk, NodeId}));
Copy link
Member Author

@artemmufazalov artemmufazalov Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no NodeId in WhiteboardVDisksData, without it there is no VDisk Developer UI link in PDiskSpaceDistribution

preparedVDisks.sort((disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId));

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

const rawNode = nodeResponse.SystemStateInfo?.[0];
const preparedNode = prepareNodeSystemState(rawNode);

return {
...preparedPDisk,
NodeId: preparedPDisk.NodeId ?? preparedNode.NodeId,
NodeId,
NodeHost: preparedNode.Host,
NodeType: preparedNode.Roles?.[0],
NodeDC: preparedNode.DC,
Expand Down
Loading