Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

53 linesCodeBlameRaw
1import type { DeliveryMetrics, UsageReport } from "@g1t/contracts";
2
3export function deliveryTotals(rows: DeliveryMetrics[]) {
4 return rows.reduce(
5 (total, row) => ({
6 opened: total.opened + row.opened,
7 merged: total.merged + row.merged,
8 active: total.active + row.active,
9 unreviewed: total.unreviewed + row.unreviewed,
10 queued: total.queued + row.queued,
11 checking: total.checking + row.checking,
12 failed: total.failed + row.failed,
13 leadSeconds: total.leadSeconds + row.leadSeconds,
14 reviewSeconds: total.reviewSeconds + row.reviewSeconds,
15 reviewed: total.reviewed + row.reviewed,
16 }),
17 { opened: 0, merged: 0, active: 0, unreviewed: 0, queued: 0, checking: 0, failed: 0, leadSeconds: 0, reviewSeconds: 0, reviewed: 0 },
18 );
19}
20
21/** How billing names a project's usage: its repository, `owner/name`, as Usage filters it. */
22export function usageKey(repo: { namespace: string; name: string }): string {
23 return `${repo.namespace}/${repo.name}`.toLowerCase();
24}
25
26export function projectCosts(report: UsageReport | null): Map<string, number> {
27 const costs = new Map<string, number>();
28 for (const product of report?.products ?? [])
29 for (const meter of product.meters) {
30 for (const row of meter.byProject) costs.set(row.project, (costs.get(row.project) ?? 0) + row.micros);
31 }
32 return costs;
33}
34
35export function duration(seconds: number | null): string {
36 if (seconds == null || !Number.isFinite(seconds)) return "—";
37 if (seconds < 60) return "<1m";
38 if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
39 if (seconds < 86400) return `${(seconds / 3600).toFixed(1)}h`;
40 return `${(seconds / 86400).toFixed(1)}d`;
41}
42
43/** Merge service batches and fill quiet UTC days so gaps never disappear. */
44export function deliveryDays(from: string, until: string, rows: { day: string; merged: number }[]) {
45 const counts = new Map<string, number>();
46 for (const row of rows) counts.set(row.day, (counts.get(row.day) ?? 0) + row.merged);
47 const days = [];
48 for (let at = Date.parse(`${from}T00:00:00Z`); at <= Date.parse(`${until}T00:00:00Z`); at += 86_400_000) {
49 const day = new Date(at).toISOString().slice(0, 10);
50 days.push({ day, merged: counts.get(day) ?? 0 });
51 }
52 return days;
53}