test(admin): закрепить смысл таймингов прогона + время работы в отладке
Тесты на queued_s/duration_s фиксируют ровно ту путаницу, из-за которой метрика и разъехалась: ожидание в очереди и время работы — разные величины, а у прогонов до миграции 006 длительности просто нет (вместо неё раньше показывалось время в очереди). Панель отладки теперь показывает, сколько идущий прогон уже работает — по этому и виден застрявший, а не только по отсутствию heartbeat. README: фактические числа тестов (150, проверено прогоном run_tests.sh). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ interface Run {
|
||||
id: number; source_id: number; status: string; stage: string; target: number;
|
||||
fetched: number; processed: number; added: number; duplicates: number;
|
||||
skipped: number; failed: number; error: string | null; percent: number;
|
||||
started_at: string; heartbeat_at: string | null;
|
||||
started_at: string; run_started_at: string | null; heartbeat_at: string | null;
|
||||
queued_s: number | null; duration_s: number | null;
|
||||
}
|
||||
|
||||
interface DebugData {
|
||||
@@ -42,6 +43,13 @@ function fmtNum(n: number | null | undefined): string {
|
||||
return n == null ? '—' : n.toLocaleString('ru-RU');
|
||||
}
|
||||
|
||||
/** Сколько идущий прогон уже работает — по нему видно застрявший. */
|
||||
function runningFor(run: Run): string {
|
||||
if (!run.run_started_at) return 'ещё не начат';
|
||||
const sec = Math.max(0, (Date.now() - new Date(run.run_started_at + 'Z').getTime()) / 1000);
|
||||
return sec < 90 ? `${Math.round(sec)}с` : `${Math.round(sec / 60)} мин`;
|
||||
}
|
||||
|
||||
export function Debug() {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['admin-debug'],
|
||||
@@ -87,8 +95,9 @@ export function Debug() {
|
||||
<div className="space-y-3">
|
||||
{data.sources.active_runs.map((r) => (
|
||||
<div key={r.id} className="flex items-center gap-4">
|
||||
<div className="w-40 shrink-0 text-sm text-gray-600 truncate">
|
||||
<div className="w-52 shrink-0 text-sm text-gray-600 truncate">
|
||||
#{r.id} · источник {r.source_id}
|
||||
<span className="text-gray-400"> · {runningFor(r)}</span>
|
||||
</div>
|
||||
<ProgressBar
|
||||
percent={r.percent}
|
||||
@@ -189,6 +198,7 @@ export function Debug() {
|
||||
<span className={r.status === 'error' ? 'text-red-600' : 'text-amber-600'}>{r.status}</span>
|
||||
<span className="text-gray-500">
|
||||
получено {r.fetched}/{r.target}, добавлено {r.added}, ошибок {r.failed}
|
||||
{r.duration_s != null && ` · работал ${Math.round(r.duration_s)}с`}
|
||||
</span>
|
||||
{r.error && <span className="w-full text-xs font-mono text-red-600 break-all">{r.error}</span>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user