Show live repair progress on requests
Magent CI/CD / verify (push) Canceled after 4m53s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-09-01 21:17:50 +12:00
parent 6391fbfd81
commit 3fc52f70c7
5 changed files with 469 additions and 5 deletions
+62
View File
@@ -1992,6 +1992,7 @@ button:disabled,
}
.request-overview,
.request-repair-activity,
.request-journey,
.request-advanced {
border: 1px solid var(--ops-line);
@@ -2005,6 +2006,63 @@ button:disabled,
overflow: hidden;
}
.request-repair-activity {
display: grid;
gap: 16px;
padding: 18px 20px;
border-color: rgba(126, 215, 255, 0.3);
background:
radial-gradient(circle at 6% 0%, rgba(14, 165, 233, 0.13), transparent 34%),
rgba(255, 255, 255, 0.018);
}
.request-repair-activity.is-complete { border-color: rgba(72, 224, 178, 0.34); }
.request-repair-activity.is-attention { border-color: rgba(255, 141, 157, 0.4); }
.request-repair-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 18px;
}
.request-repair-heading h2 { margin: 0; font-size: clamp(1.15rem, 2vw, 1.55rem); }
.request-repair-heading p { max-width: 88ch; margin: 7px 0 0; color: var(--ops-muted); line-height: 1.5; }
.request-repair-meta { display: grid; justify-items: end; gap: 7px; color: var(--ops-muted); }
.request-repair-meta small { white-space: nowrap; font-size: 0.7rem; }
.request-repair-steps {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 9px;
}
.request-repair-step {
position: relative;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: start;
gap: 9px;
min-width: 0;
padding: 12px;
border: 1px solid var(--ops-line-soft);
border-radius: var(--ops-radius);
background: rgba(255, 255, 255, 0.022);
}
.request-repair-step > i {
width: 10px;
height: 10px;
margin-top: 3px;
border: 2px solid currentColor;
border-radius: 50%;
color: var(--ops-muted);
}
.request-repair-step.is-complete > i { color: var(--request-green); background: currentColor; }
.request-repair-step.is-active > i {
color: var(--request-cyan);
box-shadow: 0 0 12px currentColor;
animation: request-operation-pulse 1.15s ease-in-out infinite;
}
.request-repair-step.is-attention > i { color: var(--request-red); background: currentColor; }
.request-repair-step > div { display: grid; gap: 4px; min-width: 0; }
.request-repair-step strong { color: var(--ops-text); font-size: 0.76rem; }
.request-repair-step span { color: var(--ops-muted); font-size: 0.72rem; line-height: 1.4; }
.request-overview-block {
grid-column: span 6;
display: grid;
@@ -2288,6 +2346,7 @@ button:disabled,
@media (max-width: 980px) {
.request-stage { grid-column: span 6; }
.request-repair-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
@@ -2309,6 +2368,9 @@ button:disabled,
.request-action-row button,
.request-release button { width: 100%; }
.request-operation-heading { align-items: flex-start; flex-direction: column; }
.request-repair-heading { flex-direction: column; }
.request-repair-meta { justify-items: start; }
.request-repair-steps { grid-template-columns: 1fr; }
.request-operation-heading-actions { width: 100%; flex-wrap: wrap; }
.request-operation-event { align-items: flex-start; }
}
+58 -2
View File
@@ -36,6 +36,24 @@ type PipelineStage = {
link?: string | null
}
type RepairActivityStep = {
id: string
label: string
state: 'complete' | 'active' | 'attention' | 'waiting' | string
detail: string
}
type RepairActivity = {
visible?: boolean
actionId?: string
state?: 'searching' | 'downloading' | 'importing' | 'indexing' | 'complete' | 'attention' | string
headline?: string
message?: string
service?: string
updatedAt?: string | null
steps?: RepairActivityStep[]
}
type Snapshot = {
request_id: string
title: string
@@ -57,6 +75,7 @@ type Snapshot = {
}
nextStep?: { title?: string; description?: string; actionIds?: string[] }
pipeline?: PipelineStage[]
repairActivity?: RepairActivity
}
raw?: Record<string, any>
}
@@ -288,6 +307,10 @@ export default function RequestTimelinePage() {
(stage) => stage.id === 'available' && stage.state === 'active'
)
)
const repairIsActive = Boolean(
snapshot?.presentation?.repairActivity?.visible &&
!['complete', 'attention'].includes(snapshot.presentation.repairActivity.state ?? '')
)
useEffect(() => {
if (!requestId) return
@@ -367,12 +390,15 @@ export default function RequestTimelinePage() {
if (!stopped) console.error(error)
}
}
const timer = window.setInterval(() => void refresh(), awaitingMediaIndex ? 5_000 : 15_000)
const timer = window.setInterval(
() => void refresh(),
awaitingMediaIndex || repairIsActive ? 5_000 : 15_000,
)
return () => {
stopped = true
window.clearInterval(timer)
}
}, [awaitingMediaIndex, requestId, router])
}, [awaitingMediaIndex, repairIsActive, requestId, router])
const liveDownloadKey = useMemo(() => {
const downloadStage = snapshot?.presentation?.pipeline?.find((stage) => stage.id === 'download')
@@ -470,6 +496,7 @@ export default function RequestTimelinePage() {
const statusLabel = presentation.status?.label ?? fallbackStatusLabel(snapshot.state)
const statusMeaning = presentation.status?.meaning ?? snapshot.state_reason ?? 'Magent is checking this request.'
const download = presentation.download
const repairActivity = presentation.repairActivity
const downloadVisible = Boolean(download?.visible)
const nextStep = presentation.nextStep ?? {
title: snapshot.actions[0]?.label ?? 'No action needed right now',
@@ -753,6 +780,35 @@ export default function RequestTimelinePage() {
)}
</section>
{repairActivity?.visible && (
<section className={`request-repair-activity is-${repairActivity.state ?? 'searching'}`} aria-live="polite">
<div className="request-repair-heading">
<div>
<span className="section-kicker">Repair activity</span>
<h2>{repairActivity.headline ?? 'Repair in progress'}</h2>
<p>{repairActivity.message ?? 'Magent is checking the repair with the connected services.'}</p>
</div>
<div className="request-repair-meta">
<span className={`request-operation-status is-${repairActivity.state === 'attention' ? 'error' : repairActivity.state === 'complete' ? 'complete' : 'running'}`}>
{repairActivity.state === 'complete' ? 'Complete' : repairActivity.state === 'attention' ? 'Attention' : 'Live'}
</span>
{repairActivity.updatedAt && <small>Updated {formatWhen(repairActivity.updatedAt)}</small>}
</div>
</div>
<div className="request-repair-steps">
{(repairActivity.steps ?? []).map((step) => (
<div className={`request-repair-step is-${step.state}`} key={step.id}>
<i aria-hidden="true" />
<div>
<strong>{step.label}</strong>
<span>{step.detail}</span>
</div>
</div>
))}
</div>
</section>
)}
<section className="request-journey" aria-labelledby="request-journey-heading">
<div className="request-journey-heading">
<div>