Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.ydb-partitions-progress {
&__segment {
display: flex;
flex-basis: 0;

&_additional {
min-width: 20px;
}

&_main {
min-width: 70px;
}
}

&__segment-bar {
&_additional {
--g-progress-filled-background-color: var(--g-color-base-danger-heavy);
}

&_main {
--g-progress-filled-background-color: var(--g-color-base-info-heavy);
--g-progress-empty-background-color: var(--g-color-base-info-light);
}
}

&__segment-touched {
padding: var(--g-spacing-2);

font-size: var(--g-text-body-2-font-size);
line-height: var(--g-text-body-2-line-height);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {Flex, Popover, Progress, Text} from '@gravity-ui/uikit';
import {isNil} from 'lodash';

import {cn} from '../../../../../../utils/cn';

import {calcPartitionsProgress, getPartitionsLabel} from './helpers';
import i18n from './i18n';

import './PartitionsProgress.scss';

const b = cn('ydb-partitions-progress');

interface PartitionsProgressProps {
minPartitions: number;
maxPartitions?: number;
partitionsCount: number;
className?: string;
}

type SegmentPosition = 'main' | 'additional';

const SEGMENT_MODS: Record<SegmentPosition, Record<string, boolean>> = {
additional: {additional: true},
main: {main: true},
};

export const FULL_FILL_VALUE = 100;

interface SegmentProgressBarProps {
position: SegmentPosition;
value: number;
}

const SegmentProgressBar = ({position, value}: SegmentProgressBarProps) => (
<div className={b('segment-bar', SEGMENT_MODS[position])}>
<Progress value={value} size="s" />
</div>
);

export const PartitionsProgress = ({
minPartitions,
maxPartitions,
partitionsCount,
className,
}: PartitionsProgressProps) => {
const {
min,
max,
partitionsBelowMin,
partitionsAboveMax,
isBelowMin,
isAboveMax,
leftSegmentUnits,
mainSegmentUnits,
rightSegmentUnits,
mainProgressValue,
} = calcPartitionsProgress(minPartitions, maxPartitions, partitionsCount);

const partitionsLabel = getPartitionsLabel(partitionsCount);

const belowLimitTooltip = i18n('tooltip_partitions-below-limit', {
count: partitionsCount,
diff: partitionsBelowMin,
partitions: partitionsLabel,
});

const aboveLimitTooltip = i18n('tooltip_partitions-above-limit', {
count: partitionsCount,
diff: partitionsAboveMax,
partitions: partitionsLabel,
});

const currentTooltip = i18n('tooltip_partitions-current', {
count: partitionsCount,
partitions: partitionsLabel,
});

const maxLabel = isNil(max) ? i18n('value_no-limit') : max;

let tooltipContent = currentTooltip;
if (isBelowMin) {
tooltipContent = belowLimitTooltip;
} else if (isAboveMax) {
tooltipContent = aboveLimitTooltip;
}

return (
<Popover hasArrow placement="top" content={tooltipContent} className={b('segment-touched')}>
<Flex alignItems="center" gap="0.5" className={b(null, className)}>
{isBelowMin && (
<Flex
style={{flexGrow: leftSegmentUnits}}
direction="column"
gap="2"
className={b('segment', SEGMENT_MODS.additional)}
>
<SegmentProgressBar position="additional" value={FULL_FILL_VALUE} />

<Flex justifyContent="flex-start">
<Text variant="body-2" color="secondary">
{partitionsCount}
</Text>
</Flex>
</Flex>
)}

<Flex
direction="column"
className={b('segment', SEGMENT_MODS.main)}
style={{flexGrow: mainSegmentUnits}}
gap="2"
>
<SegmentProgressBar position="main" value={mainProgressValue} />

<Flex justifyContent="space-between">
<Text variant="body-2" color="secondary">
{min}
</Text>
<Text variant="body-2" color="secondary">
{maxLabel}
</Text>
</Flex>
</Flex>

{isAboveMax && (
<Flex
direction="column"
gap="2"
className={b('segment', SEGMENT_MODS.additional)}
style={{flexGrow: rightSegmentUnits}}
>
<SegmentProgressBar position="additional" value={FULL_FILL_VALUE} />

<Flex justifyContent="flex-end">
<Text variant="body-2" color="secondary">
{partitionsCount}
</Text>
</Flex>
</Flex>
)}
</Flex>
</Popover>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {isNil} from 'lodash';

import {FULL_FILL_VALUE} from './PartitionsProgress';
import i18n from './i18n';

export interface PartitionsProgressCalcResult {
min: number;
max?: number;

partitionsBelowMin: number;
partitionsAboveMax: number;

isBelowMin: boolean;
isAboveMax: boolean;

leftSegmentUnits: number;
mainSegmentUnits: number;
rightSegmentUnits: number;

mainProgressValue: number;
}

export function calcPartitionsProgress(
minPartitions: number,
maxPartitions: number | undefined,
partitionsCount: number,
): PartitionsProgressCalcResult {
const min = minPartitions;

const hasMaxLimit = !isNil(maxPartitions);

if (!hasMaxLimit) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would be good to move calculations to some function (maybe utils.ts in the same folder) and to cover it with unit tests

const partitionsBelowMin = Math.max(0, min - partitionsCount);
const partitionsAboveMax = 0;
const isBelowMin = partitionsBelowMin > 0;
const isAboveMax = false;

// When max limit is not provided, reserve a fixed 20% of the total width
// for the "below min" segment (1:4 ratio between warning and main segments).
const leftSegmentUnits = isBelowMin ? 1 : 0;
const mainSegmentUnits = isBelowMin ? 4 : 1;
const rightSegmentUnits = 0;

const mainProgressValue = partitionsCount < min ? 0 : FULL_FILL_VALUE;

return {
min,
max: undefined,

partitionsBelowMin,
partitionsAboveMax,

isBelowMin,
isAboveMax,

leftSegmentUnits,
mainSegmentUnits,
rightSegmentUnits,

mainProgressValue,
};
}

const max = Math.max(maxPartitions as number, minPartitions);

const range = Math.max(0, max - min);

const partitionsBelowMin = Math.max(0, min - partitionsCount);
const partitionsAboveMax = Math.max(0, partitionsCount - max);

const isBelowMin = partitionsBelowMin > 0;
const isAboveMax = partitionsAboveMax > 0;

const mainSegmentUnits = range || 1;

let mainProgressValue = 0;
if (range > 0) {
if (partitionsCount <= min) {
mainProgressValue = 0;
} else if (partitionsCount >= max) {
mainProgressValue = FULL_FILL_VALUE;
} else {
mainProgressValue = ((partitionsCount - min) / range) * 100;
}
}

const leftSegmentUnits = isBelowMin ? partitionsBelowMin : 0;
const rightSegmentUnits = isAboveMax ? partitionsAboveMax : 0;

return {
min,
max,

partitionsBelowMin,
partitionsAboveMax,

isBelowMin,
isAboveMax,

leftSegmentUnits,
mainSegmentUnits,
rightSegmentUnits,

mainProgressValue,
};
}

export const getPartitionsLabel = (count: number) =>
count === 1 ? i18n('value_partition-one') : i18n('value_partition-many');
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tooltip_partitions-current": "{{count}} {{partitions}}",
"tooltip_partitions-below-limit": "{{count}} {{partitions}}. {{diff}} less than the limit",
"tooltip_partitions-above-limit": "{{count}} {{partitions}}. {{diff}} over the limit",

"value_partition-one": "partition",
"value_partition-many": "partitions",

"value_no-limit": "No limit"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeysets} from '../../../../../../../utils/i18n';

import en from './en.json';

const COMPONENT = 'partitions-progress';

export default registerKeysets(COMPONENT, {en});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

.ydb-diagnostics-table-info {
&__title {
@include mixins.info-viewer-title();
margin-bottom: 10px;

font-weight: 600;
@include mixins.body-2-typography();
}

&__progress-bar {
max-width: 656px;
padding: var(--g-spacing-3) 0 var(--g-spacing-4);
}

&__row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import React from 'react';
import {InfoViewer} from '../../../../../components/InfoViewer';
import type {EPathType, TEvDescribeSchemeResult} from '../../../../../types/api/schema';
import {cn} from '../../../../../utils/cn';
import {EntityTitle} from '../../../EntityTitle/EntityTitle';

import {PartitionsProgress} from './PartitionsProgress/PartitionsProgress';
import i18n from './i18n';
import {prepareTableInfo} from './prepareTableInfo';

Expand All @@ -18,22 +18,33 @@ interface TableInfoProps {
}

export const TableInfo = ({data, type}: TableInfoProps) => {
const title = <EntityTitle data={data?.PathDescription} />;

const {
generalInfo,
tableStatsInfo,
tabletMetricsInfo = [],
partitionConfigInfo = [],
partitionProgressConfig,
} = React.useMemo(() => prepareTableInfo(data, type), [data, type]);

// Feature flag: show partitions progress only if WINDOW_SHOW_TABLE_SETTINGS is truthy
const isPartitionsProgressEnabled = Boolean(window.WINDOW_SHOW_TABLE_SETTINGS);

return (
<div className={b()}>
<div className={b('title')}>{i18n('title')}</div>
{isPartitionsProgressEnabled && partitionProgressConfig && (
<div className={b('progress-bar')}>
<PartitionsProgress
minPartitions={partitionProgressConfig.minPartitions}
partitionsCount={partitionProgressConfig.partitionsCount}
maxPartitions={partitionProgressConfig.maxPartitions}
/>
</div>
)}
<InfoViewer
info={generalInfo}
title={title}
className={b('info-block')}
renderEmptyState={() => <div className={b('title')}>{title}</div>}
renderEmptyState={() => null}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is returning null instead of message intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is intentional)
Previously we used renderEmptyState to render the block title when there was no data, because the title was inside InfoViewer. After implementing a progress bar TableInfo renders a permanent header above the whole block. If I return a title from renderEmptyState now, we will have two titles for the same section in "no data" case, which looks redundant.

/>
<div className={b('row')}>
{tableStatsInfo ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"tableStats": "Table Stats",
"tabletMetrics": "Tablet Metrics",
"title": "Partitioning",
"tableStats": "Stats",
"tabletMetrics": "Metrics",
Copy link
Collaborator

Choose a reason for hiding this comment

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

it would be great to create new keys in i18n-naming-ruleset.md format

(and very super best to change old keys to use it =)) )

"partitionConfig": "Partition Config",

"label.ttl": "TTL for rows",
Expand Down
Loading
Loading