Skip to content
Open
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
180 changes: 180 additions & 0 deletions crates/api/examples/dashboard_e2e_seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ fn seed_tree_repository() -> bool {
)
}

fn seed_dependency_graph() -> bool {
matches!(
std::env::var("DASHBOARD_E2E_DEPENDENCY_GRAPH").as_deref(),
Ok("1" | "true" | "yes")
)
}

fn seed_fork_compare_repository() -> bool {
matches!(
std::env::var("DASHBOARD_E2E_FORK_REFS").as_deref(),
Expand Down Expand Up @@ -299,6 +306,9 @@ async fn main() -> anyhow::Result<()> {
.await?;
seed_tree_refs(&pool, user.id, tree_repository.id).await?;
seed_repository_traffic(&pool, tree_repository.id).await?;
if seed_dependency_graph() {
seed_dependency_graph_fixture(&pool, tree_repository.id, &suffix).await?;
}
if seed_blob_edge_files() {
seed_blob_edge_cases(&pool, tree_repository.id).await?;
}
Expand Down Expand Up @@ -2190,6 +2200,176 @@ Describe the change.
Ok(())
}

async fn seed_dependency_graph_fixture(
pool: &PgPool,
repository_id: Uuid,
suffix: &str,
) -> anyhow::Result<()> {
let commit_id = sqlx::query_scalar::<_, Uuid>(
r#"
SELECT target_commit_id
FROM repository_git_refs
WHERE repository_id = $1 AND name = 'refs/heads/main'
"#,
)
.bind(repository_id)
.fetch_one(pool)
.await?;
let short_suffix = &suffix[..12];
for (path, content) in [
(
"package.json",
r#"{"dependencies":{"@playwright/test":"^1.56.0"},"devDependencies":{"vitest":"^4.0.0"}}"#,
),
(
"package-lock.json",
r#"{"packages":{"node_modules/@playwright/test":{"version":"1.56.0"},"node_modules/vitest":{"version":"4.0.0"}}}"#,
),
(
"crates/api/Cargo.toml",
r#"[package]
name = "opengithub-api"
[dependencies]
sqlx = "0.8"
"#,
),
] {
sqlx::query(
r#"
INSERT INTO repository_files (repository_id, commit_id, path, content, oid, byte_size)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (repository_id, commit_id, lower(path))
DO UPDATE SET content = EXCLUDED.content, oid = EXCLUDED.oid, byte_size = EXCLUDED.byte_size
"#,
)
.bind(repository_id)
.bind(commit_id)
.bind(path)
.bind(content)
.bind(format!("dependency-{short_suffix}-{}", path.replace('/', "-")))
.bind(content.len() as i64)
.execute(pool)
.await?;
}

let package_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO dependency_packages (ecosystem, name, package_href)
VALUES ('npm', '@playwright/test', 'https://www.npmjs.com/package/@playwright/test')
ON CONFLICT (ecosystem, lower(name))
DO UPDATE SET package_href = EXCLUDED.package_href, updated_at = now()
RETURNING id
"#,
)
.fetch_one(pool)
.await?;
let manifest_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO dependency_manifests (repository_id, path, ecosystem, lockfile_path, dependency_count)
VALUES ($1, 'package.json', 'npm', 'package-lock.json', 2)
ON CONFLICT (repository_id, lower(path))
DO UPDATE SET ecosystem = EXCLUDED.ecosystem,
lockfile_path = EXCLUDED.lockfile_path,
dependency_count = EXCLUDED.dependency_count,
updated_at = now()
RETURNING id
"#,
)
.bind(repository_id)
.fetch_one(pool)
.await?;
sqlx::query(
r#"
INSERT INTO repository_dependencies (
repository_id, manifest_id, package_id, package_version, relationship, license, lockfile_path
)
VALUES ($1, $2, $3, '1.56.0', 'direct', 'Apache-2.0', 'package-lock.json')
ON CONFLICT (manifest_id, package_id, relationship)
DO UPDATE SET package_version = EXCLUDED.package_version,
license = EXCLUDED.license,
lockfile_path = EXCLUDED.lockfile_path,
updated_at = now()
"#,
)
.bind(repository_id)
.bind(manifest_id)
.bind(package_id)
.execute(pool)
.await?;

let public_owner_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO users (username, email, display_name, avatar_url)
VALUES ($1, $2, $3, NULL)
ON CONFLICT (lower(email)) DO UPDATE SET username = EXCLUDED.username
RETURNING id
"#,
)
.bind(format!("public-consumer-{short_suffix}"))
.bind(format!("public-consumer-{short_suffix}@opengithub.local"))
.bind(format!("Public consumer {short_suffix}"))
.fetch_one(pool)
.await?;
let private_owner_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO users (username, email, display_name, avatar_url)
VALUES ($1, $2, $3, NULL)
ON CONFLICT (lower(email)) DO UPDATE SET username = EXCLUDED.username
RETURNING id
"#,
)
.bind(format!("private-consumer-{short_suffix}"))
.bind(format!("private-consumer-{short_suffix}@opengithub.local"))
.bind(format!("Private consumer {short_suffix}"))
.fetch_one(pool)
.await?;
let public_repo_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO repositories (owner_user_id, name, description, visibility, default_branch, created_by_user_id)
VALUES ($1, $2, 'Uses the opengithub package in production.', 'public', 'main', $1)
ON CONFLICT (owner_user_id, lower(name)) WHERE owner_user_id IS NOT NULL
DO UPDATE SET description = EXCLUDED.description, visibility = 'public'
RETURNING id
"#,
)
.bind(public_owner_id)
.bind(format!("workflow-tools-{short_suffix}"))
.fetch_one(pool)
.await?;
let private_repo_id = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO repositories (owner_user_id, name, description, visibility, default_branch, created_by_user_id)
VALUES ($1, $2, 'Private dependent repository.', 'private', 'main', $1)
ON CONFLICT (owner_user_id, lower(name)) WHERE owner_user_id IS NOT NULL
DO UPDATE SET description = EXCLUDED.description, visibility = 'private'
RETURNING id
"#,
)
.bind(private_owner_id)
.bind(format!("private-workflow-tools-{short_suffix}"))
.fetch_one(pool)
.await?;
for dependent_repo_id in [public_repo_id, private_repo_id] {
sqlx::query(
r#"
INSERT INTO repository_dependents (
source_repository_id, dependent_repository_id, package_id, manifest_path
)
VALUES ($1, $2, $3, 'package.json')
ON CONFLICT (source_repository_id, dependent_repository_id, package_id)
DO UPDATE SET manifest_path = EXCLUDED.manifest_path, detected_at = now()
"#,
)
.bind(repository_id)
.bind(dependent_repo_id)
.bind(package_id)
.execute(pool)
.await?;
}

