Merge pull request #7973 from ouchihao/codex/bug-monitor-auto-detail-refresh

fix(monitor): refresh cached availability on automatic reload
This commit is contained in:
Wesley Liddick
2026-10-12 14:35:24 +08:00
committed by GitHub
2 changed files with 81 additions and 4 deletions
@@ -7,7 +7,7 @@
:loading="loading"
:auto-refresh="autoRefresh"
@update:window="handleWindowChange"
@refresh="manualReload"
@refresh="refresh()"
/>
<MonitorCardGrid
@@ -67,7 +67,7 @@ const autoRefresh = useAutoRefresh({
intervals: [30, 60, 120] as const,
defaultInterval: DEFAULT_INTERVAL_SECONDS,
defaultEnabled: true,
onRefresh: () => reload(true),
onRefresh: () => refresh(true),
shouldPause: () => document.hidden || loading.value,
})
const countdown = autoRefresh.countdown
@@ -109,8 +109,8 @@ async function reload(silent = false) {
}
}
async function manualReload() {
await reload(false)
async function refresh(silent = false) {
await reload(silent)
// After base reload, refresh any cached detail records so non-7d availability
// values stay in sync without forcing the user to switch tabs again.
if (currentWindow.value !== '7d') {
@@ -0,0 +1,77 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, flushPromises, shallowMount } from '@vue/test-utils'
import ChannelStatusV1View from '../ChannelStatusV1View.vue'
const mocks = vi.hoisted(() => ({ list: vi.fn(), status: vi.fn() }))
vi.mock('@/api/channelMonitor', () => mocks)
vi.mock('@/stores/app', () => ({ useAppStore: () => ({ cachedPublicSettings: { channel_monitor_enabled: true }, showError: vi.fn() }) }))
vi.mock('vue-i18n', async () => ({
...await vi.importActual<typeof import('vue-i18n')>('vue-i18n'),
useI18n: () => ({ t: (key: string) => key })
}))
enableAutoUnmount(afterEach)
afterEach(() => { vi.useRealTimers(); localStorage.clear() })
function detail(availability: number) {
return { id: 1, models: [{ model: 'model', availability_15d: availability, availability_30d: availability }] }
}
beforeEach(() => {
vi.useFakeTimers()
vi.resetAllMocks()
localStorage.clear()
localStorage.setItem('channel-status-auto-refresh', JSON.stringify({ enabled: true, interval_seconds: 30 }))
mocks.list.mockImplementation(async () => ({ items: [{ id: 1, name: 'Monitor', primary_model: 'model', primary_status: 'operational', availability_7d: 99 }] }))
mocks.status.mockResolvedValue(detail(90))
})
function mountView() {
return shallowMount(ChannelStatusV1View, {
global: { stubs: {
AppLayout: { template: '<div><slot /></div>' },
MonitorHero: { props: ['loading'], emits: ['update:window', 'refresh'], template: `<div :data-loading="loading">
<button class="fifteen" @click="$emit('update:window', '15d')">15d</button>
<button class="thirty" @click="$emit('update:window', '30d')">30d</button>
<button class="refresh" @click="$emit('refresh')">Refresh</button>
</div>` },
MonitorCardGrid: false,
MonitorCard: { props: ['availabilityValue'], template: '<output>{{ availabilityValue }}</output>' }
} }
})
}
describe('channel monitor automatic detail refresh', () => {
it.each(['fifteen', 'thirty'])('refreshes the cached %s-day availability on timer ticks', async (tab) => {
const wrapper = mountView()
await flushPromises()
await wrapper.get('.' + tab).trigger('click')
await flushPromises()
expect(wrapper.get('output').text()).toBe('90')
mocks.status.mockResolvedValue(detail(98))
await vi.advanceTimersByTimeAsync(30000)
await flushPromises()
expect(wrapper.get('output').text()).toBe('98')
expect(mocks.status).toHaveBeenCalledTimes(2)
expect(wrapper.get('[data-loading]').attributes('data-loading')).toBe('false')
})
it('keeps the seven-day view on the list endpoint without extra detail requests', async () => {
const wrapper = mountView()
await flushPromises()
await vi.advanceTimersByTimeAsync(30000)
await flushPromises()
expect(mocks.list).toHaveBeenCalledTimes(2)
expect(mocks.status).not.toHaveBeenCalled()
expect(wrapper.get('output').text()).toBe('99')
})
it('still refreshes cached availability on manual reload', async () => {
const wrapper = mountView()
await flushPromises()
await wrapper.get('.fifteen').trigger('click')
await flushPromises()
mocks.status.mockResolvedValue(detail(98))
await wrapper.get('.refresh').trigger('click')
await flushPromises()
expect(wrapper.get('output').text()).toBe('98')
})
})