diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.scss b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.scss new file mode 100644 index 0000000000..2fbe6073f9 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.scss @@ -0,0 +1,57 @@ +.ydb-partitions-progress { + &__segment { + display: flex; + flex-basis: 0; + + &_additional { + min-width: 20px; + } + + &_main { + min-width: 70px; + } + } + + &__segment-bar { + --segment-filled-bg-color: var(--g-color-base-neutral-heavy); + --segment-empty-bg-color: var(--g-color-base-neutral-light); + overflow: hidden; + + height: 10px; + + border-radius: var(--g-border-radius-xs); + background-color: var(--segment-empty-bg-color); + + &_additional { + --segment-filled-bg-color: var(--g-color-base-danger-heavy); + --segment-empty-bg-color: var(--g-color-base-danger-light); + } + + &_main { + --segment-filled-bg-color: var(--g-color-base-info-heavy); + --segment-empty-bg-color: var(--g-color-base-info-light); + } + } + + &__segment-fill { + height: 100%; + + background-color: var(--segment-filled-bg-color); + + transition: + transform 0.6s ease, + width 0.6s ease, + background-color 0.6s ease; + + &_min-fill { + min-width: 20px; + } + } + + &__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); + } +} diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.tsx b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.tsx new file mode 100644 index 0000000000..04edd1a941 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/PartitionsProgress.tsx @@ -0,0 +1,143 @@ +import {Flex, Popover, Text} from '@gravity-ui/uikit'; +import {isNil} from 'lodash'; + +import {cn} from '../../../../../../utils/cn'; + +import {getPartitionsTooltip} from './helpers'; +import i18n from './i18n'; +import {FULL_FILL_VALUE, calcPartitionsProgress} from './utils'; + +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> = { + additional: {additional: true}, + main: {main: true}, +}; + +interface SegmentProgressBarProps { + position: SegmentPosition; + value: number; + withMinFill?: boolean; +} + +const SegmentProgressBar = ({position, value, withMinFill}: SegmentProgressBarProps) => { + const width = `${value}%`; + + return ( +
+
+
+ ); +}; + +export const PartitionsProgress = ({ + minPartitions, + maxPartitions, + partitionsCount, + className, +}: PartitionsProgressProps) => { + const { + min, + max, + partitionsBelowMin, + partitionsAboveMax, + isBelowMin, + isAboveMax, + leftSegmentUnits, + mainSegmentUnits, + rightSegmentUnits, + mainProgressValue, + } = calcPartitionsProgress(minPartitions, maxPartitions, partitionsCount); + + const tooltip = getPartitionsTooltip({ + count: partitionsCount, + belowMin: partitionsBelowMin, + aboveMax: partitionsAboveMax, + isBelowMin: isBelowMin, + isAboveMax: isAboveMax, + }); + + const maxLabel = isNil(max) ? i18n('value_no-limit') : max; + + const hasAdditionalSegments = isBelowMin || isAboveMax; + const withMinFill = !hasAdditionalSegments; + + return ( + + + {isBelowMin && ( + + + + + + {partitionsCount} + + + + )} + + + + + + + {min} + + + {maxLabel} + + + + + {isAboveMax && ( + + + + + + {partitionsCount} + + + + )} + + + ); +}; diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/__test__/calcPartitionsProgress.test.ts b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/__test__/calcPartitionsProgress.test.ts new file mode 100644 index 0000000000..4a8747224b --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/__test__/calcPartitionsProgress.test.ts @@ -0,0 +1,199 @@ +import {FULL_FILL_VALUE, calcPartitionsProgress} from '../utils'; + +describe('calcPartitionsProgress', () => { + describe('when maxPartitions is not provided', () => { + test('should reserve fixed 20% (1:4) for below-min segment when count < min', () => { + // minPartitions = 5, partitionsCount = 2 + const res = calcPartitionsProgress(5, undefined, 2); + + expect(res).toEqual({ + min: 5, + max: undefined, + + partitionsBelowMin: 3, + partitionsAboveMax: 0, + + isBelowMin: true, + isAboveMax: false, + + leftSegmentUnits: 1, + mainSegmentUnits: 4, + rightSegmentUnits: 0, + + mainProgressValue: 0, + }); + }); + + test('should not show warning segment when count >= min', () => { + // minPartitions = 3, partitionsCount = 3 + const res = calcPartitionsProgress(3, undefined, 3); + + expect(res).toEqual({ + min: 3, + max: undefined, + + partitionsBelowMin: 0, + partitionsAboveMax: 0, + + isBelowMin: false, + isAboveMax: false, + + leftSegmentUnits: 0, + mainSegmentUnits: 1, + rightSegmentUnits: 0, + + mainProgressValue: FULL_FILL_VALUE, + }); + }); + }); + + describe('when maxPartitions is provided', () => { + test('should calculate proportional progress inside [min, max]', () => { + // minPartitions = 2, maxPartitions = 6, partitionsCount = 4 => (4-2)/(6-2)=0.5 => 50% + const res = calcPartitionsProgress(2, 6, 4); + + expect(res).toEqual({ + min: 2, + max: 6, + + partitionsBelowMin: 0, + partitionsAboveMax: 0, + + isBelowMin: false, + isAboveMax: false, + + leftSegmentUnits: 0, + mainSegmentUnits: 4, + rightSegmentUnits: 0, + + mainProgressValue: 50, + }); + }); + + test('should show left overflow and empty main when count < min', () => { + // minPartitions = 5, maxPartitions = 10, partitionsCount = 2 + const res = calcPartitionsProgress(5, 10, 2); + + expect(res).toEqual({ + min: 5, + max: 10, + + partitionsBelowMin: 3, + partitionsAboveMax: 0, + + isBelowMin: true, + isAboveMax: false, + + leftSegmentUnits: 3, + mainSegmentUnits: 5, + rightSegmentUnits: 0, + + mainProgressValue: 0, + }); + }); + + test('should show right overflow and full main when count > max', () => { + // minPartitions = 3, maxPartitions = 10, partitionsCount = 13 + const res = calcPartitionsProgress(3, 10, 13); + + expect(res).toEqual({ + min: 3, + max: 10, + + partitionsBelowMin: 0, + partitionsAboveMax: 3, + + isBelowMin: false, + isAboveMax: true, + + leftSegmentUnits: 0, + mainSegmentUnits: 7, + rightSegmentUnits: 3, + + mainProgressValue: FULL_FILL_VALUE, + }); + }); + + test('should fill main segment when min === max and count equals limits', () => { + // minPartitions = 5, maxPartitions = 5, partitionsCount = 5 + const res = calcPartitionsProgress(5, 5, 5); + + expect(res).toEqual({ + min: 5, + max: 5, + + partitionsBelowMin: 0, + partitionsAboveMax: 0, + + isBelowMin: false, + isAboveMax: false, + + leftSegmentUnits: 0, + mainSegmentUnits: 1, + rightSegmentUnits: 0, + + mainProgressValue: FULL_FILL_VALUE, + }); + }); + + test('should keep main segment empty when min === max and count < min', () => { + // minPartitions = 5, maxPartitions = 5, partitionsCount = 3 + const res = calcPartitionsProgress(5, 5, 3); + + expect(res).toEqual({ + min: 5, + max: 5, + + partitionsBelowMin: 2, + partitionsAboveMax: 0, + + isBelowMin: true, + isAboveMax: false, + + leftSegmentUnits: 2, + mainSegmentUnits: 1, + rightSegmentUnits: 0, + + mainProgressValue: 0, + }); + }); + + test('should show right overflow and fill main when min === max and count > max', () => { + // minPartitions = 5, maxPartitions = 5, partitionsCount = 7 + const res = calcPartitionsProgress(5, 5, 7); + + expect(res).toEqual({ + min: 5, + max: 5, + + partitionsBelowMin: 0, + partitionsAboveMax: 2, + + isBelowMin: false, + isAboveMax: true, + + leftSegmentUnits: 0, + mainSegmentUnits: 1, + rightSegmentUnits: 2, + + mainProgressValue: FULL_FILL_VALUE, + }); + }); + + test('should clamp maxPartitions to minPartitions when max < min', () => { + // minPartitions = 5, maxPartitions = 2, partitionsCount = 6 + const res = calcPartitionsProgress(5, 2, 6); + + expect(res.min).toBe(5); + expect(res.max).toBe(5); + }); + }); + + describe('min clamping', () => { + test('should clamp minPartitions to at least 1', () => { + // minPartitions = 0, partitionsCount = 1 + const res = calcPartitionsProgress(0, undefined, 1); + expect(res.min).toBe(1); + }); + }); +}); diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/helpers.ts b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/helpers.ts new file mode 100644 index 0000000000..f752dcf0ef --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/helpers.ts @@ -0,0 +1,35 @@ +import i18n from './i18n'; + +export const getPartitionsNoun = (count: number) => + count === 1 ? i18n('value_partition-one') : i18n('value_partition-many'); + +export function getPartitionsTooltip(params: { + count: number; + belowMin: number; + aboveMax: number; + isBelowMin: boolean; + isAboveMax: boolean; +}) { + const partitions = getPartitionsNoun(params.count); + + if (params.isBelowMin) { + return i18n('tooltip_partitions-below-limit', { + count: params.count, + diff: params.belowMin, + partitions, + }); + } + + if (params.isAboveMax) { + return i18n('tooltip_partitions-above-limit', { + count: params.count, + diff: params.aboveMax, + partitions, + }); + } + + return i18n('tooltip_partitions-current', { + count: params.count, + partitions, + }); +} diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/en.json new file mode 100644 index 0000000000..ab452650af --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/en.json @@ -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" +} diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/index.ts b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/index.ts new file mode 100644 index 0000000000..0062f51ab4 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../../../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'partitions-progress'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/utils.ts b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/utils.ts new file mode 100644 index 0000000000..8c108c9fb8 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/PartitionsProgress/utils.ts @@ -0,0 +1,116 @@ +import {isNil} from 'lodash'; + +export const FULL_FILL_VALUE = 100; + +// 20% warning segment when max is not provided: 1 / (1 + 4) = 20% +const NO_MAX_LEFT_UNITS = 1; +const NO_MAX_MAIN_UNITS = 4; + +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 = Math.max(1, minPartitions); + + if (isNil(maxPartitions)) { + return calcPartitionsProgressWithoutMax(min, partitionsCount); + } + + return calcPartitionsProgressWithMax(min, maxPartitions, partitionsCount); +} + +export function calcPartitionsProgressWithoutMax( + min: number, + count: number, +): PartitionsProgressCalcResult { + const partitionsBelowMin = Math.max(0, min - count); + const isBelowMin = partitionsBelowMin > 0; + + const leftSegmentUnits = isBelowMin ? NO_MAX_LEFT_UNITS : 0; + const mainSegmentUnits = isBelowMin ? NO_MAX_MAIN_UNITS : 1; + + return { + min, + max: undefined, + + partitionsBelowMin, + partitionsAboveMax: 0, + + isBelowMin, + isAboveMax: false, + + leftSegmentUnits, + mainSegmentUnits, + rightSegmentUnits: 0, + + mainProgressValue: count < min ? 0 : FULL_FILL_VALUE, + }; +} + +export function calcPartitionsProgressWithMax( + min: number, + max: number, + count: number, +): PartitionsProgressCalcResult { + const normalizedMax = Math.max(max, min); + const range = Math.max(0, normalizedMax - min); + + const partitionsBelowMin = Math.max(0, min - count); + const partitionsAboveMax = Math.max(0, count - normalizedMax); + + const isBelowMin = partitionsBelowMin > 0; + const isAboveMax = partitionsAboveMax > 0; + + const mainProgressValue = calcMainProgressValue(min, normalizedMax, range, count); + + return { + min, + max: normalizedMax, + + partitionsBelowMin, + partitionsAboveMax, + + isBelowMin, + isAboveMax, + + leftSegmentUnits: isBelowMin ? partitionsBelowMin : 0, + mainSegmentUnits: range || 1, + rightSegmentUnits: isAboveMax ? partitionsAboveMax : 0, + + mainProgressValue, + }; +} + +function calcMainProgressValue(min: number, max: number, range: number, count: number) { + if (range === 0) { + return count >= min ? FULL_FILL_VALUE : 0; + } + + if (count <= min) { + return 0; + } + + if (count >= max) { + return FULL_FILL_VALUE; + } + + return ((count - min) / range) * 100; +} diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.scss b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.scss index 4f787845b4..5774ba225e 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.scss +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.scss @@ -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 { diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx index 74eaf96f84..064e92b1d4 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx @@ -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'; @@ -18,22 +18,33 @@ interface TableInfoProps { } export const TableInfo = ({data, type}: TableInfoProps) => { - const title = ; - 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 (
+
{i18n('title_partitioning')}
+ {isPartitionsProgressEnabled && partitionProgressConfig && ( +
+ +
+ )}
{title}
} + renderEmptyState={() => null} />
{tableStatsInfo ? ( @@ -42,7 +53,7 @@ export const TableInfo = ({data, type}: TableInfoProps) => { null} /> @@ -53,13 +64,13 @@ export const TableInfo = ({data, type}: TableInfoProps) => {
null} /> null} /> diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json index e449db46b7..5ad08ad3e3 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json @@ -1,20 +1,21 @@ { - "tableStats": "Table Stats", - "tabletMetrics": "Tablet Metrics", - "partitionConfig": "Partition Config", + "title_partitioning": "Partitioning", + "title_table-stats": "Stats", + "title_tablet-metrics": "Metrics", + "title_partition-config": "Partition config", - "label.ttl": "TTL for rows", - "value.ttl": "column: '{{columnName}}', expire after: {{expireTime}}", - "label.standalone": "Standalone", - "label.partitioning": "Partitioning", - "label.partitioning-by-size": "Partitioning by size", - "value.partitioning-by-size.enabled": "Enabled, split size: {{size}}", - "label.partitioning-by-load": "Partitioning by load", - "label.partitions-min": "Min number of partitions", - "label.partitions-max": "Max number of partitions", - "label.read-replicas": "Read replicas (followers)", - "label.bloom-filter": "Bloom filter", + "field_ttl-for-rows": "TTL for rows", + "field_standalone": "Standalone", + "field_partitioning": "Partitioning", + "field_partitioning-by-size": "Partitioning by size", + "field_partitioning-by-load": "Partitioning by load", + "field_min-partitions-count": "Min number of partitions", + "field_max-partitions-count": "Max number of partitions", + "field_read-replicas": "Read replicas (followers)", + "field_bloom-filter": "Bloom filter", - "enabled": "Enabled", - "disabled": "Disabled" + "value_ttl-config": "column: '{{columnName}}', expire after: {{expireTime}}", + "value_partitioning-by-size-enabled": "Enabled, split size: {{size}}", + "value_enabled": "Enabled", + "value_disabled": "Disabled" } diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.tsx index b96e78fef9..97eea34220 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.tsx @@ -1,4 +1,5 @@ import {Text} from '@gravity-ui/uikit'; +import {isNil} from 'lodash'; import omit from 'lodash/omit'; import {toFormattedSize} from '../../../../../components/FormattedBytes/utils'; @@ -16,9 +17,9 @@ import type { TEvDescribeSchemeResult, TPartitionConfig, TTTLSettings, + TTablePartition, } from '../../../../../types/api/schema'; import {EPathType} from '../../../../../types/api/schema'; -import {valueIsDefined} from '../../../../../utils'; import {formatBytes, formatNumber} from '../../../../../utils/dataFormatters/dataFormatters'; import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers'; import {isNumeric} from '../../../../../utils/utils'; @@ -33,12 +34,12 @@ const isInStoreColumnTable = (table: TColumnTableDescription) => { const prepareTTL = (ttl: TTTLSettings | TColumnDataLifeCycle) => { // ExpireAfterSeconds could be 0 if (ttl.Enabled && ttl.Enabled.ColumnName && ttl.Enabled.ExpireAfterSeconds !== undefined) { - const value = i18n('value.ttl', { + const value = i18n('value_ttl-config', { columnName: ttl.Enabled.ColumnName, expireTime: formatDurationToShortTimeFormat(ttl.Enabled.ExpireAfterSeconds * 1000, 1), }); - return {label: i18n('label.ttl'), value}; + return {label: i18n('field_ttl-for-rows'), value}; } return undefined; }; @@ -47,7 +48,7 @@ function prepareColumnTableGeneralInfo(columnTable: TColumnTableDescription) { const columnTableGeneralInfo: InfoViewerItem[] = []; columnTableGeneralInfo.push({ - label: i18n('label.standalone'), + label: i18n('field_standalone'), value: String(!isInStoreColumnTable(columnTable)), }); @@ -56,7 +57,7 @@ function prepareColumnTableGeneralInfo(columnTable: TColumnTableDescription) { const content = `PARTITION BY HASH(${columns})`; columnTableGeneralInfo.push({ - label: i18n('label.partitioning'), + label: i18n('field_partitioning'), value: ( {content} @@ -82,27 +83,27 @@ const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings? const partitioningBySize = PartitioningPolicy.SizeToSplit && Number(PartitioningPolicy.SizeToSplit) > 0 - ? i18n('value.partitioning-by-size.enabled', { + ? i18n('value_partitioning-by-size-enabled', { size: formatBytes(PartitioningPolicy.SizeToSplit), }) - : i18n('disabled'); + : i18n('value_disabled'); const partitioningByLoad = PartitioningPolicy.SplitByLoadSettings?.Enabled - ? i18n('enabled') - : i18n('disabled'); + ? i18n('value_enabled') + : i18n('value_disabled'); generalTableInfo.push( - {label: i18n('label.partitioning-by-size'), value: partitioningBySize}, - {label: i18n('label.partitioning-by-load'), value: partitioningByLoad}, + {label: i18n('field_partitioning-by-size'), value: partitioningBySize}, + {label: i18n('field_partitioning-by-load'), value: partitioningByLoad}, { - label: i18n('label.partitions-min'), + label: i18n('field_min-partitions-count'), value: formatNumber(PartitioningPolicy.MinPartitionsCount || 0), }, ); if (PartitioningPolicy.MaxPartitionsCount) { generalTableInfo.push({ - label: i18n('label.partitions-max'), + label: i18n('field_max-partitions-count'), value: formatNumber(PartitioningPolicy.MaxPartitionsCount), }); } @@ -119,7 +120,7 @@ const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings? readReplicasConfig = `ANY_AZ: ${FollowerCount}`; } - generalTableInfo.push({label: i18n('label.read-replicas'), value: readReplicasConfig}); + generalTableInfo.push({label: i18n('field_read-replicas'), value: readReplicasConfig}); } if (TTLSettings) { @@ -129,16 +130,41 @@ const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings? } } - if (valueIsDefined(EnableFilterByKey)) { + if (!isNil(EnableFilterByKey)) { generalTableInfo.push({ - label: i18n('label.bloom-filter'), - value: EnableFilterByKey ? i18n('enabled') : i18n('disabled'), + label: i18n('field_bloom-filter'), + value: EnableFilterByKey ? i18n('value_enabled') : i18n('value_disabled'), }); } return generalTableInfo; }; +type PartitionProgressConfig = { + minPartitions: number; + maxPartitions?: number; + partitionsCount: number; +}; + +const preparePartitionProgressConfig = ( + PartitionConfig: TPartitionConfig, + TablePartitions?: TTablePartition[], +): PartitionProgressConfig => { + const {PartitioningPolicy} = PartitionConfig; + + // We are convinced, there is always at least one partition; + // fallback and clamp to 1 if value is missing. + const minPartitions = Math.max(1, PartitioningPolicy?.MinPartitionsCount ?? 1); + const maxPartitions = PartitioningPolicy?.MaxPartitionsCount; + const partitionsCount = TablePartitions?.length ?? 1; + + return { + minPartitions, + maxPartitions, + partitionsCount, + }; +}; + /** Prepares data for Table, ColumnTable and ColumnStore */ export const prepareTableInfo = (data?: TEvDescribeSchemeResult, type?: EPathType) => { if (!data) { @@ -148,6 +174,7 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult, type?: EPathTyp const {PathDescription = {}} = data; const { + TablePartitions, TableStats = {}, TabletMetrics = {}, Table: {PartitionConfig = {}, TTLSettings} = {}, @@ -181,10 +208,15 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult, type?: EPathTyp const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig; let generalInfo: InfoViewerItem[] = []; + let partitionProgressConfig: PartitionProgressConfig | undefined; switch (type) { case EPathType.EPathTypeTable: { generalInfo = prepareTableGeneralInfo(PartitionConfig, TTLSettings); + partitionProgressConfig = preparePartitionProgressConfig( + PartitionConfig, + TablePartitions, + ); break; } case EPathType.EPathTypeColumnTable: { @@ -252,5 +284,11 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult, type?: EPathTyp ); } - return {generalInfo, tableStatsInfo, tabletMetricsInfo, partitionConfigInfo}; + return { + generalInfo, + tableStatsInfo, + tabletMetricsInfo, + partitionConfigInfo, + partitionProgressConfig, + }; }; diff --git a/src/types/api/schema/schema.ts b/src/types/api/schema/schema.ts index 319268cc69..0ba521918b 100644 --- a/src/types/api/schema/schema.ts +++ b/src/types/api/schema/schema.ts @@ -334,7 +334,7 @@ interface TPathVersion { GeneralVersion?: string; } -interface TTablePartition { +export interface TTablePartition { /** bytes */ EndOfRangeKeyPrefix?: unknown; IsPoint?: boolean; diff --git a/src/types/window.d.ts b/src/types/window.d.ts index cb10abf0a8..abd9bfa486 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -45,5 +45,7 @@ interface Window { api: import('../services/api/index').YdbEmbeddedAPI; + WINDOW_SHOW_TABLE_SETTINGS?: boolean; + [key: `yaCounter${number}`]: any; }