diff --git a/src/utils/dataFormatters/__test__/formatUptime.test.ts b/src/utils/dataFormatters/__test__/formatUptime.test.ts index 50b2dd1d4c..5f74ce3c3c 100644 --- a/src/utils/dataFormatters/__test__/formatUptime.test.ts +++ b/src/utils/dataFormatters/__test__/formatUptime.test.ts @@ -1,11 +1,17 @@ -import {getDowntimeFromDateFormatted, getUptimeFromDateFormatted} from '../dataFormatters'; +import {EMPTY_DATA_PLACEHOLDER} from '../../constants'; +import {UNBREAKABLE_GAP} from '../../utils'; +import { + formatUptimeInSeconds, + getDowntimeFromDateFormatted, + getUptimeFromDateFormatted, +} from '../dataFormatters'; describe('getUptimeFromDateFormatted', () => { it('should calculate and format uptime', () => { expect(getUptimeFromDateFormatted(3_600_000, 7_200_000)).toBe('1:00:00'); }); it('should return 0 if dateFrom after dateTo', () => { - expect(getUptimeFromDateFormatted(3_600_000, 3_599_000)).toBe('0:00:00'); + expect(getUptimeFromDateFormatted(3_600_000, 3_599_000)).toBe('0s'); }); }); describe('getDowntimeFromDateFormatted', () => { @@ -13,9 +19,49 @@ describe('getDowntimeFromDateFormatted', () => { expect(getDowntimeFromDateFormatted(3_600_000, 7_200_000)).toBe('-1:00:00'); }); it('should not add sign if downtime is 0', () => { - expect(getDowntimeFromDateFormatted(3_600_000, 3_600_000)).toBe('0:00:00'); + expect(getDowntimeFromDateFormatted(3_600_000, 3_600_000)).toBe('0s'); }); it('should return 0 if dateFrom after dateTo', () => { - expect(getDowntimeFromDateFormatted(3_600_000, 3_599_000)).toBe('0:00:00'); + expect(getDowntimeFromDateFormatted(3_600_000, 3_599_000)).toBe('0s'); + }); +}); +describe('formatUptimeInSeconds', () => { + const M = 60; + const H = 60 * M; + const D = 24 * H; + + it('should return days if value is more than 24h', () => { + expect(formatUptimeInSeconds(D)).toBe('1d' + UNBREAKABLE_GAP + '00:00:00'); + expect(formatUptimeInSeconds(D + H + M + 12)).toBe('1d' + UNBREAKABLE_GAP + '01:01:12'); + expect(formatUptimeInSeconds(12 * D + 12 * H + 12 * M + 12)).toBe( + '12d' + UNBREAKABLE_GAP + '12:12:12', + ); + expect(formatUptimeInSeconds(1234 * D + 12 * H + 12 * M + 12)).toBe( + '1234d' + UNBREAKABLE_GAP + '12:12:12', + ); + }); + it('should return hours if value is less than 24h', () => { + expect(formatUptimeInSeconds(H + M + 12)).toBe('1:01:12'); + expect(formatUptimeInSeconds(12 * H + 12 * M + 12)).toBe('12:12:12'); + }); + it('should return minutes if value is less than hour', () => { + expect(formatUptimeInSeconds(M + 12)).toBe('1:12'); + expect(formatUptimeInSeconds(12 * M + 2)).toBe('12:02'); + }); + it('should return second if value is less than hour', () => { + expect(formatUptimeInSeconds(12)).toBe('12s'); + expect(formatUptimeInSeconds(2)).toBe('2s'); + }); + it('should correctly process negative values', () => { + expect(formatUptimeInSeconds(-0)).toBe('0s'); + expect(formatUptimeInSeconds(-12)).toBe('-12s'); + expect(formatUptimeInSeconds(-1 * (12 * M + 2))).toBe('-12:02'); + expect(formatUptimeInSeconds(-1 * (12 * H + 12 * M + 12))).toBe('-12:12:12'); + expect(formatUptimeInSeconds(-1 * (12 * D + 12 * H + 12 * M + 12))).toBe( + '-12d' + UNBREAKABLE_GAP + '12:12:12', + ); + }); + it('should return empty placeholder on NaN', () => { + expect(formatUptimeInSeconds(Number.NaN)).toBe(EMPTY_DATA_PLACEHOLDER); }); }); diff --git a/src/utils/dataFormatters/dataFormatters.ts b/src/utils/dataFormatters/dataFormatters.ts index 326d13e688..8186526bf4 100644 --- a/src/utils/dataFormatters/dataFormatters.ts +++ b/src/utils/dataFormatters/dataFormatters.ts @@ -1,4 +1,4 @@ -import {dateTimeParse} from '@gravity-ui/date-utils'; +import {dateTimeParse, duration} from '@gravity-ui/date-utils'; import type {TVDiskID, TVSlotId} from '../../types/api/vdisk'; import { @@ -6,9 +6,9 @@ import { getSizeWithSignificantDigits, } from '../bytesParsers/formatBytes'; import type {BytesSizes} from '../bytesParsers/formatBytes'; -import {DAY_IN_SECONDS, HOUR_IN_SECONDS} from '../constants'; +import {EMPTY_DATA_PLACEHOLDER, HOUR_IN_SECONDS} from '../constants'; import {configuredNumeral} from '../numeral'; -import {isNumeric} from '../utils'; +import {UNBREAKABLE_GAP, isNumeric} from '../utils'; import {formatValues} from './common'; import {formatNumberWithDigits, getNumberWithSignificantDigits} from './formatNumber'; @@ -40,19 +40,38 @@ export const stringifyVdiskId = (id?: TVDiskID | TVSlotId) => { return id ? Object.values(id).join('-') : ''; }; -export const formatUptimeInSeconds = (seconds: number) => { - const days = Math.floor(seconds / DAY_IN_SECONDS); - const remain = seconds % DAY_IN_SECONDS; +/** + * It works well only with positive values, + * if you want to get negative formatted uptime, use some wrapper like getDowntimeFromDateFormatted + */ +export function formatUptimeInSeconds(seconds: number) { + if (!isNumeric(seconds)) { + return EMPTY_DATA_PLACEHOLDER; + } - const uptime = [days && `${days}d`, configuredNumeral(remain).format('00:00:00')] - .filter(Boolean) - .join(' '); + // duration.format() doesn't work well with negative values + // negative value will be displayed like -2d -12:-58:-21 + // so we process positive duration and only then add sign if any + const sign = seconds < 0 ? '-' : ''; + const d = duration(Math.abs(seconds), 's').rescale(); - return uptime; -}; + let value: string; + + if (d.days() > 0) { + value = d.format(`d[${i18n('d')}${UNBREAKABLE_GAP}]hh:mm:ss`); + } else if (d.hours() > 0) { + value = d.format('h:mm:ss'); + } else if (d.minutes() > 0) { + value = d.format('m:ss'); + } else { + value = d.format(`s[${i18n('s')}]`); + } + + return sign + value; +} export const formatMsToUptime = (ms?: number) => { - return ms && formatUptimeInSeconds(ms / 1000); + return formatUptimeInSeconds(Number(ms) / 1000); }; export function getUptimeFromDateFormatted(dateFrom?: number | string, dateTo?: number | string) { @@ -72,10 +91,7 @@ export function getDowntimeFromDateFormatted(dateFrom?: number | string, dateTo? // Prevent wrong negative uptime values diff = diff < 0 ? 0 : diff; - const formattedUptime = formatUptimeInSeconds(diff); - - // Do not add sign to 0 values to prevent -0:00:00 uptime - return diff === 0 ? formattedUptime : '-' + formattedUptime; + return formatUptimeInSeconds(-diff); } export function calcTimeDiffInSec( diff --git a/src/utils/dataFormatters/i18n/en.json b/src/utils/dataFormatters/i18n/en.json index fffa73d78e..2eda1adbee 100644 --- a/src/utils/dataFormatters/i18n/en.json +++ b/src/utils/dataFormatters/i18n/en.json @@ -1,3 +1,6 @@ { - "format-cpu.cores": ["core", "cores", "cores", "cores"] + "format-cpu.cores": ["core", "cores", "cores", "cores"], + + "d": "d", + "s": "s" } diff --git a/src/utils/dataFormatters/i18n/index.ts b/src/utils/dataFormatters/i18n/index.ts index 3a60fdb981..9dfb1e7d97 100644 --- a/src/utils/dataFormatters/i18n/index.ts +++ b/src/utils/dataFormatters/i18n/index.ts @@ -1,8 +1,7 @@ import {registerKeysets} from '../../i18n'; import en from './en.json'; -import ru from './ru.json'; const COMPONENT = 'ydb-format-cpu'; -export default registerKeysets(COMPONENT, {ru, en}); +export default registerKeysets(COMPONENT, {en}); diff --git a/src/utils/dataFormatters/i18n/ru.json b/src/utils/dataFormatters/i18n/ru.json deleted file mode 100644 index d0e8aa33ad..0000000000 --- a/src/utils/dataFormatters/i18n/ru.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "format-cpu.cores": ["ядро", "ядра", "ядер", "ядер"] -} diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts index 25d0eb5560..631313543f 100644 --- a/src/utils/nodes.ts +++ b/src/utils/nodes.ts @@ -74,7 +74,7 @@ export function prepareNodeSystemState( const DC = systemState.Location?.DataCenter || systemState.DataCenter; const TenantName = systemState?.Tenants?.[0]; - let Uptime: string; + let Uptime: PreparedNodeSystemState['Uptime']; if (systemState.DisconnectTime) { Uptime = getDowntimeFromDateFormatted(systemState.DisconnectTime); diff --git a/tests/suites/nodes/nodes.test.ts b/tests/suites/nodes/nodes.test.ts index 58086ed489..d1c66daa1d 100644 --- a/tests/suites/nodes/nodes.test.ts +++ b/tests/suites/nodes/nodes.test.ts @@ -94,7 +94,13 @@ test.describe('Test Nodes Paginated Table', async () => { const uptimeValues = await paginatedTable.getColumnValues('Uptime'); for (const uptime of uptimeValues) { - expect(uptime).toMatch(/^(\d+d\s)?(\d+):(\d{2}):(\d{2})$/); // Format: DDd? HH:MM:SS + // \d+d\xa0\d{2}:\d{2}:\d{2} - DDd HH:MM:SS, 1d 00:20:30 + // \d{1,2}:\d{2}:\d{2}$ - HH:MM:SS, 1:02:02 or 12:02:02 + // \d{1,2}:\d{2}$ - MM:SS, 1:02 or 12:02 + // \d{1,2}s$ - SSs, 1s or 12s + expect(uptime).toMatch( + /^(\d+d\xa0\d{2}:\d{2}:\d{2}|\d{1,2}:\d{2}:\d{2}|\d{1,2}:\d{2}|\d{1,2}s)$/, + ); } });