Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@ GITHUB_ISSUES_REPO=njrini99-code/helmv3
# Private Supabase Storage bucket used for screenshot signed links.
HELM_BRIDGE_FEEDBACK_BUCKET=helm-bridge-feedback

# -----------------------------------------------------------------------------
# Helm Bridge Work log (PR timeline tab)
# -----------------------------------------------------------------------------
# Comma-separated GitHub logins to filter PRs (e.g. your username). Omit to show all repo PRs.
GITHUB_PR_AUTHOR_LOGINS=your-github-username
# Max PRs to fetch (default 60, max 100)
# GITHUB_PR_FETCH_LIMIT=60

# -----------------------------------------------------------------------------
# Inngest (Durable workflows — replaces scattered cron + retry loops)
# -----------------------------------------------------------------------------
Expand Down
9 changes: 0 additions & 9 deletions scripts/__tests__/baseball-stale-route-links.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ const SCAN_ROOTS = [
join(REPO_ROOT, 'e2e'),
];
const ALLOWLIST = new Set([
join(REPO_ROOT, 'src/lib/baseball/stats-route-aliases.ts'),
join(
REPO_ROOT,
'src/app/baseball/(dashboard)/dashboard/stats/games/new/page.tsx',
),
join(
REPO_ROOT,
'src/app/baseball/(dashboard)/dashboard/stats/games/new/error.tsx',
),
]);

