Skip to content

Commit

Permalink
feat(Network): add table view (#1622)
Browse files Browse the repository at this point in the history
  • Loading branch information
artemmufazalov authored Nov 18, 2024
1 parent cbc146b commit 507aa71
Show file tree
Hide file tree
Showing 25 changed files with 661 additions and 61 deletions.
22 changes: 14 additions & 8 deletions src/components/NodeHostWrapper/NodeHostWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {PopoverBehavior} from '@gravity-ui/uikit';

import {getDefaultNodePath} from '../../containers/Node/NodePages';
import type {NodeAddress} from '../../types/additionalProps';
import type {TSystemStateInfo} from '../../types/api/nodes';
import type {TNodeInfo, TSystemStateInfo} from '../../types/api/nodes';
import {
createDeveloperUIInternalPageHref,
createDeveloperUILinkWithNodeId,
Expand All @@ -13,22 +13,33 @@ import {EntityStatus} from '../EntityStatus/EntityStatus';
import {NodeEndpointsTooltipContent} from '../TooltipsContent';

export type NodeHostData = NodeAddress &
Pick<TNodeInfo, 'ConnectStatus'> &
Pick<TSystemStateInfo, 'SystemState'> & {
NodeId: string | number;
TenantName?: string;
};

export type StatusForIcon = 'SystemState' | 'ConnectStatus';

interface NodeHostWrapperProps {
node: NodeHostData;
getNodeRef?: (node?: NodeAddress) => string | null;
database?: string;
statusForIcon?: StatusForIcon;
}

export const NodeHostWrapper = ({node, getNodeRef, database}: NodeHostWrapperProps) => {
export const NodeHostWrapper = ({
node,
getNodeRef,
database,
statusForIcon,
}: NodeHostWrapperProps) => {
if (!node.Host) {
return <span></span>;
}

const status = statusForIcon === 'ConnectStatus' ? node.ConnectStatus : node.SystemState;

const isNodeAvailable = !isUnavailableNode(node);

let developerUIInternalHref: string | undefined;
Expand Down Expand Up @@ -56,12 +67,7 @@ export const NodeHostWrapper = ({node, getNodeRef, database}: NodeHostWrapperPro
behavior={PopoverBehavior.Immediate}
delayClosing={200}
>
<EntityStatus
name={node.Host}
status={node.SystemState}
path={nodePath}
hasClipboardButton
/>
<EntityStatus name={node.Host} status={status} path={nodePath} hasClipboardButton />
</CellWithPopover>
);
};
30 changes: 30 additions & 0 deletions src/components/nodesColumns/__test__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {UNBREAKABLE_GAP} from '../../../utils/utils';
import {prepareClockSkewValue, preparePingTimeValue} from '../utils';

describe('preparePingTimeValue', () => {
it('Should correctly prepare value', () => {
expect(preparePingTimeValue(1)).toEqual(`0${UNBREAKABLE_GAP}ms`);
expect(preparePingTimeValue(100)).toEqual(`0.1${UNBREAKABLE_GAP}ms`);
expect(preparePingTimeValue(5_550)).toEqual(`6${UNBREAKABLE_GAP}ms`);
expect(preparePingTimeValue(100_000)).toEqual(`100${UNBREAKABLE_GAP}ms`);
});
});

describe('prepareClockSkewValue', () => {
it('Should correctly prepare 0 or very low values', () => {
expect(prepareClockSkewValue(0)).toEqual(`0${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(10)).toEqual(`0${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(-10)).toEqual(`0${UNBREAKABLE_GAP}ms`);
});
it('Should correctly prepare positive values', () => {
expect(prepareClockSkewValue(100)).toEqual(`+0.1${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(5_500)).toEqual(`+6${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(100_000)).toEqual(`+100${UNBREAKABLE_GAP}ms`);
});

it('Should correctly prepare negative values', () => {
expect(prepareClockSkewValue(-100)).toEqual(`-0.1${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(-5_500)).toEqual(`-6${UNBREAKABLE_GAP}ms`);
expect(prepareClockSkewValue(-100_000)).toEqual(`-100${UNBREAKABLE_GAP}ms`);
});
});
183 changes: 176 additions & 7 deletions src/components/nodesColumns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ import {valueIsDefined} from '../../utils';
import {cn} from '../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {
formatPercent,
formatStorageValues,
formatStorageValuesToGb,
} from '../../utils/dataFormatters/dataFormatters';
import {getSpaceUsageSeverity} from '../../utils/storage';
import type {Column} from '../../utils/tableUtils/types';
import {isNumeric} from '../../utils/utils';
import {bytesToSpeed, isNumeric} from '../../utils/utils';
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
import {MemoryViewer} from '../MemoryViewer/MemoryViewer';
import {NodeHostWrapper} from '../NodeHostWrapper/NodeHostWrapper';
import type {NodeHostData} from '../NodeHostWrapper/NodeHostWrapper';
import type {NodeHostData, StatusForIcon} from '../NodeHostWrapper/NodeHostWrapper';
import {PoolsGraph} from '../PoolsGraph/PoolsGraph';
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
import {TabletsStatistic} from '../TabletsStatistic';
Expand All @@ -27,6 +28,7 @@ import {UsageLabel} from '../UsageLabel/UsageLabel';
import {NODES_COLUMNS_IDS, NODES_COLUMNS_TITLES} from './constants';
import i18n from './i18n';
import type {GetNodesColumnsParams} from './types';
import {prepareClockSkewValue, preparePingTimeValue} from './utils';

import './NodesColumns.scss';

Expand All @@ -41,15 +43,22 @@ export function getNodeIdColumn<T extends {NodeId?: string | number}>(): Column<
align: DataTable.RIGHT,
};
}
export function getHostColumn<T extends NodeHostData>({
getNodeRef,
database,
}: GetNodesColumnsParams): Column<T> {
export function getHostColumn<T extends NodeHostData>(
{getNodeRef, database}: GetNodesColumnsParams,
{statusForIcon = 'SystemState'}: {statusForIcon?: StatusForIcon} = {},
): Column<T> {
return {
name: NODES_COLUMNS_IDS.Host,
header: NODES_COLUMNS_TITLES.Host,
render: ({row}) => {
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} database={database} />;
return (
<NodeHostWrapper
node={row}
getNodeRef={getNodeRef}
database={database}
statusForIcon={statusForIcon}
/>
);
},
width: 350,
align: DataTable.LEFT,
Expand Down Expand Up @@ -363,3 +372,163 @@ export function getMissingDisksColumn<T extends {Missing?: number}>(): Column<T>
defaultOrder: DataTable.DESCENDING,
};
}

// Network diagnostics columns
export function getConnectionsColumn<T extends {Connections?: number}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.Connections,
header: NODES_COLUMNS_TITLES.Connections,
render: ({row}) => (isNumeric(row.Connections) ? row.Connections : EMPTY_DATA_PLACEHOLDER),
align: DataTable.RIGHT,
width: 130,
};
}
export function getNetworkUtilizationColumn<
T extends {
NetworkUtilization?: number;
NetworkUtilizationMin?: number;
NetworkUtilizationMax?: number;
},
>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.NetworkUtilization,
header: NODES_COLUMNS_TITLES.NetworkUtilization,
render: ({row}) => {
const {NetworkUtilization, NetworkUtilizationMin = 0, NetworkUtilizationMax = 0} = row;

if (!isNumeric(NetworkUtilization)) {
return EMPTY_DATA_PLACEHOLDER;
}

return (
<CellWithPopover
placement={['top', 'auto']}
fullWidth
content={
<DefinitionList responsive>
<DefinitionList.Item key={'NetworkUtilization'} name={i18n('sum')}>
{formatPercent(NetworkUtilization)}
</DefinitionList.Item>
<DefinitionList.Item key={'NetworkUtilizationMin'} name={i18n('min')}>
{formatPercent(NetworkUtilizationMin)}
</DefinitionList.Item>
<DefinitionList.Item key={'NetworkUtilizationMax'} name={i18n('max')}>
{formatPercent(NetworkUtilizationMax)}
</DefinitionList.Item>
</DefinitionList>
}
>
{formatPercent(NetworkUtilization)}
</CellWithPopover>
);
},
align: DataTable.RIGHT,
width: 110,
};
}
export function getSendThroughputColumn<T extends {SendThroughput?: string}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.SendThroughput,
header: NODES_COLUMNS_TITLES.SendThroughput,
render: ({row}) =>
isNumeric(row.SendThroughput)
? bytesToSpeed(row.SendThroughput)
: EMPTY_DATA_PLACEHOLDER,
align: DataTable.RIGHT,
width: 110,
};
}
export function getReceiveThroughputColumn<T extends {ReceiveThroughput?: string}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.ReceiveThroughput,
header: NODES_COLUMNS_TITLES.ReceiveThroughput,
render: ({row}) =>
isNumeric(row.ReceiveThroughput)
? bytesToSpeed(row.ReceiveThroughput)
: EMPTY_DATA_PLACEHOLDER,
align: DataTable.RIGHT,
width: 110,
};
}
export function getPingTimeColumn<
T extends {
PingTimeUs?: string;
PingTimeMinUs?: string;
PingTimeMaxUs?: string;
},
>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.PingTime,
header: NODES_COLUMNS_TITLES.PingTime,
render: ({row}) => {
const {PingTimeUs, PingTimeMinUs = 0, PingTimeMaxUs = 0} = row;

if (!isNumeric(PingTimeUs)) {
return EMPTY_DATA_PLACEHOLDER;
}

return (
<CellWithPopover
placement={['top', 'auto']}
fullWidth
content={
<DefinitionList responsive>
<DefinitionList.Item key={'PingTimeUs'} name={i18n('avg')}>
{preparePingTimeValue(PingTimeUs)}
</DefinitionList.Item>
<DefinitionList.Item key={'PingTimeMinUs'} name={i18n('min')}>
{preparePingTimeValue(PingTimeMinUs)}
</DefinitionList.Item>
<DefinitionList.Item key={'PingTimeMaxUs'} name={i18n('max')}>
{preparePingTimeValue(PingTimeMaxUs)}
</DefinitionList.Item>
</DefinitionList>
}
>
{preparePingTimeValue(PingTimeUs)}
</CellWithPopover>
);
},
align: DataTable.RIGHT,
width: 110,
};
}
export function getClockSkewColumn<
T extends {ClockSkewUs?: string; ClockSkewMinUs?: string; ClockSkewMaxUs?: string},
>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.ClockSkew,
header: NODES_COLUMNS_TITLES.ClockSkew,
render: ({row}) => {
const {ClockSkewUs, ClockSkewMinUs = 0, ClockSkewMaxUs = 0} = row;

if (!isNumeric(ClockSkewUs)) {
return EMPTY_DATA_PLACEHOLDER;
}

return (
<CellWithPopover
placement={['top', 'auto']}
fullWidth
content={
<DefinitionList responsive>
<DefinitionList.Item key={'ClockSkewUs'} name={i18n('avg')}>
{prepareClockSkewValue(ClockSkewUs)}
</DefinitionList.Item>
<DefinitionList.Item key={'ClockSkewMinUs'} name={i18n('min')}>
{prepareClockSkewValue(ClockSkewMinUs)}
</DefinitionList.Item>
<DefinitionList.Item key={'ClockSkewMaxUs'} name={i18n('max')}>
{prepareClockSkewValue(ClockSkewMaxUs)}
</DefinitionList.Item>
</DefinitionList>
}
>
{prepareClockSkewValue(ClockSkewUs)}
</CellWithPopover>
);
},
align: DataTable.RIGHT,
width: 110,
};
}
36 changes: 36 additions & 0 deletions src/components/nodesColumns/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export const NODES_COLUMNS_IDS = {
Load: 'Load',
DiskSpaceUsage: 'DiskSpaceUsage',
TotalSessions: 'TotalSessions',
Connections: 'Connections',
NetworkUtilization: 'NetworkUtilization',
SendThroughput: 'SendThroughput',
ReceiveThroughput: 'ReceiveThroughput',
PingTime: 'PingTime',
ClockSkew: 'ClockSkew',
Missing: 'Missing',
Tablets: 'Tablets',
PDisks: 'PDisks',
Expand Down Expand Up @@ -80,6 +86,24 @@ export const NODES_COLUMNS_TITLES = {
get TotalSessions() {
return i18n('sessions');
},
get Connections() {
return i18n('connections');
},
get NetworkUtilization() {
return i18n('utilization');
},
get SendThroughput() {
return i18n('send');
},
get ReceiveThroughput() {
return i18n('receive');
},
get PingTime() {
return i18n('ping');
},
get ClockSkew() {
return i18n('skew');
},
get Missing() {
return i18n('missing');
},
Expand Down Expand Up @@ -162,6 +186,12 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
Load: ['LoadAverage'],
DiskSpaceUsage: ['DiskSpaceUsage'],
TotalSessions: ['SystemState'],
Connections: ['Connections'],
NetworkUtilization: ['NetworkUtilization'],
SendThroughput: ['SendThroughput'],
ReceiveThroughput: ['ReceiveThroughput'],
PingTime: ['PingTime'],
ClockSkew: ['ClockSkew'],
Missing: ['Missing'],
Tablets: ['Tablets', 'Database'],
PDisks: ['PDisks'],
Expand All @@ -184,6 +214,12 @@ const NODES_COLUMNS_TO_SORT_FIELDS: Record<NodesColumnId, NodesSortValue | undef
Load: 'LoadAverage',
DiskSpaceUsage: 'DiskSpaceUsage',
TotalSessions: undefined,
Connections: 'Connections',
NetworkUtilization: 'NetworkUtilization',
SendThroughput: 'SendThroughput',
ReceiveThroughput: 'ReceiveThroughput',
PingTime: 'PingTime',
ClockSkew: 'ClockSkew',
Missing: 'Missing',
Tablets: undefined,
PDisks: undefined,
Expand Down
13 changes: 12 additions & 1 deletion src/components/nodesColumns/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@

"system-state": "System State",
"connect-status": "Connect Status",
"utilization": "Utilization",
"network-utilization": "Network Utilization",
"connections": "Connections",
"clock-skew": "Clock Skew",
"ping-time": "Ping Time"
"skew": "Skew",
"ping-time": "Ping Time",
"ping": "Ping",
"send": "Send",
"receive": "Receive",

"max": "Max",
"min": "Min",
"avg": "Avg",
"sum": "Sum"
}
Loading

0 comments on commit 507aa71

Please sign in to comment.