Skip to content

fix(FormattedBytes): show 1_000 with another unit #1901

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 3 commits into from
Feb 4, 2025
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
2 changes: 1 addition & 1 deletion src/components/FormattedBytes/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export const toFormattedSize = (
return null;
}

return <FormattedBytes value={value} significantDigits={2} {...params} />;
return <FormattedBytes value={value} {...params} />;
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems we don't use significantDigits param any more. What do you think about remove it completely? It seems it will simplify logic for formatting values a lot.

};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {DefinitionList} from '@gravity-ui/components';

import {ContentWithPopup} from '../../../../../components/ContentWithPopup/ContentWithPopup';
import type {DiskErasureGroupsStats} from '../../../../../store/reducers/cluster/types';
import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
import {formatBytes, getBytesSizeUnit} from '../../../../../utils/bytesParsers';
import {cn} from '../../../../../utils/cn';
import i18n from '../../../i18n';

Expand Down Expand Up @@ -36,7 +36,7 @@ interface GroupsStatsPopupContentProps {
function GroupsStatsPopupContent({stats}: GroupsStatsPopupContentProps) {
const {diskType, erasure, allocatedSize, availableSize} = stats;

const sizeToConvert = getSizeWithSignificantDigits(Math.max(allocatedSize, availableSize), 2);
const sizeToConvert = getBytesSizeUnit(Math.max(allocatedSize, availableSize));

const convertedAllocatedSize = formatBytes({value: allocatedSize, size: sizeToConvert});
const convertedAvailableSize = formatBytes({value: availableSize, size: sizeToConvert});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {CellWithPopover} from '../../../../../components/CellWithPopover/CellWit
import {LinkToSchemaObject} from '../../../../../components/LinkToSchemaObject/LinkToSchemaObject';
import {topTablesApi} from '../../../../../store/reducers/tenantOverview/executeTopTables/executeTopTables';
import type {KeyValueRow} from '../../../../../types/api/query';
import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
import {formatBytes, getBytesSizeUnit} from '../../../../../utils/bytesParsers';
import {useAutoRefreshInterval} from '../../../../../utils/hooks';
import {parseQueryErrorToString} from '../../../../../utils/query';
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
Expand Down Expand Up @@ -35,7 +35,7 @@ export function TopTables({path}: TopTablesProps) {
const data = currentData?.resultSets?.[0]?.result || [];

const formatSize = (value?: number) => {
const size = getSizeWithSignificantDigits(data?.length ? Number(data[0].Size) : 0, 0);
const size = getBytesSizeUnit(data?.length ? Number(data[0].Size) : 0);

return formatBytes({value, size, precision: 1});
};
Expand Down
14 changes: 0 additions & 14 deletions src/utils/bytesParsers/__test__/formatBytes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,6 @@ describe('formatBytes', () => {
`100${UNBREAKABLE_GAP}000${UNBREAKABLE_GAP}B/s`,
);
});
it('should return fixed amount of significant digits', () => {
expect(formatBytes({value: 99_000, significantDigits: 2})).toEqual(
`99${UNBREAKABLE_GAP}000${UNBREAKABLE_GAP}B`,
);
expect(formatBytes({value: 100_000, significantDigits: 2})).toEqual(
`100${UNBREAKABLE_GAP}KB`,
);
expect(formatBytes({value: 99_000_000_000_000, significantDigits: 2})).toEqual(
`99${UNBREAKABLE_GAP}000${UNBREAKABLE_GAP}GB`,
);
expect(formatBytes({value: 100_000_000_000_000, significantDigits: 2})).toEqual(
`100${UNBREAKABLE_GAP}TB`,
);
});
it('should return empty string on invalid data', () => {
expect(formatBytes({value: undefined})).toEqual('');
expect(formatBytes({value: null})).toEqual('');
Expand Down
42 changes: 7 additions & 35 deletions src/utils/bytesParsers/formatBytes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,23 @@ const sizes = {
value: TERABYTE,
label: i18n('tb'),
},
};
} as const;

export type BytesSizes = keyof typeof sizes;

/**
* This function is needed to keep more than 3 digits of the same size.
*
* @param significantDigits - number of digits above 3
* @returns size to format value to get required number of digits
*
* By default value converted to the next size when it's above 1000,
* so we have 900mb and 1gb. To extend it additional significantDigits could be set
*
* significantDigits value added above default 3
*
* significantDigits = 1 - 9 000 mb and 10 gb
*
* significantDigits = 2 - 90 000 mb and 100 gb
*
* significantDigits = 3 - 900 000 mb and 1000 gb
*/
export const getSizeWithSignificantDigits = (value: number, significantDigits: number) => {
const multiplier = 10 ** significantDigits;

const tbLevel = sizes.tb.value * multiplier;
const gbLevel = sizes.gb.value * multiplier;
const mbLevel = sizes.mb.value * multiplier;
const kbLevel = sizes.kb.value * multiplier;

export const getBytesSizeUnit = (value: number) => {
let size: BytesSizes = 'b';

if (value >= kbLevel) {
if (value >= sizes.kb.value) {
size = 'kb';
}
if (value >= mbLevel) {
if (value >= sizes.mb.value) {
size = 'mb';
}
if (value >= gbLevel) {
if (value >= sizes.gb.value) {
size = 'gb';
}
if (value >= tbLevel) {
if (value >= sizes.tb.value) {
size = 'tb';
}

Expand All @@ -87,15 +63,11 @@ const addSpeedLabel = (result: string, size: BytesSizes) => {
return addSizeLabel(result, size) + i18n('perSecond');
};

/**
* @param significantDigits - number of digits above 3
*/
export const formatBytes = ({
value,
size,
withSpeedLabel = false,
withSizeLabel = true,
significantDigits = 0,
delimiter,
...params
}: FormatValuesArgs<BytesSizes>) => {
Expand All @@ -105,7 +77,7 @@ export const formatBytes = ({

const numValue = Number(value);

const sizeToConvert = size ?? getSizeWithSignificantDigits(numValue, significantDigits);
const sizeToConvert = size ?? getBytesSizeUnit(numValue);

const result = formatToSize({value: numValue, size: sizeToConvert, ...params});

Expand Down
6 changes: 6 additions & 0 deletions src/utils/dataFormatters/__test__/formatNumbers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ describe('formatNumericValues', () => {
const result = formatNumericValues(1024, 2048);
expect(result).toEqual(['1', `2${UNBREAKABLE_GAP}k`]);
});

it('should format values without units (less than 1000)', () => {
const result1 = formatNumericValues(10, 20);
expect(result1).toEqual(['10', `20`]);
Comment on lines +26 to +27
Copy link
Member Author

Choose a reason for hiding this comment

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

Before in such case formatNumericValues returned ['0', '0']

});

it('should format value with label if set', () => {
const result = formatNumericValues(1024, 2048, undefined, undefined, true);
expect(result).toEqual([`1${UNBREAKABLE_GAP}k`, `2${UNBREAKABLE_GAP}k`]);
Expand Down
7 changes: 3 additions & 4 deletions src/utils/dataFormatters/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@ export type FormatValuesArgs<T> = Omit<FormatToSizeArgs<T>, 'value'> & {
value: number | string | undefined | null;
withSpeedLabel?: boolean;
withSizeLabel?: boolean;
significantDigits?: number;
delimiter?: string;
};

export function formatValues<T>(
formatter: (args: FormatValuesArgs<T>) => string,
sizeGetter: (value: number, significantDigits: number) => T,
sizeGetter: (value: number) => T,
value?: number,
total?: number,
size?: T,
delimiter?: string,
withValueLabel = false,
) {
let calculatedSize = sizeGetter(Number(value), 0);
let calculatedSize = sizeGetter(Number(value));
let valueWithSizeLabel = true;
let valuePrecision = 0;

if (isNumeric(total)) {
calculatedSize = sizeGetter(Number(total), 0);
calculatedSize = sizeGetter(Number(total));
valueWithSizeLabel = withValueLabel;
valuePrecision = 1;
}
Expand Down
11 changes: 4 additions & 7 deletions src/utils/dataFormatters/dataFormatters.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import {dateTimeParse, duration} from '@gravity-ui/date-utils';

import type {TVDiskID, TVSlotId} from '../../types/api/vdisk';
import {
formatBytes as formatBytesCustom,
getSizeWithSignificantDigits,
} from '../bytesParsers/formatBytes';
import {formatBytes as formatBytesCustom, getBytesSizeUnit} from '../bytesParsers/formatBytes';
import type {BytesSizes} from '../bytesParsers/formatBytes';
import {HOUR_IN_SECONDS} from '../constants';
import {configuredNumeral} from '../numeral';
import {UNBREAKABLE_GAP, isNumeric} from '../utils';

import {formatValues} from './common';
import {formatNumberWithDigits, getNumberWithSignificantDigits} from './formatNumber';
import {formatNumberWithDigits, getNumberSizeUnit} from './formatNumber';
import type {Digits} from './formatNumber';
import i18n from './i18n';

Expand Down Expand Up @@ -114,7 +111,7 @@ export function formatStorageValues(
) {
return formatValues<BytesSizes>(
formatBytesCustom,
getSizeWithSignificantDigits,
getBytesSizeUnit,
value,
total,
size,
Expand All @@ -132,7 +129,7 @@ export function formatNumericValues(
) {
return formatValues<Digits>(
formatNumberWithDigits,
getNumberWithSignificantDigits,
getNumberSizeUnit,
value,
total,
size,
Expand Down
55 changes: 18 additions & 37 deletions src/utils/dataFormatters/formatNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import type {FormatToSizeArgs, FormatValuesArgs} from './common';
import {formatNumber, roundToPrecision} from './dataFormatters';

const sizes = {
noUnit: {
value: 1,
label: '',
},
thousand: {
value: 1_000,
label: i18n('label_thousand'),
Expand All @@ -25,43 +29,19 @@ const sizes = {

export type Digits = keyof typeof sizes;

/**
* This function is needed to keep more than 3 digits of the same size.
*
* @param significantDigits - number of digits above 3
* @returns size to format value to get required number of digits
*
* By default value converted to the next size when it's above 1000,
* so we have 900k and 1m. To extend it additional significantDigits could be set
*
* significantDigits value added above default 3
*
* significantDigits = 1 - 9 000k and 10m
*
* significantDigits = 2 - 90 000m and 100b
*
* significantDigits = 3 - 900 000b and 1000t
*/
export const getNumberWithSignificantDigits = (value: number, significantDigits: number) => {
const multiplier = 10 ** significantDigits;

const thousandLevel = sizes.thousand.value * multiplier;
const millionLevel = sizes.million.value * multiplier;
const billionLevel = sizes.billion.value * multiplier;
const trillionLevel = sizes.trillion.value * multiplier;

let size: Digits = 'thousand';

if (value > thousandLevel) {
export const getNumberSizeUnit = (value: number) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

May we use http://numeraljs.com/ instead of custom unit getter?

Copy link
Member Author

Choose a reason for hiding this comment

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

We use custom getter, because together with auto formatting we can pass specific unit - it's helpful for data inside tables (data on every row should be with the same unit). AFAIK numeral cannot do formatting to specific units. While formatNumber is not widely used, formatBytes with the same approach is used in most of the tables

let size: Digits = 'noUnit';

if (value >= sizes.thousand.value) {
size = 'thousand';
}
if (value >= millionLevel) {
if (value >= sizes.million.value) {
size = 'million';
}
if (value >= billionLevel) {
if (value >= sizes.billion.value) {
size = 'billion';
}
if (value >= trillionLevel) {
if (value >= sizes.trillion.value) {
size = 'trillion';
}

Expand All @@ -75,17 +55,18 @@ const formatToSize = ({value, size = 'thousand', precision = 0}: FormatToSizeArg
};

const addSizeLabel = (result: string, size: Digits, delimiter = UNBREAKABLE_GAP) => {
return result + delimiter + sizes[size].label;
const label = sizes[size].label;
if (!label) {
return result;
}

return result + delimiter + label;
};

/**
* @param significantDigits - number of digits above 3
*/
export const formatNumberWithDigits = ({
value,
size,
withSizeLabel = true,
significantDigits = 0,
delimiter,
...params
}: FormatValuesArgs<Digits>) => {
Expand All @@ -95,7 +76,7 @@ export const formatNumberWithDigits = ({

const numValue = Number(value);

const sizeToConvert = size ?? getNumberWithSignificantDigits(numValue, significantDigits);
const sizeToConvert = size ?? getNumberSizeUnit(numValue);

const result = formatToSize({value: numValue, size: sizeToConvert, ...params});

Expand Down
Loading