feat(admin): observability — job history, live "now analyzing", durations & metrics
Crawler + analysis admin dashboards gain a Live / History / Metrics segmented view. History: searchable, filterable, paginated job log per subsystem (crawler_jobs across all states/kinds; page_analysis terminal outcomes), with inline dead-job requeue and a Duration column. Live: enrich analysis SSE events with manga title + chapter number and add a sticky "Now analyzing" banner that jumps to and highlights the page. Metrics: new durable crawl_metrics table (migration 0028) timing every crawl op (manga list walk, manga detail, cover, whole chapter; per-page derived from chapter) plus page_analysis.duration_ms for analysis. New endpoints serve per-type average durations + success rates and a recent-ops log; a cron reaper (CRAWL_METRICS_RETENTION_DAYS) bounds growth. Tested: repo + API integration tests, vitest for the API client and fmtDuration, and Playwright for the History/Metrics tabs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,16 @@ async fn worker_reanalyzes_done_page_when_forced(pool: PgPool) {
|
||||
handle.shutdown().await;
|
||||
|
||||
assert_eq!(dispatcher.call_count(), 1, "force must re-dispatch");
|
||||
|
||||
// The worker stamps how long the dispatch took onto the existing row
|
||||
// (recorded after ack; shutdown has awaited the in-flight job).
|
||||
let dur: Option<i64> =
|
||||
sqlx::query_scalar("SELECT duration_ms FROM page_analysis WHERE page_id = $1")
|
||||
.bind(page_id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(dur.is_some(), "worker should record analysis duration_ms");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
|
||||
Reference in New Issue
Block a user