mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
Replace fake relative time strings with ISO 8601 timestamps in API
The API was returning human-readable strings like "2h ago" and "in 4 hours" instead of real timestamps. The frontend now formats these for display using timeAgo() and a new timeUntil() helper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
daf5f5e24d
commit
4976358260
10 changed files with 70 additions and 47 deletions
|
|
@ -12,6 +12,20 @@ export function timeAgo(iso: string): string {
|
|||
return `${days}d ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an ISO 8601 timestamp as a relative future time string (e.g. "in 2h", "in 3d").
|
||||
*/
|
||||
export function timeUntil(iso: string): string {
|
||||
const seconds = Math.floor((new Date(iso).getTime() - Date.now()) / 1000);
|
||||
if (seconds < 60) return "in <1m";
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) return `in ${minutes}m`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `in ${hours}h`;
|
||||
const days = Math.floor(hours / 24);
|
||||
return `in ${days}d`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a human-readable date label for grouping (e.g. "Today", "Yesterday", "Previous 7 days").
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Link, Outlet, useNavigate } from "react-router";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { apiJson } from "../api-client";
|
||||
import { timeAgo } from "../lib/time";
|
||||
import type { PaginatedSavedQueryList, PaginatedHistoryEntryList } from "@qltysh/arc-api-client";
|
||||
import type { Route } from "./+types/insights";
|
||||
|
||||
|
|
@ -108,7 +109,7 @@ export default function InsightsLayout({ loaderData }: Route.ComponentProps) {
|
|||
{entry.sql}
|
||||
</span>
|
||||
<span className="font-mono text-[10px] text-fg-muted">
|
||||
{entry.timestamp} · {entry.rowsReturned} rows
|
||||
{timeAgo(entry.timestamp)} · {entry.rowsReturned} rows
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import type {
|
|||
VerificationStatus,
|
||||
} from "../data/verifications";
|
||||
import { apiJson } from "../api-client";
|
||||
import { timeAgo } from "../lib/time";
|
||||
import type { VerificationDetailResponse } from "@qltysh/arc-api-client";
|
||||
import type { Route } from "./+types/verification-detail";
|
||||
|
||||
|
|
@ -317,7 +318,7 @@ export default function VerificationDetail({ loaderData }: Route.ComponentProps)
|
|||
<ResultIcon result={run.result} />
|
||||
</td>
|
||||
<td className="py-2.5 px-3 text-fg-muted">{run.workflow}</td>
|
||||
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{run.timestamp}</td>
|
||||
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{timeAgo(run.timestamp)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from "@heroicons/react/24/outline";
|
||||
import { Link } from "react-router";
|
||||
import { apiJson } from "../api-client";
|
||||
import { timeAgo, timeUntil } from "../lib/time";
|
||||
import type { PaginatedWorkflowList } from "@qltysh/arc-api-client";
|
||||
import type { Route } from "./+types/workflows";
|
||||
|
||||
|
|
@ -110,10 +111,10 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||
name: w.name,
|
||||
slug: w.slug,
|
||||
filename: w.filename,
|
||||
lastRun: w.last_run?.label ?? "never",
|
||||
lastRun: w.last_run?.ran_at ? timeAgo(w.last_run.ran_at) : "never",
|
||||
color: slugColorMap[w.slug] ?? "var(--color-teal-500)",
|
||||
schedule: w.schedule?.expression,
|
||||
nextRun: w.schedule?.next_run,
|
||||
nextRun: w.schedule?.next_run ? timeUntil(w.schedule.next_run) : undefined,
|
||||
}));
|
||||
return { workflows };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,7 @@ mod usage {
|
|||
}
|
||||
|
||||
mod workflows {
|
||||
use super::ts;
|
||||
use arc_types::*;
|
||||
|
||||
pub fn list_items() -> Vec<WorkflowListItem> {
|
||||
|
|
@ -1112,28 +1113,28 @@ mod workflows {
|
|||
name: "Fix Build".into(),
|
||||
slug: "fix_build".into(),
|
||||
filename: "fix_build.dot".into(),
|
||||
last_run: Some(WorkflowLastRun { label: "2 hours ago".into() }),
|
||||
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-15T12:00:00Z") }),
|
||||
schedule: None,
|
||||
},
|
||||
WorkflowListItem {
|
||||
name: "Implement Feature".into(),
|
||||
slug: "implement".into(),
|
||||
filename: "implement.dot".into(),
|
||||
last_run: Some(WorkflowLastRun { label: "4 days ago".into() }),
|
||||
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-11T10:00:00Z") }),
|
||||
schedule: None,
|
||||
},
|
||||
WorkflowListItem {
|
||||
name: "Sync Drift".into(),
|
||||
slug: "sync_drift".into(),
|
||||
filename: "sync_drift.dot".into(),
|
||||
last_run: Some(WorkflowLastRun { label: "1 day ago".into() }),
|
||||
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-14T14:00:00Z") }),
|
||||
schedule: None,
|
||||
},
|
||||
WorkflowListItem {
|
||||
name: "Expand Product".into(),
|
||||
slug: "expand".into(),
|
||||
filename: "expand.dot".into(),
|
||||
last_run: Some(WorkflowLastRun { label: "2 weeks ago".into() }),
|
||||
last_run: Some(WorkflowLastRun { ran_at: ts("2025-09-01T08:00:00Z") }),
|
||||
schedule: None,
|
||||
},
|
||||
]
|
||||
|
|
@ -1373,6 +1374,7 @@ disk = 10
|
|||
}
|
||||
|
||||
mod verifications {
|
||||
use super::ts;
|
||||
use arc_types::*;
|
||||
|
||||
// ── Category definitions (name, question, controls) ─────────────────
|
||||
|
|
@ -1417,35 +1419,35 @@ mod verifications {
|
|||
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2h ago",
|
||||
timestamp: "2025-09-15T12:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-046",
|
||||
run_title: "PR #311 \u{2014} Update rate limiter config",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "5h ago",
|
||||
timestamp: "2025-09-15T09:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-044",
|
||||
run_title: "PR #309 \u{2014} Migrate to pnpm",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "1d ago",
|
||||
timestamp: "2025-09-14T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-042",
|
||||
run_title: "PR #307 \u{2014} Fix session timeout",
|
||||
workflow: "fix_build",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2d ago",
|
||||
timestamp: "2025-09-13T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-040",
|
||||
run_title: "PR #305 \u{2014} Add webhook retries",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "3d ago",
|
||||
timestamp: "2025-09-12T14:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1455,35 +1457,35 @@ mod verifications {
|
|||
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2h ago",
|
||||
timestamp: "2025-09-15T12:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-046",
|
||||
run_title: "PR #311 \u{2014} Update rate limiter config",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "5h ago",
|
||||
timestamp: "2025-09-15T09:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-044",
|
||||
run_title: "PR #309 \u{2014} Migrate to pnpm",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "1d ago",
|
||||
timestamp: "2025-09-14T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-042",
|
||||
run_title: "PR #307 \u{2014} Fix session timeout",
|
||||
workflow: "fix_build",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2d ago",
|
||||
timestamp: "2025-09-13T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-040",
|
||||
run_title: "PR #305 \u{2014} Add webhook retries",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "3d ago",
|
||||
timestamp: "2025-09-12T14:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1493,35 +1495,35 @@ mod verifications {
|
|||
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2h ago",
|
||||
timestamp: "2025-09-15T12:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-046",
|
||||
run_title: "PR #311 \u{2014} Update rate limiter config",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "5h ago",
|
||||
timestamp: "2025-09-15T09:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-044",
|
||||
run_title: "PR #309 \u{2014} Migrate to pnpm",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "1d ago",
|
||||
timestamp: "2025-09-14T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-042",
|
||||
run_title: "PR #307 \u{2014} Fix session timeout",
|
||||
workflow: "fix_build",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "2d ago",
|
||||
timestamp: "2025-09-13T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-040",
|
||||
run_title: "PR #305 \u{2014} Add webhook retries",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "3d ago",
|
||||
timestamp: "2025-09-12T14:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1531,35 +1533,35 @@ mod verifications {
|
|||
run_title: "PR #312 \u{2014} Add OAuth2 PKCE flow",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "2h ago",
|
||||
timestamp: "2025-09-15T12:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-046",
|
||||
run_title: "PR #311 \u{2014} Update rate limiter config",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "5h ago",
|
||||
timestamp: "2025-09-15T09:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-044",
|
||||
run_title: "PR #309 \u{2014} Migrate to pnpm",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "1d ago",
|
||||
timestamp: "2025-09-14T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-042",
|
||||
run_title: "PR #307 \u{2014} Fix session timeout",
|
||||
workflow: "fix_build",
|
||||
result: VerificationStatus::Fail,
|
||||
timestamp: "2d ago",
|
||||
timestamp: "2025-09-13T14:00:00Z",
|
||||
},
|
||||
RecentResultDef {
|
||||
run_id: "run-040",
|
||||
run_title: "PR #305 \u{2014} Add webhook retries",
|
||||
workflow: "code_review",
|
||||
result: VerificationStatus::Pass,
|
||||
timestamp: "3d ago",
|
||||
timestamp: "2025-09-12T14:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -2027,7 +2029,7 @@ mod verifications {
|
|||
run: RunReference { id: r.run_id.into(), title: r.run_title.into() },
|
||||
workflow: WorkflowReference { slug: r.workflow.into() },
|
||||
result: r.result,
|
||||
timestamp: r.timestamp.into(),
|
||||
timestamp: ts(r.timestamp),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -2420,14 +2422,14 @@ mod insights {
|
|||
HistoryEntry {
|
||||
id: "h1".into(),
|
||||
sql: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1".into(),
|
||||
timestamp: "2 min ago".into(),
|
||||
timestamp: ts("2025-09-15T13:58:00Z"),
|
||||
elapsed: 0.342,
|
||||
row_count: 6,
|
||||
},
|
||||
HistoryEntry {
|
||||
id: "h2".into(),
|
||||
sql: "SELECT * FROM runs WHERE status = 'failed' LIMIT 100".into(),
|
||||
timestamp: "8 min ago".into(),
|
||||
timestamp: ts("2025-09-15T13:52:00Z"),
|
||||
elapsed: 0.127,
|
||||
row_count: 23,
|
||||
},
|
||||
|
|
@ -2435,7 +2437,7 @@ mod insights {
|
|||
id: "h3".into(),
|
||||
sql: "SELECT date_trunc('day', created_at) as d, COUNT(*) FROM runs GROUP BY 1"
|
||||
.into(),
|
||||
timestamp: "15 min ago".into(),
|
||||
timestamp: ts("2025-09-15T13:45:00Z"),
|
||||
elapsed: 0.531,
|
||||
row_count: 30,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1796,19 +1796,21 @@ components:
|
|||
example: "0 */6 * * *"
|
||||
next_run:
|
||||
type: string
|
||||
description: Human-readable relative timestamp of the next run.
|
||||
example: in 4 hours
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp of the next scheduled run.
|
||||
example: "2025-09-15T18:00:00Z"
|
||||
|
||||
WorkflowLastRun:
|
||||
description: Information about a workflow's most recent run.
|
||||
type: object
|
||||
required:
|
||||
- label
|
||||
- ran_at
|
||||
properties:
|
||||
label:
|
||||
ran_at:
|
||||
type: string
|
||||
description: Human-readable relative timestamp.
|
||||
example: 2 hours ago
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp of the most recent run.
|
||||
example: "2025-09-15T12:00:00Z"
|
||||
|
||||
UsageStageRef:
|
||||
description: Reference to a usage stage.
|
||||
|
|
@ -2536,8 +2538,9 @@ components:
|
|||
$ref: "#/components/schemas/VerificationStatus"
|
||||
timestamp:
|
||||
type: string
|
||||
description: Human-readable relative timestamp of the evaluation.
|
||||
example: 2h ago
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp of the evaluation.
|
||||
example: "2025-09-15T12:00:00Z"
|
||||
|
||||
SiblingControl:
|
||||
description: Summary of a sibling verification control in the same category.
|
||||
|
|
@ -2998,8 +3001,9 @@ components:
|
|||
example: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1"
|
||||
timestamp:
|
||||
type: string
|
||||
description: Human-readable relative timestamp of execution.
|
||||
example: 2 min ago
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp of execution.
|
||||
example: "2025-09-15T14:00:00Z"
|
||||
elapsed:
|
||||
type: number
|
||||
description: Query execution time in seconds.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export interface HistoryEntry {
|
|||
*/
|
||||
'sql': string;
|
||||
/**
|
||||
* Human-readable relative timestamp of execution.
|
||||
* ISO 8601 timestamp of execution.
|
||||
*/
|
||||
'timestamp': string;
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export interface RecentControlResult {
|
|||
'workflow': WorkflowReference;
|
||||
'result': VerificationStatus;
|
||||
/**
|
||||
* Human-readable relative timestamp of the evaluation.
|
||||
* ISO 8601 timestamp of the evaluation.
|
||||
*/
|
||||
'timestamp': string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
*/
|
||||
export interface WorkflowLastRun {
|
||||
/**
|
||||
* Human-readable relative timestamp.
|
||||
* ISO 8601 timestamp of the most recent run.
|
||||
*/
|
||||
'label': string;
|
||||
'ran_at': string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export interface WorkflowSchedule {
|
|||
*/
|
||||
'expression': string;
|
||||
/**
|
||||
* Human-readable relative timestamp of the next run.
|
||||
* ISO 8601 timestamp of the next scheduled run.
|
||||
*/
|
||||
'next_run'?: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue