TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import { Route } from "lucide-react";2import { Badge, StatusBadge } from "@/components/ui/badge";3import { EmptyState } from "@/components/ui/empty-state";4import { formatBytes, formatMs } from "@/lib/format";5import type { RequestAttemptView } from "@/lib/queries/dashboard";6import { HttpCode } from "./requests-table";78function outcomeStatus(outcome: string): string {9 if (outcome === "success") return "success";10 if (outcome === "blocked") return "blocked";11 if (outcome === "timeout") return "pending";12 return "failed";13}1415/**16 * Vertical timeline of routing attempts. `provider` is only present when the organization has17 * provider visibility enabled; it is then labelled as debug output.18 */19export function AttemptsTimeline({ attempts, providerVisibility }: { attempts: RequestAttemptView[]; providerVisibility: boolean }) {20 if (!attempts.length) {21 return <EmptyState compact icon={Route} title="No attempts recorded" description="Attempts appear once the router has tried at least one route for this request." />;22 }23 return (24 <ol className="relative ml-3 border-l border-border">25 {attempts.map((a) => (26 <li key={a.id} className="relative pb-5 pl-6 last:pb-0">27 <span aria-hidden className={`absolute -left-[5px] top-1.5 size-2.5 rounded-full border-2 border-bg-elevated ${a.outcome === "success" ? "bg-success" : a.outcome === "blocked" ? "bg-danger" : "bg-warning"}`} />28 <div className="flex flex-wrap items-center gap-2 text-[13px]">29 <span className="font-mono text-[12px] text-fg-subtle">#{a.attemptNo}</span>30 <StatusBadge status={outcomeStatus(a.outcome)} />31 <span className="text-fg-muted">32 via <span className="text-fg">{a.network === "isp" ? "ISP" : a.network}</span>33 {a.country ? <span className="font-mono"> · {a.country}</span> : null}34 </span>35 {providerVisibility && a.provider ? (36 <Badge variant="outline" className="font-mono">37 <span className="text-fg-subtle">debug</span> {a.provider}38 </Badge>39 ) : null}40 <span className="ml-auto font-mono tabular text-fg-muted">{formatMs(a.durationMs)}</span>41 </div>42 <dl className="mt-1.5 grid grid-cols-2 gap-x-6 gap-y-1 text-[12.5px] sm:grid-cols-4">43 <div>44 <dt className="text-fg-subtle">Outcome</dt>45 <dd className="font-medium">{a.outcome.replace(/_/g, " ")}</dd>46 </div>47 <div>48 <dt className="text-fg-subtle">HTTP</dt>49 <dd>50 <HttpCode code={a.httpStatus} />51 </dd>52 </div>53 <div>54 <dt className="text-fg-subtle">Bytes</dt>55 <dd className="font-mono tabular">{formatBytes(a.bytesIn + a.bytesOut)}</dd>56 </div>57 <div>58 <dt className="text-fg-subtle">Routing score</dt>59 <dd className="font-mono tabular">{a.routingScore === null ? "—" : a.routingScore.toFixed(2)}</dd>60 </div>61 {a.errorCode ? (62 <div className="col-span-2">63 <dt className="text-fg-subtle">Error</dt>64 <dd className="font-mono text-danger">{a.errorCode}</dd>65 </div>66 ) : null}67 {a.blockReason ? (68 <div className="col-span-2">69 <dt className="text-fg-subtle">Block reason</dt>70 <dd>{a.blockReason}</dd>71 </div>72 ) : null}73 </dl>74 </li>75 ))}76 </ol>77 );78}79