fix(ops): initialize custom time ranges in local time

This commit is contained in:
chihao.ou
2026-10-09 20:00:02 +08:00
parent 3a6fd1c9db
commit 2bfc49477c
2 changed files with 57 additions and 3 deletions
@@ -10,7 +10,7 @@ import { adminAPI } from '@/api'
import { opsAPI, type OpsDashboardOverview, type OpsMetricThresholds, type OpsRealtimeTrafficSummary } from '@/api/admin/ops'
import type { OpsRequestDetailsPreset } from './OpsRequestDetailsModal.vue'
import { useAdminSettingsStore } from '@/stores'
import { formatNumber } from '@/utils/format'
import { formatDateTimeLocalInput, formatNumber } from '@/utils/format'
import { formatMemorySizeMB } from '../utils/opsFormatters'
type RealtimeWindow = '1min' | '5min' | '30min' | '1h'
@@ -178,8 +178,8 @@ function handleTimeRangeChange(val: string | number | boolean | null) {
// 初始化为最近1小时
const now = new Date()
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000)
customStartTimeInput.value = oneHourAgo.toISOString().slice(0, 16)
customEndTimeInput.value = now.toISOString().slice(0, 16)
customStartTimeInput.value = formatDateTimeLocalInput(oneHourAgo.getTime() / 1000)
customEndTimeInput.value = formatDateTimeLocalInput(now.getTime() / 1000)
showCustomTimeRangeDialog.value = true
} else {
emit('update:timeRange', newValue)
@@ -0,0 +1,54 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import OpsDashboardHeader from '../OpsDashboardHeader.vue'
vi.mock('@/api', () => ({ adminAPI: { groups: { getAll: vi.fn().mockResolvedValue([]) } } }))
vi.mock('@/api/admin/ops', () => ({ opsAPI: { getRealtimeTrafficSummary: vi.fn().mockResolvedValue({ enabled: true, summary: null }) } }))
vi.mock('@/stores', () => ({ useAdminSettingsStore: () => ({ opsRealtimeMonitoringEnabled: true, setOpsRealtimeMonitoringEnabledLocal: vi.fn() }) }))
vi.mock('vue-i18n', async () => ({
...await vi.importActual<typeof import('vue-i18n')>('vue-i18n'),
useI18n: () => ({ t: (key: string) => key })
}))
enableAutoUnmount(afterEach)
beforeEach(() => vi.stubEnv('TZ', 'Asia/Shanghai'))
afterEach(() => { vi.useRealTimers(); vi.unstubAllEnvs() })
async function openCustomRange(now: Date) {
expect(now.getTimezoneOffset()).toBe(-480)
vi.useFakeTimers()
vi.setSystemTime(now)
const wrapper = mount(OpsDashboardHeader, {
props: { platform: '', groupId: null, timeRange: '1h', queryMode: 'auto', loading: false, lastUpdated: null },
global: { stubs: {
Select: true, HelpTooltip: true, Icon: true,
BaseDialog: { props: ['show'], template: '<div v-if="show"><slot /></div>' }
} }
})
await flushPromises()
const selector = wrapper.findAllComponents({ name: 'Select' }).find(select =>
select.props('options').some((option: { value: unknown }) => option.value === 'custom')
)!
selector.vm.$emit('update:modelValue', 'custom')
await flushPromises()
return wrapper
}
describe('ops custom time range timezone', () => {
it.each([
[14, 23, '2026-10-09T13:23', '2026-10-09T14:23'],
[0, 15, '2026-10-08T23:15', '2026-10-09T00:15']
] as const)('prefills the previous local hour at %i:%i', async (hour, minute, start, end) => {
const wrapper = await openCustomRange(new Date(2026, 9, 9, hour, minute))
const inputs = wrapper.findAll<HTMLInputElement>('input[type="datetime-local"]')
expect(inputs.map(input => input.element.value)).toEqual([start, end])
})
it('submits the previous real hour without applying the timezone offset twice', async () => {
const now = new Date(2026, 9, 9, 14, 23)
const wrapper = await openCustomRange(now)
await wrapper.findAll('button').find(button => button.text() === 'common.confirm')!.trigger('click')
expect(wrapper.emitted('update:customTimeRange')).toEqual([
[new Date(now.getTime() - 3600000).toISOString(), now.toISOString()]
])
})
})