Repository: getsentry/sentry

Issue:
Update GitHub integration to support generic issue types (ticketing feature)
Update the GitHub integration so that it displays the `IssueEvidence` for a generic issue type. See https://github.com/getsentry/sentry/pull/41041 for how it was done for performance issues as it'll be similar.


Discussion:

Resolving patch:
diff --git a/src/sentry/integrations/github/issues.py b/src/sentry/integrations/github/issues.py
--- a/src/sentry/integrations/github/issues.py
+++ b/src/sentry/integrations/github/issues.py
@@ -1,10 +1,11 @@
 from __future__ import annotations
 
+from operator import attrgetter
 from typing import Any, Mapping, Sequence
 
 from django.urls import reverse
 
-from sentry.eventstore.models import Event
+from sentry.eventstore.models import Event, GroupEvent
 from sentry.integrations.mixins.issues import MAX_CHAR, IssueBasicMixin
 from sentry.models import ExternalIssue, Group, User
 from sentry.shared_integrations.exceptions import ApiError, IntegrationError
@@ -37,13 +38,25 @@ def get_performance_issue_body(self, event: Event) -> str:
         body += f"| **Repeating Spans ({num_repeating_spans})** | {truncatechars(repeating_spans, MAX_CHAR)} |"
         return body
 
-    def get_group_description(self, group: Group, event: Event, **kwargs: Any) -> str:
+    def get_generic_issue_body(self, event: GroupEvent) -> str:
+        body = "|  |  |\n"
+        body += "| ------------- | --------------- |\n"
+        for evidence in sorted(
+            event.occurrence.evidence_display, key=attrgetter("important"), reverse=True
+        ):
+            body += f"| **{evidence.name}** | {truncatechars(evidence.value, MAX_CHAR)} |\n"
+
+        return body[:-2]
+
+    def get_group_description(self, group: Group, event: Event | GroupEvent, **kwargs: Any) -> str:
         output = self.get_group_link(group, **kwargs)
 
         if group.issue_category == GroupCategory.PERFORMANCE:
             body = self.get_performance_issue_body(event)
             output.extend([body])
-
+        elif isinstance(event, GroupEvent) and event.occurrence is not None:
+            body = self.get_generic_issue_body(event)
+            output.extend([body])
         else:
             body = self.get_group_body(group, event)
             if body:


Test patch:
diff --git a/tests/sentry/integrations/github/test_issues.py b/tests/sentry/integrations/github/test_issues.py
--- a/tests/sentry/integrations/github/test_issues.py
+++ b/tests/sentry/integrations/github/test_issues.py
@@ -4,21 +4,19 @@
 import responses
 from django.test import RequestFactory
 
-from sentry.event_manager import EventManager
 from sentry.integrations.github.integration import GitHubIntegration
 from sentry.integrations.github.issues import GitHubIssueBasic
 from sentry.models import ExternalIssue, Integration
 from sentry.testutils import TestCase
-from sentry.testutils.helpers import override_options
+from sentry.testutils.cases import PerformanceIssueTestCase
 from sentry.testutils.helpers.datetime import before_now, iso_format
+from sentry.testutils.helpers.notifications import TEST_ISSUE_OCCURRENCE
 from sentry.testutils.silo import region_silo_test
-from sentry.types.issues import GroupType
 from sentry.utils import json
-from sentry.utils.samples import load_data
 
 
 @region_silo_test
-class GitHubIssueBasicTest(TestCase):
+class GitHubIssueBasicTest(TestCase, PerformanceIssueTestCase):
     @cached_property
     def request(self):
         return RequestFactory()
@@ -101,30 +99,9 @@ def test_create_issue(self, mock_get_jwt):
         payload = json.loads(request.body)
         assert payload == {"body": "This is the description", "assignee": None, "title": "hello"}
 
-    def test_performance_issues_description(self):
-        """Test that a GitHub issue created from a performance issue has span evidence data in its description"""
-        event_data = load_data(
-            "transaction-n-plus-one",
-            timestamp=before_now(minutes=10),
-            fingerprint=[f"{GroupType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES.value}-group1"],
-        )
-        perf_event_manager = EventManager(event_data)
-        perf_event_manager.normalize()
-        with override_options(
-            {
-                "performance.issues.all.problem-creation": 1.0,
-                "performance.issues.all.problem-detection": 1.0,
-                "performance.issues.n_plus_one_db.problem-creation": 1.0,
-            }
-        ), self.feature(
-            [
-                "organizations:performance-issues-ingest",
-                "projects:performance-suspect-spans-ingestion",
-            ]
-        ):
-            event = perf_event_manager.save(self.project.id)
-        event = event.for_group(event.groups[0])
-
+    def test_performance_issues_content(self):
+        """Test that a GitHub issue created from a performance issue has the expected title and description"""
+        event = self.create_performance_issue()
         description = GitHubIssueBasic().get_group_description(event.group, event)
         assert "db - SELECT `books_author`.`id`, `books_author" in description
         title = GitHubIssueBasic().get_group_title(event.group, event)
@@ -133,8 +110,30 @@ def test_performance_issues_description(self):
             == 'N+1 Query: SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = %s LIMIT 21'
         )
 
-    def test_error_issues_description(self):
-        """Test that a GitHub issue created from an error issue has message  data in its description"""
+    def test_generic_issues_content(self):
+        """Test that a GitHub issue created from a generic issue has the expected title and description"""
+
+        occurrence = TEST_ISSUE_OCCURRENCE
+        event = self.store_event(
+            data={
+                "event_id": "a" * 32,
+                "message": "oh no",
+                "timestamp": iso_format(before_now(minutes=1)),
+            },
+            project_id=self.project.id,
+        )
+        event = event.for_group(event.groups[0])
+        event.occurrence = occurrence
+
+        description = GitHubIssueBasic().get_group_description(event.group, event)
+        assert event.occurrence.evidence_display[0].value in description
+        assert event.occurrence.evidence_display[1].value in description
+        assert event.occurrence.evidence_display[2].value in description
+        title = GitHubIssueBasic().get_group_title(event.group, event)
+        assert title == event.occurrence.issue_title
+
+    def test_error_issues_content(self):
+        """Test that a GitHub issue created from an error issue has the expected title and descriptionn"""
         event = self.store_event(
             data={
                 "event_id": "a" * 32,
@@ -146,6 +145,8 @@ def test_error_issues_description(self):
 
         description = GitHubIssueBasic().get_group_description(event.group, event)
         assert "oh no" in description
+        title = GitHubIssueBasic().get_group_title(event.group, event)
+        assert title == event.title
 
     @responses.activate
     @patch("sentry.integrations.github.client.get_jwt", return_value="jwt_token_1")