Skip to content

Commit c58fd63

Browse files
feat: Add GitHub link unfurling documentation
Co-authored-by: neel.shah <[email protected]>
1 parent 38e5055 commit c58fd63

File tree

2 files changed

+274
-13
lines changed
  • develop-docs/integrations
  • docs/organization/integrations/source-code-mgmt/github

2 files changed

+274
-13
lines changed

develop-docs/integrations/github.mdx

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,277 @@ privatekeyprivatekeyprivatekeyprivatekey
127127
```
128128

129129
Take note that your private key should be a multiline string without any whitespace before the start of a new line of the key.
130+
131+
## Link Unfurling
132+
133+
Link unfurling allows Sentry to display rich previews of Sentry links when they're shared in GitHub (such as in pull request descriptions, issue comments, or commit messages). This provides context about Sentry issues directly within GitHub without requiring users to click through.
134+
135+
### Overview
136+
137+
When a user shares a Sentry link in GitHub, the integration should:
138+
139+
1. Detect the shared Sentry link via GitHub webhooks
140+
2. Parse the link to extract the issue/event ID
141+
3. Fetch relevant data from Sentry's API
142+
4. Return formatted preview data to GitHub
143+
144+
### Implementation Steps
145+
146+
#### 1. Configure Webhook Events
147+
148+
Ensure your GitHub App is subscribed to events that include link sharing. While GitHub doesn't have a direct "link_shared" event like Slack, you can detect links in:
149+
150+
- Pull request descriptions and comments
151+
- Issue comments
152+
- Commit messages
153+
154+
The relevant webhook events are already configured if you followed the setup above:
155+
- `pull_request` - Captures links in PR descriptions
156+
- `issue_comment` - Captures links in comments
157+
- `push` - Captures links in commit messages
158+
159+
#### 2. Add Link Detection Logic
160+
161+
In your webhook handler, add logic to detect Sentry URLs in the incoming payload:
162+
163+
```python
164+
import re
165+
from urllib.parse import urlparse
166+
167+
SENTRY_LINK_PATTERN = re.compile(
168+
r'https?://(?:[\w-]+\.)?sentry\.io/(?:organizations/[\w-]+/)?issues/(\d+)/?(?:\?.*)?'
169+
)
170+
171+
def extract_sentry_links(text):
172+
"""Extract Sentry issue links from text."""
173+
if not text:
174+
return []
175+
176+
matches = SENTRY_LINK_PATTERN.finditer(text)
177+
return [
178+
{
179+
'url': match.group(0),
180+
'issue_id': match.group(1)
181+
}
182+
for match in matches
183+
]
184+
```
185+
186+
#### 3. Fetch Issue Data
187+
188+
When a Sentry link is detected, fetch the issue details:
189+
190+
```python
191+
def get_issue_preview_data(issue_id, organization_slug):
192+
"""Fetch issue data for preview."""
193+
from sentry.models import Group
194+
195+
try:
196+
issue = Group.objects.get(id=issue_id)
197+
198+
return {
199+
'title': issue.title,
200+
'status': issue.get_status_display(),
201+
'level': issue.level,
202+
'event_count': issue.times_seen,
203+
'user_count': issue.count_unique_users(),
204+
'first_seen': issue.first_seen,
205+
'last_seen': issue.last_seen,
206+
'permalink': issue.get_absolute_url(),
207+
}
208+
except Group.DoesNotExist:
209+
return None
210+
```
211+
212+
#### 4. Format Preview for GitHub
213+
214+
GitHub doesn't have a native unfurling API like Slack. Instead, you can:
215+
216+
**Option A: Add a Comment with Issue Details**
217+
218+
Post an automated comment with rich issue details:
219+
220+
```python
221+
def post_issue_preview_comment(github_client, repo, comment_id, issue_data):
222+
"""Post a comment with Sentry issue preview."""
223+
224+
comment_body = f"""
225+
### 🔍 Sentry Issue Preview
226+
227+
**{issue_data['title']}**
228+
229+
- **Status:** {issue_data['status']}
230+
- **Level:** {issue_data['level']}
231+
- **Events:** {issue_data['event_count']}
232+
- **Users Affected:** {issue_data['user_count']}
233+
- **Last Seen:** {issue_data['last_seen']}
234+
235+
[View in Sentry]({issue_data['permalink']})
236+
237+
---
238+
*This preview was automatically generated by Sentry*
239+
"""
240+
241+
github_client.create_comment(
242+
repo=repo,
243+
issue_number=comment_id,
244+
body=comment_body
245+
)
246+
```
247+
248+
**Option B: Use GitHub Checks or Status API**
249+
250+
For pull requests, you can use the Checks API to show Sentry issue information:
251+
252+
```python
253+
def create_sentry_check(github_client, repo, commit_sha, issues):
254+
"""Create a GitHub check with Sentry issues."""
255+
256+
if not issues:
257+
return
258+
259+
summary = f"Found {len(issues)} Sentry issue(s) referenced in this PR"
260+
261+
text = "\\n".join([
262+
f"- [{issue['title']}]({issue['permalink']}) - "
263+
f"{issue['event_count']} events, {issue['user_count']} users affected"
264+
for issue in issues
265+
])
266+
267+
github_client.create_check_run(
268+
repo=repo,
269+
name="Sentry Issues",
270+
head_sha=commit_sha,
271+
status="completed",
272+
conclusion="neutral",
273+
output={
274+
"title": "Sentry Issues Referenced",
275+
"summary": summary,
276+
"text": text
277+
}
278+
)
279+
```
280+
281+
#### 5. Handle Webhook Events
282+
283+
Add the unfurling logic to your webhook handler:
284+
285+
```python
286+
def handle_github_webhook(event_type, payload):
287+
"""Handle GitHub webhook events for link unfurling."""
288+
289+
if event_type == 'pull_request':
290+
# Check PR description
291+
pr_body = payload.get('pull_request', {}).get('body', '')
292+
links = extract_sentry_links(pr_body)
293+
294+
if links:
295+
repo = payload['repository']['full_name']
296+
pr_number = payload['pull_request']['number']
297+
298+
for link in links:
299+
issue_data = get_issue_preview_data(link['issue_id'], org_slug)
300+
if issue_data:
301+
post_issue_preview_comment(
302+
github_client,
303+
repo,
304+
pr_number,
305+
issue_data
306+
)
307+
308+
elif event_type == 'issue_comment':
309+
# Check comment body
310+
comment_body = payload.get('comment', {}).get('body', '')
311+
links = extract_sentry_links(comment_body)
312+
313+
if links:
314+
# Similar logic as above
315+
pass
316+
```
317+
318+
### Best Practices
319+
320+
1. **Rate Limiting**: Be mindful of GitHub's API rate limits. Cache issue data when possible.
321+
322+
2. **Permissions**: Ensure the GitHub App has the necessary permissions:
323+
- `issues: write` - To post comments on issues
324+
- `pull_requests: write` - To post comments on PRs
325+
- `checks: write` - To create check runs (optional)
326+
327+
3. **Deduplication**: Track which links have already been unfurled to avoid posting duplicate previews.
328+
329+
4. **Privacy**: Only unfurl links for issues the GitHub user has access to. Verify permissions before displaying issue details.
330+
331+
5. **Error Handling**: Gracefully handle cases where:
332+
- The issue doesn't exist
333+
- The user doesn't have permission to view the issue
334+
- The GitHub API request fails
335+
336+
### Testing
337+
338+
To test link unfurling locally:
339+
340+
1. Use ngrok to expose your local Sentry instance
341+
2. Create a test GitHub repository
342+
3. Install your GitHub App on the test repository
343+
4. Create a PR or comment with a Sentry issue link
344+
5. Verify the webhook is received and the preview is generated
345+
346+
### Example: Complete Webhook Handler
347+
348+
```python
349+
from rest_framework.response import Response
350+
from rest_framework import status
351+
352+
class GitHubWebhookEndpoint(Endpoint):
353+
authentication_classes = ()
354+
permission_classes = ()
355+
356+
def post(self, request):
357+
event_type = request.META.get('HTTP_X_GITHUB_EVENT')
358+
359+
if event_type in ['pull_request', 'issue_comment']:
360+
# Extract text content
361+
text = self._extract_text_from_payload(request.data, event_type)
362+
363+
# Find Sentry links
364+
sentry_links = extract_sentry_links(text)
365+
366+
if sentry_links:
367+
# Process each link asynchronously
368+
from sentry.tasks.integrations import unfurl_github_links
369+
unfurl_github_links.apply_async(
370+
kwargs={
371+
'payload': request.data,
372+
'event_type': event_type,
373+
'links': sentry_links
374+
}
375+
)
376+
377+
return Response(status=status.HTTP_204_NO_CONTENT)
378+
379+
def _extract_text_from_payload(self, payload, event_type):
380+
if event_type == 'pull_request':
381+
return payload.get('pull_request', {}).get('body', '')
382+
elif event_type == 'issue_comment':
383+
return payload.get('comment', {}).get('body', '')
384+
return ''
385+
```
386+
387+
### Configuration Options
388+
389+
You may want to add configuration options to control link unfurling behavior:
390+
391+
```yml
392+
# config.yml
393+
github-app.unfurl-links: true
394+
github-app.unfurl-method: "comment" # or "check"
395+
github-app.unfurl-delay: 5 # seconds to wait before posting
396+
```
397+
398+
### Resources
399+
400+
- [GitHub REST API Documentation](https://docs.github.com/en/rest)
401+
- [GitHub Webhooks Documentation](https://docs.github.com/en/developers/webhooks-and-events/webhooks)
402+
- [GitHub Checks API](https://docs.github.com/en/rest/checks)
403+
- [Slack Link Unfurling](./slack/#link-unfurling) - Similar concept for reference

docs/organization/integrations/source-code-mgmt/github/index.mdx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -353,19 +353,6 @@ Sentry will comment on the pull request with up to five issues per file that wer
353353

354354
This feature requires [code mappings](/product/issues/suspect-commits/#2-set-up-code-mappings) and is currently only supported for Python, JavaScript/TypeScript, PHP, and Ruby files. If you're using a different language, [let us know on this GitHub ticket](https://github.com/getsentry/sentry/issues/69824). For JavaScript/TypeScript, please ensure that you've unminified your code by [setting up source maps](/platforms/javascript/sourcemaps/).
355355

356-
### Link Unfurling
357-
358-
When you share Sentry issue links in GitHub (such as in pull request descriptions, issue comments, or commit messages), Sentry can automatically unfurl these links to display rich previews with key issue details. This makes it easier to understand the context of the issue without leaving GitHub.
359-
360-
The link preview includes:
361-
362-
- Issue title and description
363-
- Issue status and priority
364-
- Error count and user impact
365-
- Link to view the full issue in Sentry
366-
367-
This feature is automatically enabled once your GitHub integration has been set up and Sentry detects links shared in your GitHub repositories.
368-
369356
### Missing Member Detection
370357

371358
If there are users committing to GitHub repositories linked to Sentry and they're not members of your organization, Sentry detects them as missing members. Once a month, Sentry sends organization owners and managers an email reminding them to invite those users to join their org. Sentry also shows a banner to invite missing members in the **Settings > Members** page.

0 commit comments

Comments
 (0)