Simplify Relay Lab dashboard and retry handling

This commit is contained in:
2026-11-07 21:14:51 -07:00
parent c7298662a1
commit 9716886700
3 changed files with 34 additions and 25 deletions
+26 -19
View File
@@ -1,6 +1,7 @@
'use strict'; 'use strict';
const states = ['queued', 'running', 'delivered', 'dead']; const states = ['queued', 'running', 'delivered', 'dead'];
const pollInterval = 750;
const byId = (id) => document.getElementById(id); const byId = (id) => document.getElementById(id);
let lastRequest; let lastRequest;
let sending = false; let sending = false;
@@ -15,16 +16,19 @@ function renderList(id, items, toRow, emptyMessage) {
byId(id).replaceChildren(...(items.length ? items.map(toRow) : [element('li', emptyMessage)])); byId(id).replaceChildren(...(items.length ? items.map(toRow) : [element('li', emptyMessage)]));
} }
function renderPipeline({ queued = 0, running = 0, delivered = 0, dead = 0 }) {
byId('pipeline-queued').textContent = `${queued} waiting`;
byId('pipeline-running').textContent = `${running} active / 2 workers`;
byId('pipeline-outcome').textContent = `${delivered} delivered / ${dead} dead-lettered`;
byId('pipeline').dataset.active = String(running > 0);
}
function render(state) { function render(state) {
const latest = state.events[0]; const latest = state.events[0];
byId('backend-event').textContent = latest byId('backend-event').textContent = latest
? `${latest.job_id.slice(0, 8)} · ${latest.message}` ? `${latest.job_id.slice(0, 8)} · ${latest.message}`
: 'No queue events yet.'; : 'No queue events yet.';
const { queued = 0, running = 0, delivered = 0, dead = 0 } = state.counts; renderPipeline(state.counts);
byId('pipeline-queued').textContent = `${queued} waiting`;
byId('pipeline-running').textContent = `${running} active / 2 workers`;
byId('pipeline-outcome').textContent = `${delivered} delivered / ${dead} dead-lettered`;
byId('pipeline').dataset.active = String(running > 0);
byId('metrics').replaceChildren(...states.map((name) => { byId('metrics').replaceChildren(...states.map((name) => {
const cell = element('div', ''); const cell = element('div', '');
cell.dataset.state = name; cell.dataset.state = name;
@@ -66,45 +70,48 @@ async function poll() {
connection.textContent = online ? 'Queue connected' : 'Queue disconnected'; connection.textContent = online ? 'Queue connected' : 'Queue disconnected';
connection.dataset.online = String(online); connection.dataset.online = String(online);
} }
setTimeout(poll, 750); setTimeout(poll, pollInterval);
} }
} }
async function submit(request, button) { async function submit(request, button) {
if (sending) return; if (sending) return;
sending = true; sending = true;
byId('backend-request').textContent = 'Sending request…'; const inspector = byId('backend-request');
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { inspector.textContent = 'Sending request…';
button.animate( let responseSummary = 'No API response available.';
[{ transform: 'scale(.96)', boxShadow: '0 0 0 0 #bd93f9aa' },
{ transform: 'scale(1)', boxShadow: '0 0 0 8px #bd93f900' }],
{ duration: 320, easing: 'ease-out' },
);
}
try { try {
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
button.animate?.([
{ transform: 'scale(.96)', boxShadow: '0 0 0 0 #bd93f9aa' },
{ transform: 'scale(1)', boxShadow: '0 0 0 8px #bd93f900' },
], { duration: 320, easing: 'ease-out' });
}
const response = await fetch('/api/jobs', { const response = await fetch('/api/jobs', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request), body: JSON.stringify(request),
}); });
responseSummary = `HTTP ${response.status} · Response was not valid JSON.`;
const result = await response.json(); const result = await response.json();
byId('backend-request').textContent = `HTTP ${response.status} · ${JSON.stringify(result)}`; responseSummary = `HTTP ${response.status} · ${JSON.stringify(result)}`;
if (!response.ok) throw new Error(result.error || 'Request failed.'); if (!response.ok) throw new Error(result.error || 'Request failed.');
lastRequest = request; lastRequest = request;
byId('repeat').disabled = false; byId('repeat').disabled = false;
byId('status').textContent = `${result.created ? 'Queued job' : 'Reused job'} ${result.id.slice(0, 8)}.`; byId('status').textContent = `${result.created ? 'Queued job' : 'Reused job'} ${result.id.slice(0, 8)}.`;
} catch (error) { } catch (error) {
if (byId('backend-request').textContent === 'Sending request…') {
byId('backend-request').textContent = 'No API response available.';
}
byId('status').textContent = error.message; byId('status').textContent = error.message;
} finally { } finally {
inspector.textContent = responseSummary;
sending = false; sending = false;
} }
} }
for (const button of document.querySelectorAll('[data-scenario]')) { for (const button of document.querySelectorAll('[data-scenario]')) {
button.addEventListener('click', () => submit({ key: crypto.randomUUID(), scenario: button.dataset.scenario }, button)); button.addEventListener('click', () => {
const request = { key: crypto.randomUUID(), scenario: button.dataset.scenario };
submit(request, button);
});
} }
byId('repeat').addEventListener('click', (event) => submit(lastRequest, event.currentTarget)); byId('repeat').addEventListener('click', (event) => submit(lastRequest, event.currentTarget));
poll(); poll();
+4 -4
View File
@@ -12,10 +12,10 @@
<main> <main>
<section class="hero"> <section class="hero">
<div class="hero-copy"> <div class="hero-copy">
<p class="label">01 / RELIABILITY ENGINEERING</p> <p class="label">01 / RELIABILITY ENGINEERING</p>
<h1>Delivery<br><em>under failure.</em></h1> <h1>Delivery<br><em>under failure.</em></h1>
<p>Run an event through a durable queue. Watch two workers compete for jobs, retry failed deliveries, and recover abandoned work.</p> <p>Run an event through a durable queue. Watch two workers compete for jobs, retry failed deliveries, and recover abandoned work.</p>
<div class="chips"><span>SQLite WAL</span><span>2 workers</span><span>At-least-once attempts</span></div> <div class="chips"><span>SQLite WAL</span><span>2 workers</span><span>At-least-once attempts</span></div>
</div> </div>
<figure class="pipeline" id="pipeline"> <figure class="pipeline" id="pipeline">
<figcaption><span>FIG. 01 / DELIVERY PIPELINE</span><span>LIVE</span></figcaption> <figcaption><span>FIG. 01 / DELIVERY PIPELINE</span><span>LIVE</span></figcaption>
+4 -2
View File
@@ -4,6 +4,8 @@ import sqlite3
from contextlib import contextmanager from contextlib import contextmanager
from uuid import uuid4 from uuid import uuid4
MAX_ATTEMPTS = 3
class Conflict(Exception): class Conflict(Exception):
"""An idempotency key was reused for a different request.""" """An idempotency key was reused for a different request."""
@@ -77,7 +79,7 @@ class QueueStore:
(now,), (now,),
).fetchall() ).fetchall()
for row in expired: for row in expired:
state = "dead" if row["attempts"] >= 3 else "queued" state = "dead" if row["attempts"] >= MAX_ATTEMPTS else "queued"
db.execute(""" db.execute("""
UPDATE jobs SET state = ?, available_at = ?, lease_token = NULL, UPDATE jobs SET state = ?, available_at = ?, lease_token = NULL,
lease_until = NULL WHERE id = ? lease_until = NULL WHERE id = ?
@@ -104,7 +106,7 @@ class QueueStore:
delay = 2 ** attempt delay = 2 ** attempt
if success: if success:
state, message = "delivered", "Consumer acknowledged the event" state, message = "delivered", "Consumer acknowledged the event"
elif attempt >= 3: elif attempt >= MAX_ATTEMPTS:
state, message = "dead", "Consumer failed · attempts exhausted" state, message = "dead", "Consumer failed · attempts exhausted"
else: else:
state, message = "queued", f"Consumer failed · retry in {delay}s" state, message = "queued", f"Consumer failed · retry in {delay}s"