diff --git a/agent/tools/publish_review.py b/agent/tools/publish_review.py index 78682317..19f330ab 100644 --- a/agent/tools/publish_review.py +++ b/agent/tools/publish_review.py @@ -43,7 +43,7 @@ from ..reviewer_publish import ( settle_review_check_run, ) from ..reviewer_reconcile import reconcile_findings_with_review_threads -from ..utils.dashboard_links import dashboard_thread_url +from ..utils.dashboard_links import dashboard_review_url from ..utils.github_checks import review_check_conclusion from ..utils.github_token import ( GitHubAuthError, @@ -238,7 +238,7 @@ async def _publish_review_async( # reviewed, not the stale one this run was created for. head_sha = await resolve_review_head_sha(thread_id, {"head_sha": head_sha}) review_trace_url = await _resolve_review_trace_url(thread_id, trace_link_config_override) - review_ui_url = dashboard_thread_url(thread_id) + review_ui_url = dashboard_review_url(owner, repo, pr_number) findings = await _backfill_findings_from_pr_threads( thread_id=thread_id, owner=owner, diff --git a/agent/utils/dashboard_links.py b/agent/utils/dashboard_links.py index 5639c5ae..a343a4db 100644 --- a/agent/utils/dashboard_links.py +++ b/agent/utils/dashboard_links.py @@ -6,9 +6,24 @@ from urllib.parse import quote _DEFAULT_DASHBOARD_BASE_URL = "https://openswe.vercel.app" +def _dashboard_base_url() -> str: + return os.environ.get("DASHBOARD_BASE_URL", _DEFAULT_DASHBOARD_BASE_URL).strip().rstrip("/") + + def dashboard_thread_url(thread_id: str) -> str | None: """Build the dashboard thread URL for a given thread id.""" - base_url = os.environ.get("DASHBOARD_BASE_URL", _DEFAULT_DASHBOARD_BASE_URL).strip().rstrip("/") + base_url = _dashboard_base_url() if not base_url or not thread_id: return None return f"{base_url}/agents/{quote(thread_id, safe='')}" + + +def dashboard_review_url(owner: str, repo: str, pr_number: int) -> str | None: + """Build the dashboard review-detail URL for a PR.""" + base_url = _dashboard_base_url() + if not base_url or not owner or not repo or not pr_number: + return None + return ( + f"{base_url}/agents/reviews/" + f"{quote(owner, safe='')}/{quote(repo, safe='')}/{quote(str(pr_number), safe='')}" + ) diff --git a/tests/test_dashboard_links.py b/tests/test_dashboard_links.py new file mode 100644 index 00000000..f04ff2f1 --- /dev/null +++ b/tests/test_dashboard_links.py @@ -0,0 +1,24 @@ +import importlib + +from agent.utils import dashboard_links + + +def test_dashboard_review_url_points_to_reviews_page(monkeypatch) -> None: + monkeypatch.setenv("DASHBOARD_BASE_URL", "https://example.com") + importlib.reload(dashboard_links) + url = dashboard_links.dashboard_review_url("owner", "repo", 42) + assert url == "https://example.com/agents/reviews/owner/repo/42" + + +def test_dashboard_review_url_escapes_segments(monkeypatch) -> None: + monkeypatch.setenv("DASHBOARD_BASE_URL", "https://example.com") + importlib.reload(dashboard_links) + url = dashboard_links.dashboard_review_url("my org", "my/repo", 7) + assert url == "https://example.com/agents/reviews/my%20org/my%2Frepo/7" + + +def test_dashboard_review_url_requires_fields(monkeypatch) -> None: + monkeypatch.setenv("DASHBOARD_BASE_URL", "https://example.com") + importlib.reload(dashboard_links) + assert dashboard_links.dashboard_review_url("", "repo", 1) is None + assert dashboard_links.dashboard_review_url("owner", "repo", 0) is None