Overview: report liveness and a Source whose poll loop stopped

This commit is contained in:
2026-11-02 01:22:34 +08:00
parent a662188a57
commit b348d69beb
10 changed files with 153 additions and 13 deletions
+37 -1
View File
@@ -11,7 +11,7 @@ import { snapshot, threadSummary, ticketSummary, triggerView } from '../src/app/
import { basisFacts, ticketDetail } from '../src/app/views/tickets.ts'
import { threadDetail } from '../src/app/views/threads.ts'
import { droppedReplies, sourcesStatus } from '../src/app/views/sources.ts'
import { overview } from '../src/app/views/overview.ts'
import { overview, pollAgeMs } from '../src/app/views/overview.ts'
import { markdown, plain } from '../src/app/views/text.ts'
import { policyHistory } from '../src/app/settings-history.ts'
import { KV } from '../src/kv.ts'
@@ -319,3 +319,39 @@ it('reads each ticket a page\'s follow-ups reached once, and nothing for a messa
expect(followupOwners(store.triggers.get('f3')!, records)).toEqual([])
expect([records.tickets.get, records.threads.get].map(read => read.mock.calls.length)).toEqual([0, 0])
})
it('reads the age of a poll round from the newest report a Source carries, and nothing before it reports one', () => {
const now = Date.now()
expect(pollAgeMs([{}, {}])).toBeUndefined()
expect(pollAgeMs([{ at: new Date(now - 300_000).toISOString() }, { at: new Date(now - 60_000).toISOString() }]))
.toBeGreaterThanOrEqual(59_000)
expect(pollAgeMs([{ at: 'not a time' }])).toBeUndefined()
})
it('lists a Source whose poll loop stopped past a few of its own intervals, and never one still starting or off', () => {
const host = views()
const long = new Date(Date.now() - 20 * 60_000).toISOString()
// github polls every 60 seconds by default, so 20 minutes is well past the floor: the loop stopped.
expect(problems(host, { github: { state: 'ready', detail: '', at: long } }).filter(item => item.kind === 'stalled'))
.toEqual([{ kind: 'stalled', source: { id: 'github', title: githubKind.title }, ageMs: expect.any(Number), intervalMs: 60_000 }])
// A Source that never reported a round is starting, not stalled; the grid an operator reads says so separately.
expect(problems(host, { github: { state: 'starting', detail: '' } }).filter(item => item.kind === 'stalled')).toEqual([])
// A fresh round, or one off Source that is switched off, is not stalled either.
const fresh = new Date(Date.now() - 5_000).toISOString()
expect(problems(host, { github: { state: 'ready', detail: '', at: fresh } }).filter(item => item.kind === 'stalled')).toEqual([])
store.saveSettings({ ...store.settings(), sources: { ...store.settings().sources, github: { enabled: false, config: {} } } })
expect(problems(host, { github: { state: 'ready', detail: '', at: long } }).filter(item => item.kind === 'stalled')).toEqual([])
})
it('reports whether the service is alive and polling apart from whether any Trigger arrived', () => {
const host = views()
const startedAt = new Date(Date.now() - 7_200_000).toISOString()
const polled = new Date(Date.now() - 30_000).toISOString()
const view = overview(host, { github: { state: 'ready', detail: '', at: polled } }, 'v1', load, undefined, startedAt)
expect(view.liveness).toMatchObject({ startedAt, lastPollAt: polled, polling: true })
expect(view.liveness.uptimeMs).toBeGreaterThanOrEqual(7_190_000)
// The process can answer for hours with nothing running: no Source enables no polling, whatever the uptime says.
store.saveSettings({ ...store.settings(), sources: { github: { enabled: false, config: {} }, feishu: { enabled: false, config: {} } } })
expect(overview(host, {}, 'v1', load, undefined, startedAt).liveness).toMatchObject({ polling: false, lastPollAt: null, pollAgeMs: null })
expect(overview(host, {}, 'v1', load).liveness).toMatchObject({ startedAt: null, uptimeMs: null, polling: false })
})