feat(issues): Add endpoint for pull requests linked to an issue#116744
Conversation
Linked PRs only lived in the activity timeline, which is historical and
capped. This adds a current-state read API that pulls the high-signal
recent PRs out of activity and into something the sidebar can render.
GET /organizations/{org}/issues/{issue_id}/pull-requests/ returns the
5 most recent PRs linked through GroupLink (pull_request + resolves) in
the last 90 days, newest first. Reuses PullRequestSerializer for the
base fields and decorates each row with dateLinked and a best-effort
status (merged/open/closed/draft/unknown).
Status is live-fetched via the integration client for GitHub/GHE only,
scoped to the PR org so a stale Repository.integration_id can't reach
another org's installation. Anything else degrades to unknown. Nothing
is persisted, status from webhooks can be a follow-up.
Gated behind organizations:issue-details-linked-pull-requests, backend
only for now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🚨 Warning: This pull request contains Frontend and Backend changes! It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently. Have questions? Please ask in the |
mypy flagged the create_linked_pull_request helper for missing param and return types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the new group pull requests URL name to the region-pinned list so getsentry URL action checks know how to route it. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Push the org filter into the pull request lookup instead of filtering the bulk result in Python. Keeps the GroupLink ordering, just makes the access boundary more obvious. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Normalize the tiny provider response shape before mapping it to the sidebar PR status. Keeps the fallback behavior the same, just makes the status code a little easier to read and type. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Use explicit literal returns for provider PR states so full backend typing can verify the PullRequestStatus response type. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Keep the linked pull requests endpoint off the legacy unscoped group URL helper so getsentry URL validation does not need a new deprecated action entry. Regenerate known API URL types after removing the legacy issues/groups pull-requests routes. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4a349c2. Configure here.
Remove the deprecation decorator from the new endpoint and stop filtering linked pull requests by age. The response remains capped to the five newest resolving links. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
# Conflicts: # static/app/utils/api/knownSentryApiUrls.generated.ts
|
|
||
| assert response.status_code == 404 | ||
|
|
||
| def test_empty_response(self) -> None: |
There was a problem hiding this comment.
Could add a test that limit of 5 PRs is respected when there are more than 5 that exist
There was a problem hiding this comment.
Maybe also when a PR or repo gets deleted?
| title="Commit link", | ||
| linked_type=GroupLink.LinkedType.commit, | ||
| ) | ||
| Activity.objects.create( |
There was a problem hiding this comment.
it's silly but i think the SET_RESOLVED_IN_PULL_REQUEST Activity is actually irrelevant to issue resolution via PR behavior (although probably still good to include in the test for consistency). more discussion of that here: https://sentry.slack.com/archives/C0B349R63M1/p1779886618643259
| } | ||
| date_linked_by_pr_id = {link.linked_id: link.datetime for link in group_links} | ||
| pull_request_responses: list[LinkedPullRequestResponse] = [] | ||
| for pull_request in pull_requests: |
There was a problem hiding this comment.
probably doesn't matter in practice but could maybe issue these status requests in parallel if latency becomes a factor
| project_id=group.project_id, | ||
| linked_type=GroupLink.LinkedType.pull_request, | ||
| relationship=GroupLink.Relationship.resolves, | ||
| ).order_by("-datetime")[:DEFAULT_LIMIT] |
There was a problem hiding this comment.
Should the 90 day time frame be specified here?
There was a problem hiding this comment.
i ended up removing the 90 day time frame, i can clean that up more
|
|
||
| installation = integration.get_installation(organization_id=pull_request.organization_id) | ||
| client = installation.get_client() | ||
| get_pull_request = getattr(client, "get_pull_request", None) |
There was a problem hiding this comment.
should we be using the SCM platform to retrieve PRs?

Linked PRs only live in the activity timeline today This adds a current-state read API so the recent ones can be surfaced somewhere more useful like the issue sidebar.
GET /organizations/{org}/issues/{issue_id}/pull-requests/returns the 5 most recent PRs linked throughGroupLink, with a best-effortstatusthat's live-fetched for GitHub/GHE andunknowneverywhere else.gated behind
organizations:issue-details-linked-pull-requests.part of CORE-207