Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 0 additions & 42 deletions platform/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* Tokens come from the user's NextAuth session.
*/

import { normalizeRepoSlug } from "./repo-slug";

const API = "https://api.github.com";

export interface GitHubOrgSummary {
Expand Down Expand Up @@ -180,43 +178,3 @@ export async function listUserOrgs(

return detailed;
}

interface RawRepoDetail {
archived: boolean;
}

/**
* Checks GitHub's `archived` flag for each given remote URL, using the
* caller's own OAuth token. Ephemeral: nothing here is persisted, and
* nothing calls this automatically — it's meant to run on-demand for
* whatever repos are currently on screen, not as a background sync.
*
* The login scope has no `repo` grant, so a private repo 404s; that comes
* back as `null` (unknown) for that entry rather than failing the batch —
* archived status for private repos just can't be determined this way.
*/
export async function checkArchivedStatus(
remoteUrls: (string | null | undefined)[],
accessToken: string,
): Promise<Record<string, boolean | null>> {
const pathBySlug = new Map<string, string>();
for (const url of remoteUrls) {
const slug = normalizeRepoSlug(url ?? null);
if (!slug || !slug.startsWith("github.com/")) continue;
const path = slug.slice("github.com/".length);
if (path.split("/").length === 2) pathBySlug.set(slug, path);
}

const entries = await Promise.all(
Array.from(pathBySlug.entries()).map(async ([slug, path]) => {
try {
const detail = await call<RawRepoDetail>(`/repos/${path}`, accessToken);
return [slug, detail.archived] as const;
} catch {
return [slug, null] as const;
}
}),
);

return Object.fromEntries(entries);
}
25 changes: 22 additions & 3 deletions platform/lib/integrations/datadog/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ import {

import { logger } from "@/lib/debug";
import { decryptCredentials } from "@/lib/encryption";
import { normalizeRepoSlug } from "@/lib/repo-slug";

export { normalizeRepoSlug };

const DEFAULT_BACKFILL_DAYS = 30;
const PAGE_LIMIT = 100;
Expand Down Expand Up @@ -246,6 +243,28 @@ async function loadRepoLookup(
return { byNormalizedSlug };
}

/**
* Normalize a git remote URL or Datadog slug into "host/path" form
* (lowercased, no scheme, no `.git`, no trailing slash). Returns `null`
* for empty input.
*/
export function normalizeRepoSlug(
input: string | null | undefined,
): string | null {
if (!input) return null;
let s = input.trim().toLowerCase();
if (!s) return null;
// git@github.com:org/repo.git → github.com/org/repo
s = s.replace(/^git@([^:]+):/, "$1/");
// ssh://git@host/org/repo or https://host/org/repo → host/org/repo
s = s.replace(/^[a-z]+:\/\//, "");
s = s.replace(/^git@/, "");
s = s.replace(/^www\./, "");
s = s.replace(/\.git$/, "");
s = s.replace(/\/+$/, "");
return s || null;
}

/**
* Return the first occurrence of each item keyed by `idFn`.
*
Expand Down
24 changes: 0 additions & 24 deletions platform/lib/repo-slug.ts

This file was deleted.

11 changes: 0 additions & 11 deletions platform/lib/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,6 @@ export const translations = {
ai: "AI",
sortByAi: "Most AI first",
staleOnly: "Stale (90d+)",
checkArchived: "Check archived",
checkingArchived: "Checking...",
hideArchived: "Hide archived",
archivedTag: "archived",
noGithubLink: "Sign in with GitHub to check archived status",
},
},
investHere: {
Expand Down Expand Up @@ -1881,12 +1876,6 @@ export const translations = {
ai: "IA",
sortByAi: "Mais IA primeiro",
staleOnly: "Parados (90d+)",
checkArchived: "Checar arquivados",
checkingArchived: "Checando...",
hideArchived: "Ocultar arquivados",
archivedTag: "arquivado",
noGithubLink:
"Faça login com GitHub para checar status de arquivamento",
},
},
investHere: {
Expand Down
90 changes: 2 additions & 88 deletions platform/src/app/[tenant]/dashboard/repo-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,11 @@ import { useState } from "react";

import Link from "next/link";

import {
Archive,
ArrowDownWideNarrow,
Clock,
Search,
Trash2,
} from "lucide-react";
import { useSession } from "next-auth/react";
import { ArrowDownWideNarrow, Clock, Search, Trash2 } from "lucide-react";

import { DeleteRepositoryDialog } from "@/components/repos/DeleteRepositoryDialog";
import { Button } from "@/components/ui/button";
import { useTranslation } from "@/hooks/useTranslation";
import { normalizeRepoSlug } from "@/lib/repo-slug";
import { cn } from "@/lib/utils";
import type { RepoSummary } from "@/types/temporal";
import { healthIndicator } from "@/types/temporal";
Expand Down Expand Up @@ -50,55 +42,14 @@ export function RepoList({
nowMs,
}: RepoListProps) {
const { t } = useTranslation();
const { data: session } = useSession();
const [query, setQuery] = useState("");
const [sortByAi, setSortByAi] = useState(false);
const [staleOnly, setStaleOnly] = useState(false);
const [hideArchived, setHideArchived] = useState(false);
const [checkingArchived, setCheckingArchived] = useState(false);
const [hasCheckedArchived, setHasCheckedArchived] = useState(false);
// Ephemeral, not persisted: repo-slug -> archived (null = unknown, e.g.
// private repo the session token can't read). Empty until "Check
// archived" runs, and re-fetched fresh every time — never cached across
// page loads.
const [archivedBySlug, setArchivedBySlug] = useState<
Record<string, boolean | null>
>({});
const showDeleteColumn = canDelete && !!organizationId;
// nowMs comes from the server component (page.tsx) rather than a
// Date.now() call here — calling it directly during a client render
// isn't pure.
const staleCutoff = nowMs - STALE_MS;
const hasGithubLink = !!(
session?.user as { githubAccessToken?: string } | undefined
)?.githubAccessToken;

async function handleCheckArchived() {
setCheckingArchived(true);
try {
const res = await fetch("/api/repos/check-archived", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ remoteUrls: repos.map((r) => r.remote_url) }),
});
if (res.ok) {
const data = (await res.json()) as {
archived: Record<string, boolean | null>;
};
setArchivedBySlug(data.archived);
setHasCheckedArchived(true);
}
} catch {
// Non-fatal — the button just stays available to retry.
} finally {
setCheckingArchived(false);
}
}

function isArchived(repo: RepoSummary): boolean | null {
const slug = normalizeRepoSlug(repo.remote_url);
return slug ? (archivedBySlug[slug] ?? null) : null;
}

if (repos.length === 0) {
return (
Expand All @@ -123,10 +74,6 @@ export function RepoList({
);
}

if (hideArchived) {
filtered = filtered.filter((r) => isArchived(r) !== true);
}

// Repos without AI data sort to the end regardless of direction.
const sorted = sortByAi
? [...filtered].sort(
Expand Down Expand Up @@ -170,40 +117,12 @@ export function RepoList({
<Clock className="size-4" />
{t("dashboard.repoList.staleOnly")}
</Button>
<Button
type="button"
variant="outline"
size="sm"
disabled={!hasGithubLink || checkingArchived}
title={
hasGithubLink ? undefined : t("dashboard.repoList.noGithubLink")
}
onClick={() => void handleCheckArchived()}
className="flex-shrink-0"
>
<Archive className="size-4" />
{checkingArchived
? t("dashboard.repoList.checkingArchived")
: t("dashboard.repoList.checkArchived")}
</Button>
{hasCheckedArchived && (
<Button
type="button"
variant={hideArchived ? "default" : "outline"}
size="sm"
onClick={() => setHideArchived((v) => !v)}
className="flex-shrink-0"
>
{t("dashboard.repoList.hideArchived")}
</Button>
)}
</div>
)}
{sorted.map((repo) => {
const color = healthIndicator(repo.health);
const aiPct = repo.ai_detection_coverage_pct;
const humanPct = aiPct != null ? 100 - aiPct : null;
const archived = isArchived(repo);

return (
<div
Expand All @@ -227,13 +146,8 @@ export function RepoList({
)}
/>
<div className="min-w-0">
<p className="flex items-center gap-1.5 truncate font-mono text-sm font-medium">
<p className="truncate font-mono text-sm font-medium">
{repo.name}
{archived === true && (
<span className="flex-shrink-0 rounded-full bg-muted px-1.5 py-0.5 font-sans text-[10px] font-normal text-muted-foreground">
{t("dashboard.repoList.archivedTag")}
</span>
)}
</p>
<p className="truncate text-xs text-muted-foreground">
{repo.runs_count} runs
Expand Down
38 changes: 0 additions & 38 deletions platform/src/app/api/repos/check-archived/route.ts

This file was deleted.

Loading