Ok(())
}

async fn seed_fork_compare_refs(
pool: &PgPool,
user_id: Uuid,
Expand Down
11 changes: 9 additions & 2 deletions crates/api/tests/repository_dependency_graph_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ async fn request_json(
cookie: Option<&str>,
) -> (StatusCode, Value) {
let mut builder = Request::builder().uri(uri);
builder = builder.method(method);
builder = builder.method(method).header(
"x-forwarded-for",
format!("dependency-graph-contract-{}", Uuid::new_v4()),
);
if let Some(cookie) = cookie {
builder = builder.header(header::COOKIE, cookie);
}
Expand Down Expand Up @@ -445,7 +448,11 @@ version = "0.5.2"
.all(|dependency| dependency["package"]["ecosystem"] == "npm"));

let (unauthenticated_status, unauthenticated_body) = get_json(app.clone(), &uri, None).await;
assert_eq!(unauthenticated_status, StatusCode::UNAUTHORIZED);
assert_eq!(
unauthenticated_status,
StatusCode::UNAUTHORIZED,
"{unauthenticated_body}"
);
assert_eq!(unauthenticated_body["error"]["code"], "not_authenticated");

let (outsider_status, outsider_body) =
Expand Down
24 changes: 12 additions & 12 deletions prd.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"priority": "P1",
"ui_details": "Sanitized HTML inside .markdown-body container with Primer-style typography. Headings get auto anchor links (hover paragraph mark). Code blocks have copy button + language label. Tables get sticky header on long tables. Task lists are interactive when viewer has write access (POST toggle). Mermaid renders as SVG. KaTeX for math. Images autoresize <= container width. External links get rel=noopener noreferrer. Markdown editor toolbar (bold/italic/code/link/image/list/task/quote) with live Preview tab.",
"behavior": "Server-side render via pulldown-cmark -> AST -> HTML; sanitize via ammonia (allowlist tags/attrs); rewrite relative image/link paths to repo blob URLs at the current ref; resolve @username and #issue references to live links; cache rendered HTML keyed by (content_sha, repo_id, ref). Editor preview hits the same render endpoint for fidelity.",
"data_model": "rendered_markdown_cache(content_sha, repository_id|null, ref|null, html, rendered_at) β€” TTL-managed; no user data of its own.",
"data_model": "rendered_markdown_cache(content_sha, repository_id|null, ref|null, html, rendered_at) \u2014 TTL-managed; no user data of its own.",
"dependent_on": [
"infra-001"
],
Expand All @@ -76,7 +76,7 @@
"priority": "P1",
"ui_details": "Code surface with line numbers, gutter blame/comment-add button on hover, theme-aware token colors (light + dark), language label dropdown to override detection, Symbols sidebar (functions/classes from Tree-sitter outline), Wrap toggle, Find-in-file (Cmd-F).",
"behavior": "At index time (search-006 worker), generate Tree-sitter token stream and store as compact byte ranges per (sha, path). At render: server returns text + token map; client renders HTML with token classes mapped to CSS variables defined by theme-001. Diff viewer applies same highlighting per line. Tree-sitter grammars compiled into the API binary.",
"data_model": "file_tokens(repository_id, sha, path, language, token_ranges bytea, generated_at) β€” generated once per (sha, path), cached.",
"data_model": "file_tokens(repository_id, sha, path, language, token_ranges bytea, generated_at) \u2014 generated once per (sha, path), cached.",
"dependent_on": [
"theme-001",
"search-006"
Expand Down Expand Up @@ -249,7 +249,7 @@
"page": "/{owner}/{repo}/blob/{branch}/{path}",
"priority": "P2",
"core": true,
"ui_details": "Blob view keeps repository split-pane shell. Header shows branch selector, breadcrumbs, file name, Go to file, latest commit actor/message/SHA/time, History link, metadata text like '352 lines (352 loc) Β· 15.9 KB', Code and Blame toggles, Raw link, copy raw button, download button, symbol pane button, and More file actions. Code viewer uses monospace text, line numbers, syntax highlighting, line-hover anchors, and a read-only hidden textarea for accessibility.",
"ui_details": "Blob view keeps repository split-pane shell. Header shows branch selector, breadcrumbs, file name, Go to file, latest commit actor/message/SHA/time, History link, metadata text like '352 lines (352 loc) \u00b7 15.9 KB', Code and Blame toggles, Raw link, copy raw button, download button, symbol pane button, and More file actions. Code viewer uses monospace text, line numbers, syntax highlighting, line-hover anchors, and a read-only hidden textarea for accessibility.",
"behavior": "Opening a file resolves the selected ref and renders text files with syntax highlighting. Raw streams raw bytes. Copy copies raw content or selected permalink. Download saves the raw file. Blame toggles to line-by-line commit attribution. Large/binary files show a non-renderable state with Raw/Download. Keyboard shortcuts include y for permalink, b for blame, and l for line jump.",
"data_model": "Reads git_objects, git_trees, commits, repository_git_refs, code_search_index, repository_permissions, users, and optional symbol index records. Writes recent_visits and optional code navigation cache records.",
"dependent_on": [
Expand Down Expand Up @@ -1552,7 +1552,7 @@
],
"needs_structure": true,
"build_pass": true,
"qa_pass": false
"qa_pass": true
},
{
"id": "code-security-001",
Expand Down Expand Up @@ -2150,7 +2150,7 @@
{
"id": "search-007",
"category": "feature",
"description": "File finder widget β€” keyboard-driven `t` shortcut on a repo to fuzzy-find a file by path within the current ref.",
"description": "File finder widget \u2014 keyboard-driven `t` shortcut on a repo to fuzzy-find a file by path within the current ref.",
"page": "/<owner>/<repo>/find/<ref>",
"priority": "P3",
"ui_details": "Modal-style page with single text input, instant fuzzy match against full path list, keyboard nav (up/down), Enter to open. Empty input shows full list. Highlight matched chars.",
Expand All @@ -2165,7 +2165,7 @@
{
"id": "security-002",
"category": "feature",
"description": "Active web sessions panel β€” list devices, locations, signed-in time; revoke individual sessions or sign-out-everywhere.",
"description": "Active web sessions panel \u2014 list devices, locations, signed-in time; revoke individual sessions or sign-out-everywhere.",
"page": "/settings/security/sessions",
"priority": "P1",
"ui_details": "Table: device (parsed UA), browser, IP-derived location, last active, Revoke button per row, Sign-out-everywhere button, \"current\" badge on this session.",
Expand All @@ -2180,7 +2180,7 @@
{
"id": "security-003",
"category": "feature",
"description": "Security log β€” append-only audit trail of security-relevant actions (sign-in, sign-out, OAuth link/unlink, email change, sudo confirmations, session revocations) with CSV/JSON export.",
"description": "Security log \u2014 append-only audit trail of security-relevant actions (sign-in, sign-out, OAuth link/unlink, email change, sudo confirmations, session revocations) with CSV/JSON export.",
"page": "/settings/security-log",
"priority": "P2",
"ui_details": "Table: timestamp, action, IP, location, user-agent. Filter by action type. Pagination 50/page. Export dropdown -> CSV / JSON downloads filtered view.",
Expand All @@ -2195,7 +2195,7 @@
{
"id": "globalpulls-001",
"category": "feature",
"description": "Global pull-request dashboard at /pulls β€” every PR the signed-in user opened, was assigned, was requested as reviewer, or that mentions them, across all repositories.",
"description": "Global pull-request dashboard at /pulls \u2014 every PR the signed-in user opened, was assigned, was requested as reviewer, or that mentions them, across all repositories.",
"page": "/pulls",
"priority": "P2",
"ui_details": "Tab bar (Created / Assigned / Mentioned / Review requests). Filter dropdowns (Open/Closed, repo, label, milestone). Sortable list with PR title, repo path, status icon, comment count, last activity, reviewer avatars. Pagination.",
Expand All @@ -2211,7 +2211,7 @@
{
"id": "globalissues-001",
"category": "feature",
"description": "Global issue dashboard at /issues β€” every issue the signed-in user opened, was assigned, or was mentioned in.",
"description": "Global issue dashboard at /issues \u2014 every issue the signed-in user opened, was assigned, or was mentioned in.",
"page": "/issues",
"priority": "P2",
"ui_details": "Tab bar (Created / Assigned / Mentioned). Filter dropdowns (Open/Closed, repo, label, milestone, project). Sortable list with title, repo path, status icon, comment count, last activity. Pagination.",
Expand Down Expand Up @@ -2262,7 +2262,7 @@
"priority": "P2",
"ui_details": "Backend; visible in response headers. /rate_limit endpoint exposes current bucket state.",
"behavior": "Per-token sliding-window limiter (per-IP for anon). Tiers: 5000/hr authenticated, 60/hr anon, 30/min for search. 403 on overage with body { error: { code: \"rate_limited\", message: ... } }. Versioning: server holds latest version; older clients pin via header.",
"data_model": "rate_limit_buckets(token_id|ip, resource, window_start, request_count) β€” Redis-equivalent or Postgres unlogged table.",
"data_model": "rate_limit_buckets(token_id|ip, resource, window_start, request_count) \u2014 Redis-equivalent or Postgres unlogged table.",
"dependent_on": [
"auth-001",
"infra-001"
Expand All @@ -2289,7 +2289,7 @@
{
"id": "gist-001",
"category": "feature",
"description": "Gists β€” single-file or multi-file snippets at /gist/<id> with their own git history, public/secret visibility, fork/star, embeds, and revisions.",
"description": "Gists \u2014 single-file or multi-file snippets at /gist/<id> with their own git history, public/secret visibility, fork/star, embeds, and revisions.",
"page": "/gist (your gists), /gist/new, /gist/<id>, /gist/<id>/edit, /gist/<id>/revisions",
"priority": "P3",
"ui_details": "New-gist form with file tabs (+ add file), per-file language detection, Public vs Secret toggle, description input. View page with embed snippet (<script src=...>), Star/Fork buttons (reuses social-001), comments section. Profile sidebar gets a Public gists widget.",
Expand All @@ -2306,7 +2306,7 @@
{
"id": "ai-001",
"category": "feature",
"description": "AI surface β€” repository auto-summary card, AI-generated PR summary + suggested reviewers, AI changelog from commit history. Uses the same Codex/OpenAI provider backing the build loop.",
"description": "AI surface \u2014 repository auto-summary card, AI-generated PR summary + suggested reviewers, AI changelog from commit history. Uses the same Codex/OpenAI provider backing the build loop.",
"page": "repo header (AI summary card), /<owner>/<repo>/pull/<n> (AI summary tab), /<owner>/<repo>/releases/<tag> (Generate changelog with AI button)",
"priority": "P2",
"ui_details": "Repo summary card under repo header with skeleton loader + Regenerate action, fed by README + top-level files + recent commit messages. PR AI tab: TL;DR, files-of-interest list with one-line risk note each, suggested reviewers (CODEOWNERS + recent committers heuristic), inline-comment seed visible only to PR author. AI changelog modal in releases (Compare from <prev tag>) with grouped Added/Changed/Fixed/Deprecated bullets, Edit before publish.",
Expand Down
Loading