Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
83c5a0d
feat: database storage
astandrik Apr 14, 2026
b0067b0
fix: some fixes
astandrik Apr 15, 2026
9e038f9
fix: review fixes
astandrik Apr 21, 2026
25e2668
fix: review fixes
astandrik Apr 22, 2026
ff1fad1
fix: data space usage
astandrik Apr 24, 2026
d4432e2
fix: design fixes 1
astandrik Apr 29, 2026
985fa47
fix: design review 2
astandrik Apr 29, 2026
2c72d94
fix: tooltip
astandrik Apr 29, 2026
06ddbdb
fix: refactor
astandrik May 4, 2026
b42b6ad
fix: design fixes
astandrik May 4, 2026
aa9d615
fix: design fixes
astandrik May 4, 2026
1798637
fix: refine new storage layout and tooltips
astandrik May 5, 2026
6fe846a
test: update new storage snapshots
astandrik May 5, 2026
ac47f66
test: update tenant storage screenshots
astandrik May 5, 2026
99e79cc
test: add full tenant storage screenshots
astandrik May 5, 2026
e14c455
test: update tenant storage table screenshots
astandrik May 5, 2026
2f78929
test: update tenant storage table snapshots
astandrik May 5, 2026
ddb1238
fix: address storage review feedback
astandrik May 6, 2026
223531b
fix: handle unknown storage top row data
astandrik May 6, 2026
5a7afc5
fix: handle tablet type storage fallbacks
astandrik May 6, 2026
46077d7
Merge branch 'main' into astandrik.3779-2
astandrik May 6, 2026
b7f9087
fix: preserve aggregate storage breakdown
astandrik May 6, 2026
94cfe86
fix: handle unknown tenant storage media
astandrik May 6, 2026
41654b4
fix: classify old system tablet storage
astandrik May 6, 2026
48b4528
Merge branch 'main' into astandrik.3779-2
astandrik May 6, 2026
66f40d1
fix: handle aggregate tenant storage media
astandrik May 6, 2026
7472fdd
fix: some formatting fixes
astandrik May 6, 2026
25cb600
fix: display refactoring
astandrik May 6, 2026
fc4077c
fix: legacy storage
astandrik May 7, 2026
fa65b4e
fix: handle partial tenant storage breakdown
astandrik May 7, 2026
cc2acb2
fix: review fixes
astandrik May 7, 2026
e41d4c5
fix: review fix
astandrik May 8, 2026
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
5 changes: 4 additions & 1 deletion src/containers/Cluster/ClusterOverview/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ export function getDiagramValues({
}) {
const parsedValue = parseFloat(String(value));
const parsedCapacity = parseFloat(String(capacity));
const fillWidth = (parsedValue / parsedCapacity) * 100 || 0;
const fillWidth =
Number.isFinite(parsedValue) && Number.isFinite(parsedCapacity) && parsedCapacity > 0
? (parsedValue / parsedCapacity) * 100
: 0;

const legend = legendFormatter({
value: parsedValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,27 @@ export function MetricsTabs({
[poolsCpuStats],
);
const cpuMetrics = React.useMemo(() => calculateMetricAggregates(cpuPools), [cpuPools]);
const isServerless = databaseType === 'Serverless';

// Calculate storage metrics using utility
const storageStats = React.useMemo(
() => tabletStorageStats || blobStorageStats || [],
[tabletStorageStats, blobStorageStats],
);
const storageStats = React.useMemo(() => {
const hasLimit = (stats?: TenantStorageStats[]) =>
Boolean(stats?.some((item) => Number(item.limit) > 0));

if (isServerless) {
return tabletStorageStats || blobStorageStats || [];
}

if (hasLimit(blobStorageStats)) {
return blobStorageStats || [];
}

if (hasLimit(tabletStorageStats)) {
return tabletStorageStats || [];
}

return blobStorageStats || tabletStorageStats || [];
}, [blobStorageStats, isServerless, tabletStorageStats]);
const storageMetrics = React.useMemo(
() => calculateMetricAggregates(storageStats),
[storageStats],
Expand All @@ -106,8 +121,6 @@ export function MetricsTabs({

// card variant is handled within subcomponents

const isServerless = databaseType === 'Serverless';

const renderNetworkTab = () => {
if (!showNetworkUtilization) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {MetricsTabs} from './MetricsTabs/MetricsTabs';
import {TenantCpu} from './TenantCpu/TenantCpu';
import {TenantMemory} from './TenantMemory/TenantMemory';
import {TenantNetwork} from './TenantNetwork/TenantNetwork';
import {TenantStorage} from './TenantStorage/TenantStorage';
import {TenantStorageMode} from './TenantStorage/TenantStorageMode';
import i18n from './i18n';
import {b} from './utils';

Expand Down Expand Up @@ -155,9 +155,12 @@ export function TenantOverview({
}
case TENANT_METRICS_TABS_IDS.storage: {
return (
<TenantStorage
<TenantStorageMode
database={database}
databaseFullPath={databaseFullPath}
metrics={storageMetrics}
blobStorageStats={blobStorageStats}
tabletStorageStats={tabletStorageStats}
databaseType={Type}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
import {ProgressWrapper} from '../../../../../components/ProgressWrapper';
import {getTenantPath} from '../../../../../routes';
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import type {ETenantType} from '../../../../../types/api/tenant';
import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters';
import {useSearchQuery} from '../../../../../utils/hooks';
import {TenantTabsGroups} from '../../../TenantPages';
Expand All @@ -16,19 +15,9 @@ import i18n from '../i18n';
import {TopGroups} from './TopGroups';
import {TopTables} from './TopTables';
import {storageDashboardConfig} from './storageDashboardConfig';
import type {TenantStorageProps} from './types';

export interface TenantStorageMetrics {
blobStorageUsed?: number;
blobStorageLimit?: number;
tabletStorageUsed?: number;
tabletStorageLimit?: number;
}

interface TenantStorageProps {
database: string;
metrics: TenantStorageMetrics;
databaseType?: ETenantType;
}
export type {TenantStorageMetrics} from './types';

export function TenantStorage({database, metrics, databaseType}: TenantStorageProps) {
const {blobStorageUsed, tabletStorageUsed, blobStorageLimit, tabletStorageLimit} = metrics;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
useCapabilitiesLoaded,
useNewStorageViewEnabled,
useStorageStatsAvailable,
} from '../../../../../store/reducers/capabilities/hooks';

import {TenantStorage} from './TenantStorage';
import {TenantStorageNew} from './TenantStorageNew';
import type {TenantStorageProps} from './types';

export function TenantStorageMode(props: TenantStorageProps) {
const capabilitiesLoaded = useCapabilitiesLoaded();
const newStorageViewEnabled = useNewStorageViewEnabled();
const storageStatsAvailable = useStorageStatsAvailable();

const shouldUseLegacy =
props.databaseType === 'Serverless' ||
!newStorageViewEnabled ||
!capabilitiesLoaded ||
!storageStatsAvailable;

if (shouldUseLegacy) {
return <TenantStorage {...props} />;
}

return <TenantStorageNew {...props} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.ydb-tenant-storage-new {
--ydb-storage-segment-row-tables: #5282ff;
--ydb-storage-segment-column-tables: #ff7112;
--ydb-storage-segment-topics: #ed2a7a;
--ydb-storage-segment-system: var(--g-color-base-generic-medium);
--ydb-storage-segment-unknown: var(--g-color-text-misc);

&__sections-group {
display: flex;
flex-direction: column;
}

&__sections-inner {
display: flex;
flex-direction: column;
gap: var(--g-spacing-3);
}

&__summary-skeleton-card {
display: flex;
flex-direction: column;
gap: var(--g-spacing-3);

padding: var(--g-spacing-4);

border-radius: var(--g-border-radius-xs);
background: var(--g-color-base-generic);
}

&__summary-skeleton-title {
width: 132px;
height: 28px;
}

&__summary-skeleton-description {
width: 224px;
height: 20px;
}

&__summary-skeleton-rows {
display: flex;
flex-direction: column;
gap: var(--g-spacing-3);
}

&__summary-skeleton-row {
display: flex;
flex-direction: column;
gap: var(--g-spacing-3);
}

&__summary-skeleton-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--g-spacing-4);
}

&__summary-skeleton-label {
width: 56px;
height: 24px;
}

&__summary-skeleton-metrics {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--g-spacing-3);
}

&__summary-skeleton-metric {
width: 92px;
height: 42px;
}

&__summary-skeleton-progress {
width: 100%;
height: 20px;
}

&__summary-skeleton-footer {
display: flex;
justify-content: flex-end;
}

&__summary-skeleton-percent {
width: 76px;
height: 18px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react';

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

import {ResponseError} from '../../../../../components/Errors/ResponseError';
import {Skeleton} from '../../../../../components/Skeleton/Skeleton';
import {cn} from '../../../../../utils/cn';

import {
TenantStorageGroupedMediaSectionsView,
TenantStorageMediaSectionView,
} from './TenantStorageSummarySections';
import {TenantStorageTopUsageTable} from './TenantStorageTopUsageTable';
import type {TenantStorageProps} from './types';
import {useTenantStorageNewData} from './useTenantStorageNewData';
import {buildTenantStorageMediaSections} from './utils';

import './TenantStorageNew.scss';

const b = cn('ydb-tenant-storage-new');

function TenantStorageSummarySkeleton({grouped}: {grouped: boolean}) {
const cardRowsCount = grouped ? 2 : 1;

return (
<div className={b('sections-group')}>
<div className={b('sections-inner')}>
{[0, 1].map((cardIndex) => (
<div key={cardIndex} className={b('summary-skeleton-card')}>
Comment thread
astandrik marked this conversation as resolved.
Outdated
<Skeleton className={b('summary-skeleton-title')} delay={0} />
<Skeleton className={b('summary-skeleton-description')} delay={0} />
<div className={b('summary-skeleton-rows')}>
{Array.from({length: cardRowsCount}).map((_, rowIndex) => (
<div key={rowIndex} className={b('summary-skeleton-row')}>
<div className={b('summary-skeleton-header')}>
{grouped ? (
<Skeleton
className={b('summary-skeleton-label')}
delay={0}
/>
) : (
<div />
)}
<div className={b('summary-skeleton-metrics')}>
{[0, 1, 2].map((metricIndex) => (
<Skeleton
key={metricIndex}
className={b('summary-skeleton-metric')}
delay={0}
/>
))}
</div>
</div>
<Skeleton
className={b('summary-skeleton-progress')}
delay={0}
/>
<div className={b('summary-skeleton-footer')}>
<Skeleton
className={b('summary-skeleton-percent')}
delay={0}
/>
</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
);
}

export function TenantStorageNew({
database,
databaseFullPath,
metrics,
blobStorageStats,
tabletStorageStats,
}: TenantStorageProps) {
const {currentData, data, error, isFetching} = useTenantStorageNewData({
database,
databaseFullPath,
metrics,
});
const loading = isFetching && currentData === undefined;
const mediaSections = React.useMemo(() => {
return buildTenantStorageMediaSections({
blobStorageStats,
metrics,
tabletStorageStats,
});
}, [blobStorageStats, metrics, tabletStorageStats]);

if (error && !currentData) {
return <ResponseError error={error} />;
}

const topRowsError = data.topRowsError ?? error;
const grouped = mediaSections.length > 1;

if (loading) {
return (
<Flex direction="column" gap={4} className={b()}>
<TenantStorageSummarySkeleton grouped={grouped} />
<TenantStorageTopUsageTable loading error={undefined} rows={[]} withData={false} />
</Flex>
);
}

return (
<Flex direction="column" gap={4} className={b()}>
<div className={b('sections-group')}>
<div className={b('sections-inner')}>
{grouped ? (
<TenantStorageGroupedMediaSectionsView
sections={mediaSections}
data={data}
/>
) : (
mediaSections.map((section, index) => (
<TenantStorageMediaSectionView
key={`${section.mediaType}-${index}`}
section={section}
showMediaTypeLabel={false}
data={data}
/>
))
)}
</div>
</div>
<TenantStorageTopUsageTable
loading={false}
error={topRowsError}
rows={data.topRows}
withData={Boolean(currentData)}
/>
</Flex>
);
}
Loading
Loading