fix(usage): render resource timestamps in their rollup tz, not browser-local
This commit is contained in:
@@ -32,7 +32,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
bucketKey,
|
bucketKey,
|
||||||
generateBucketRange,
|
generateBucketRange,
|
||||||
Granularity
|
Granularity,
|
||||||
|
parseRollup
|
||||||
} from '../utils/time-buckets';
|
} from '../utils/time-buckets';
|
||||||
import { buildTrendSeries } from '../utils/trend-series';
|
import { buildTrendSeries } from '../utils/trend-series';
|
||||||
import MetricChartCard from './metric-chart-card';
|
import MetricChartCard from './metric-chart-card';
|
||||||
@@ -328,12 +329,14 @@ const GpuInstancesTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
// Last Active is a UTC instant (max bucket hour) → show formatted local time.
|
// Last Active = the last active day. The backend sends a rollup-tz instant
|
||||||
|
// with its offset; parseRollup keeps that wall clock (no browser-tz convert),
|
||||||
|
// consistent with the trend chart buckets. Shown date-only.
|
||||||
const lastActiveCol = {
|
const lastActiveCol = {
|
||||||
title: intl.formatMessage({ id: 'usage.table.lastActive' }),
|
title: intl.formatMessage({ id: 'usage.table.lastActive' }),
|
||||||
dataIndex: 'last_active',
|
dataIndex: 'last_active',
|
||||||
key: 'last_active',
|
key: 'last_active',
|
||||||
render: (v?: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-')
|
render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-')
|
||||||
};
|
};
|
||||||
if (activeTableTab === 'gpu_type') {
|
if (activeTableTab === 'gpu_type') {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
ResourceEventItem,
|
ResourceEventItem,
|
||||||
ResourceEventsResponse
|
ResourceEventsResponse
|
||||||
} from '../apis/resource';
|
} from '../apis/resource';
|
||||||
|
import { parseRollup } from '../utils/time-buckets';
|
||||||
import ResourceFilterBar from './resource-filter-bar';
|
import ResourceFilterBar from './resource-filter-bar';
|
||||||
|
|
||||||
// Only these four are ever emitted (see resource_event_logger): create/delete
|
// Only these four are ever emitted (see resource_event_logger): create/delete
|
||||||
@@ -177,8 +178,10 @@ const ResourceEvents: React.FC = () => {
|
|||||||
title: intl.formatMessage({ id: 'usage.events.col.time' }),
|
title: intl.formatMessage({ id: 'usage.events.col.time' }),
|
||||||
dataIndex: 'occurred_at',
|
dataIndex: 'occurred_at',
|
||||||
key: 'occurred_at',
|
key: 'occurred_at',
|
||||||
|
// Backend sends the rollup-tz instant with its offset; parseRollup keeps
|
||||||
|
// it (no conversion to the browser tz).
|
||||||
render: (v: string) =>
|
render: (v: string) =>
|
||||||
v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-',
|
v ? parseRollup(v).format('YYYY-MM-DD HH:mm:ss') : '-',
|
||||||
width: 200
|
width: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ import useResourceMeta from '../hooks/use-resource-meta';
|
|||||||
import {
|
import {
|
||||||
bucketKey,
|
bucketKey,
|
||||||
generateBucketRange,
|
generateBucketRange,
|
||||||
Granularity
|
Granularity,
|
||||||
|
parseRollup
|
||||||
} from '../utils/time-buckets';
|
} from '../utils/time-buckets';
|
||||||
import { buildTrendSeries } from '../utils/trend-series';
|
import { buildTrendSeries } from '../utils/trend-series';
|
||||||
import MetricChartCard from './metric-chart-card';
|
import MetricChartCard from './metric-chart-card';
|
||||||
@@ -293,12 +294,14 @@ const StorageTab: React.FC = () => {
|
|||||||
render: (v: number) => (v ?? 0).toFixed(2)
|
render: (v: number) => (v ?? 0).toFixed(2)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
// Last Active is a UTC instant (max bucket hour) → show formatted local time.
|
// Last Active = the last active day. The backend sends a rollup-tz instant
|
||||||
|
// with its offset; parseRollup keeps that wall clock (no browser-tz convert),
|
||||||
|
// consistent with the trend chart buckets. Shown date-only.
|
||||||
const lastActiveCol = {
|
const lastActiveCol = {
|
||||||
title: intl.formatMessage({ id: 'usage.table.lastActive' }),
|
title: intl.formatMessage({ id: 'usage.table.lastActive' }),
|
||||||
dataIndex: 'last_active',
|
dataIndex: 'last_active',
|
||||||
key: 'last_active',
|
key: 'last_active',
|
||||||
render: (v?: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-')
|
render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-')
|
||||||
};
|
};
|
||||||
if (activeTableTab === 'volume') {
|
if (activeTableTab === 'volume') {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -13,8 +13,24 @@ export type Granularity = 'hour' | 'day' | 'week' | 'month';
|
|||||||
// Cap the hourly axis so a wide date range doesn't render hundreds of bars.
|
// Cap the hourly axis so a wide date range doesn't render hundreds of bars.
|
||||||
const HOUR_MAX_DAYS = 7;
|
const HOUR_MAX_DAYS = 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a usage timestamp keeping the offset the backend embedded (the rollup
|
||||||
|
* tz, e.g. ``+08:00``) — i.e. render that wall clock verbatim, NOT converted to
|
||||||
|
* the browser's timezone. The typed equivalent of ``dayjs.parseZone``:
|
||||||
|
* ``.utcOffset(offset)`` re-displays the same instant at the embedded offset
|
||||||
|
* (``Z`` → UTC). A dayjs object (an axis cursor) or an offset-less date string
|
||||||
|
* passes through.
|
||||||
|
*/
|
||||||
|
export const parseRollup = (value: any): dayjs.Dayjs => {
|
||||||
|
if (dayjs.isDayjs(value)) return value;
|
||||||
|
const m = typeof value === 'string' && value.match(/([+-]\d{2}:?\d{2}|Z)$/i);
|
||||||
|
return m
|
||||||
|
? dayjs(value).utcOffset(m[1].toUpperCase() === 'Z' ? 0 : m[1])
|
||||||
|
: dayjs(value);
|
||||||
|
};
|
||||||
|
|
||||||
export const bucketKey = (value: any, granularity: Granularity): string => {
|
export const bucketKey = (value: any, granularity: Granularity): string => {
|
||||||
const d = dayjs(value);
|
const d = parseRollup(value);
|
||||||
if (granularity === 'hour') return d.format('YYYY-MM-DD HH:00');
|
if (granularity === 'hour') return d.format('YYYY-MM-DD HH:00');
|
||||||
if (granularity === 'month') return d.format('YYYY-MM');
|
if (granularity === 'month') return d.format('YYYY-MM');
|
||||||
return d.format('YYYY-MM-DD'); // day / week (week-start date as returned)
|
return d.format('YYYY-MM-DD'); // day / week (week-start date as returned)
|
||||||
|
|||||||
Reference in New Issue
Block a user