const STALE_HREF = /\/baseball\/dashboard\/stats\/games\/new(?![\w-])/;
Expand Down
6 changes: 5 additions & 1 deletion src/app/admin/_components/AdminShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import {
LayoutDashboard, Activity, AlertTriangle, KeyRound, Flag, CircleDot,
Users, Timer, Rocket, HeartPulse, ExternalLink, MessageSquarePlus, Gauge, SearchCheck,
Users, Timer, Rocket, HeartPulse, ExternalLink, MessageSquarePlus, Gauge, SearchCheck, ScrollText,
} from 'lucide-react';
import {
AppShell,
Expand All @@ -16,6 +16,7 @@ import {
type CommandGroup,
type CommandItem,
} from '@/components/fairway';
import { SessionActivityProvider } from '@/components/providers/SessionActivityProvider';
import { ADMIN_NAV, hrefForShortcut } from './admin-nav';

/** Sub-route leaf labels the Breadcrumb trail can't derive from ADMIN_NAV
Expand Down Expand Up @@ -58,6 +59,7 @@ const NAV_ICON_BY_HREF = {
'/admin/golf': Flag,
'/admin/baseball': CircleDot,
'/admin/ben-leah': MessageSquarePlus,
'/admin/work': ScrollText,
'/admin/users': Users,
'/admin/jobs': Timer,
'/admin/deploys': Rocket,
Expand Down Expand Up @@ -204,6 +206,7 @@ export function AdminShell({
);

return (
<SessionActivityProvider>
<div className="fairway-ds min-h-screen bg-canvas-gradient">
<AppShell
sections={sections}
Expand Down Expand Up @@ -253,5 +256,6 @@ export function AdminShell({
placeholder="Jump to…"
/>
</div>
</SessionActivityProvider>
);
}
14 changes: 12 additions & 2 deletions src/app/admin/_components/__tests__/admin-nav.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { ADMIN_NAV, hrefForShortcut } from '@/app/admin/_components/admin-nav';
import { ADMIN_NAV, ADMIN_COMMAND_SHORTCUTS, hrefForShortcut } from '@/app/admin/_components/admin-nav';

describe('ADMIN_NAV', () => {
it('declares the canonical tabs in order', () => {
Expand All @@ -11,19 +11,29 @@ describe('ADMIN_NAV', () => {
'/admin/golf',
'/admin/baseball',
'/admin/ben-leah',
'/admin/work',
'/admin/users',
'/admin/jobs',
'/admin/deploys',
'/admin/health',
]);
expect(ADMIN_NAV.map((e) => e.key)).toEqual(['1', '2', '3', '4', '5', '6', 'B', '7', '8', '9', '0']);
expect(ADMIN_NAV.map((e) => e.key)).toEqual(['1', '2', '3', '4', '5', '6', 'B', 'W', '7', '8', '9', '0']);
});
it('maps shortcut keys to hrefs', () => {
expect(hrefForShortcut('2')).toBe('/admin/activity');
expect(hrefForShortcut('3')).toBe('/admin/errors');
expect(hrefForShortcut('B')).toBe('/admin/ben-leah');
expect(hrefForShortcut('W')).toBe('/admin/work');
expect(hrefForShortcut('9')).toBe('/admin/deploys');
expect(hrefForShortcut('0')).toBe('/admin/health');
expect(hrefForShortcut('x')).toBeNull();
});

it('keeps command-center shortcuts on real Bridge tabs', () => {
const navHrefs = new Set(ADMIN_NAV.map((entry) => entry.href));
for (const shortcut of ADMIN_COMMAND_SHORTCUTS) {
expect(navHrefs.has(shortcut.href), `${shortcut.href} is not a registered admin tab`).toBe(true);
}
expect(ADMIN_COMMAND_SHORTCUTS.map((s) => s.href)).not.toContain('/admin/audit');
});
});
10 changes: 10 additions & 0 deletions src/app/admin/_components/admin-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type AdminHref =
| '/admin/golf'
| '/admin/baseball'
| '/admin/ben-leah'
| '/admin/work'
| '/admin/users'
| '/admin/jobs'
| '/admin/deploys'
Expand All @@ -30,12 +31,21 @@ export const ADMIN_NAV: readonly AdminNavEntry[] = [
{ label: 'Golf', href: '/admin/golf', key: '5', section: 'Apps', description: 'GolfHelm production signals' },
{ label: 'Baseball', href: '/admin/baseball', key: '6', section: 'Apps', description: 'BaseballHelm production signals' },
{ label: 'Ben + Leah', href: '/admin/ben-leah', key: 'B', section: 'Operations', description: 'Submit bugs, changes, additions', meta: 'issues' },
{ label: 'Work log', href: '/admin/work', key: 'W', section: 'Operations', description: 'PR timeline — problems, fixes, areas', meta: 'prs' },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{ label: 'Users & Teams', href: '/admin/users', key: '7', section: 'Platform', description: 'Accounts, teams, engagement' },
{ label: 'Jobs & Integrity', href: '/admin/jobs', key: '8', section: 'Platform', description: 'Crons, guards, integrity checks' },
{ label: 'Deploys & Infra', href: '/admin/deploys', key: '9', section: 'Platform', description: 'Vercel releases and web insight' },
{ label: 'Health', href: '/admin/health', key: '0', section: 'Platform', description: 'Feature health across every app', meta: 'map' },
] as const;

/** Quick links in the Overview command header — must be real ADMIN_NAV routes. */
export const ADMIN_COMMAND_SHORTCUTS = [
{ href: '/admin/errors', label: 'Errors' },
{ href: '/admin/health', label: 'Feature Map' },
{ href: '/admin/deploys', label: 'Deploys' },
{ href: '/admin/auth', label: 'Auth' },
] as const satisfies ReadonlyArray<{ href: AdminHref; label: string }>;

export function hrefForShortcut(key: string): string | null {
return ADMIN_NAV.find((e) => e.key === key)?.href ?? null;
}
169 changes: 169 additions & 0 deletions src/app/admin/ben-leah/BenLeahIssueBoard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import Link from 'next/link';
import { GitPullRequest, Rocket } from 'lucide-react';
import {
fetchBenLeahIssueBoard,
type BenLeahTrackStatus,
} from '@/lib/admin/ben-leah-issues';
import { PanelNoData, PanelStale } from '../_components/PanelStates';
import { StatusPill, StatTile, type FwStatusTone } from '@/components/fairway';
import { BenLeahIssueTable } from './BenLeahIssueTable';

const TRACK_META: Record<
BenLeahTrackStatus,
{ label: string; tone: FwStatusTone; description: string }
> = {
open: {
label: 'Open',
tone: 'warning',
description: 'Still open on GitHub — waiting for work.',
},
in_progress: {
label: 'In progress',
tone: 'info',
description: 'Label status:in-progress or status:triaged on the issue.',
},
in_production: {
label: 'In production',
tone: 'success',
description: 'Closed and shipped, or label status:in-production.',
},
fixed_pending_deploy: {
label: 'Fixed · pending deploy',
tone: 'warning',
description: 'Closed on GitHub; production has not deployed since the fix.',
},
fixed: {
label: 'Fixed',
tone: 'success',
description: 'Closed on GitHub (deploy timing unknown).',
},
wont_fix: {
label: "Won't fix",
tone: 'neutral',
description: 'Label status:wontfix on the issue.',
},
};

function formatWhen(iso: string): string {
return new Date(iso).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
}

function githubIntegrationTone(status: string): FwStatusTone {
if (status === 'ok') return 'success';
if (status === 'unconfigured') return 'warning';
return 'danger';
}

export async function BenLeahIssueBoard() {
const board = await fetchBenLeahIssueBoard();

if (board.status === 'unconfigured') {
return (
<PanelNoData
label="GitHub issue tracker not configured"
description="Set GITHUB_ISSUES_TOKEN in production so Helm Bridge can list ben-leah issues."
/>
);
}

if (board.status === 'error' || !board.data) {
return <PanelStale label="Ben + Leah issue tracker" error={board.error} />;
}

const { issues, counts, repoLabel, repoIssuesUrl, productionCommitSha, productionReadyAt, fetchedAt } = {
...board.data,
fetchedAt: board.fetchedAt,
};

const openCount = counts.open + counts.in_progress;
const shippedCount = counts.in_production;
const pendingDeployCount = counts.fixed_pending_deploy;

return (
<div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-wrap gap-2">
<StatusPill tone={githubIntegrationTone(board.status)} dot size="sm">
GitHub {board.status === 'ok' ? 'live' : 'stale'}
</StatusPill>
<StatusPill tone={productionReadyAt ? 'success' : 'neutral'} dot={Boolean(productionReadyAt)} size="sm">
{productionReadyAt
? `prod ${productionCommitSha?.slice(0, 7) ?? 'build'}`
: 'prod timing n/a'}
</StatusPill>
{fetchedAt ? (
<StatusPill tone="neutral" dot={false} size="sm">
checked {new Date(fetchedAt).toLocaleTimeString()}
</StatusPill>
) : null}
</div>
<Link
href={repoIssuesUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1 text-xs font-medium text-accent-700 hover:underline"
>
<GitPullRequest size={14} aria-hidden />
All issues on {repoLabel}
</Link>
</div>

<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{(
[
['Open / in progress', openCount, 'Needs attention'],
['In production', shippedCount, 'Shipped or labeled'],
['Pending deploy', pendingDeployCount, 'Closed, not on prod yet'],
['Total tracked', issues.length, 'Last 50 updates'],
] as const
).map(([label, value, caption]) => (
<div key={label} className="rounded-xl border border-warm-200/70 bg-surface px-3 py-2">
<StatTile label={label} value={value} tone="neutral" mono />
<p className="mt-1 text-xs text-warm-500">{caption}</p>
</div>
))}
</div>

{productionReadyAt ? (
<p className="flex items-center gap-2 text-xs text-warm-500">
<Rocket size={14} className="text-accent-600" aria-hidden />
Production last ready {formatWhen(new Date(productionReadyAt).toISOString())}
{productionCommitSha ? ` · ${productionCommitSha.slice(0, 7)}` : null}
. Closed issues after that time show as <span className="font-medium text-warm-700">In production</span>.
</p>
) : (
<p className="text-xs text-warm-500">
Set VERCEL_API_TOKEN to correlate closed issues with production deploy timing. Without it, status uses GitHub labels only.
</p>
)}

{issues.length === 0 ? (
<PanelNoData
label="No Ben + Leah issues yet"
description="Submissions from this tab create GitHub issues tagged ben-leah. Submit the first one above."
/>
) : (
<BenLeahIssueTable issues={issues} />
)}

<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{(Object.keys(TRACK_META) as BenLeahTrackStatus[]).map((status) => (
<div key={status} className="rounded-fw-md bg-surface-sunken px-3 py-2">
<div className="flex items-center justify-between gap-2">
<StatusPill tone={TRACK_META[status].tone} dot size="sm">
{TRACK_META[status].label}
</StatusPill>
<span className="font-fw-mono text-sm tabular-nums text-warm-900">{counts[status]}</span>
</div>
<p className="mt-1 text-xs text-warm-500">{TRACK_META[status].description}</p>
</div>
))}
</div>
</div>
);
}
Loading
Loading