Browse Source

test(n1-api-call): Add E2E tests (#44173)

Add E2E visual snapshot tests for the N+1 API Call issue details page
and the alert email.
George Gritsouk 2 years ago
parent
commit
d192b58475

+ 51 - 0
fixtures/emails/n1_api_call.txt

@@ -0,0 +1,51 @@
+Details
+-------
+
+http://testserver/organizations/sentry/issues/1/?referrer=alert_email&alert_type=email&alert_timestamp=1337&alert_rule_id=1
+
+
+Suspect Commits
+---------------
+
+* feat: Do something to raven/base.py
+  1b17483 - dcramer@gmail.com
+
+
+Tags
+----
+
+* browser = Chrome 108.0.0
+* browser.name = Chrome
+* client_os = Mac OS X 10.15.7
+* client_os.name = Mac OS X
+* device = Mac
+* device.family = Mac
+* deviceMemory = 8 GB
+* effectiveConnectionType = 4g
+* environment = production
+* hardwareConcurrency = 10
+* lcp.element = body > div#__next > h1
+* lcp.size = 5738
+* level = info
+* os = Mac OS X 10.15.7
+* os.name = Mac OS X
+* sentry:user = ip:127.0.0.1
+* transaction = /
+* url = http://localhost:3030/
+
+
+Request
+-----------
+
+
+
+
+User
+-----------
+
+
+
+
+
+
+Unsubscribe: javascript:alert("This is a preview page, what did you expect to happen?");

+ 42 - 42
src/sentry/data/samples/transaction-n-plus-one-api-call.json

@@ -66,7 +66,7 @@
           "__span": "8e7189a4f1e24ac3",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=50"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=50"
         }
       },
       {
@@ -78,7 +78,7 @@
           "__span": "b2af9392df36fa1f",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=96"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=96"
         }
       },
       {
@@ -90,7 +90,7 @@
           "__span": "8869e7e96076fa88",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=43"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=43"
         }
       },
       {
@@ -102,7 +102,7 @@
           "__span": "ae58828e4fdd0bba",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=22"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=22"
         }
       },
       {
@@ -114,7 +114,7 @@
           "__span": "ac71c2e69245f37d",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=79"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=79"
         }
       },
       {
@@ -126,7 +126,7 @@
           "__span": "ba5183ea752ce85a",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=55"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=55"
         }
       },
       {
@@ -138,7 +138,7 @@
           "__span": "bd5f728e61f667cf",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=90"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=90"
         }
       },
       {
@@ -150,7 +150,7 @@
           "__span": "9e87e2127c3a3136",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=69"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=69"
         }
       },
       {
@@ -162,7 +162,7 @@
           "__span": "b38bff8a7d07b1bc",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=1"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=1"
         }
       },
       {
@@ -174,7 +174,7 @@
           "__span": "a530576977ba0714",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=48"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=48"
         }
       },
       {
@@ -186,7 +186,7 @@
           "__span": "918be77fbfd326ca",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=78"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=78"
         }
       },
       {
@@ -198,7 +198,7 @@
           "__span": "ae5b2d34409bb315",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=36"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=36"
         }
       },
       {
@@ -210,7 +210,7 @@
           "__span": "afa8a8b18afbad59",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=67"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=67"
         }
       },
       {
@@ -222,7 +222,7 @@
           "__span": "a39c22ce65e378cc",
           "method": "GET",
           "status_code": 200,
-          "url": "http://127.0.0.1:3000/book/?book_id=44"
+          "url": "http://127.0.0.1:3000/author/278/book?book_id=44"
         }
       }
     ]
@@ -334,7 +334,7 @@
       "timestamp": 1673477527.2685,
       "start_timestamp": 1673477526.9024,
       "exclusive_time": 366.100073,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=96",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=96",
       "op": "http.client",
       "span_id": "b2af9392df36fa1f",
       "parent_span_id": "829d17842d952371",
@@ -344,7 +344,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=96"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=96"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -352,7 +352,7 @@
       "timestamp": 1673477528.2172,
       "start_timestamp": 1673477526.9029,
       "exclusive_time": 1314.30006,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=44",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=44",
       "op": "http.client",
       "span_id": "a39c22ce65e378cc",
       "parent_span_id": "829d17842d952371",
@@ -362,7 +362,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=44"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=44"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -370,7 +370,7 @@
       "timestamp": 1673477527.2787,
       "start_timestamp": 1673477526.9031,
       "exclusive_time": 375.6001,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=22",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=22",
       "op": "http.client",
       "span_id": "ae58828e4fdd0bba",
       "parent_span_id": "829d17842d952371",
@@ -380,7 +380,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=22"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=22"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -388,7 +388,7 @@
       "timestamp": 1673477527.2765,
       "start_timestamp": 1673477526.9033,
       "exclusive_time": 373.19994,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=43",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=43",
       "op": "http.client",
       "span_id": "8869e7e96076fa88",
       "parent_span_id": "829d17842d952371",
@@ -398,7 +398,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=43"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=43"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -406,7 +406,7 @@
       "timestamp": 1673477527.2823,
       "start_timestamp": 1673477526.9035,
       "exclusive_time": 378.799915,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=79",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=79",
       "op": "http.client",
       "span_id": "ac71c2e69245f37d",
       "parent_span_id": "829d17842d952371",
@@ -416,7 +416,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=79"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=79"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -424,7 +424,7 @@
       "timestamp": 1673477527.2668,
       "start_timestamp": 1673477526.9037,
       "exclusive_time": 363.099813,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=50",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=50",
       "op": "http.client",
       "span_id": "8e7189a4f1e24ac3",
       "parent_span_id": "829d17842d952371",
@@ -434,7 +434,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=50"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=50"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -442,7 +442,7 @@
       "timestamp": 1673477527.626,
       "start_timestamp": 1673477526.904,
       "exclusive_time": 721.999884,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=55",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=55",
       "op": "http.client",
       "span_id": "ba5183ea752ce85a",
       "parent_span_id": "829d17842d952371",
@@ -452,7 +452,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=55"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=55"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -460,7 +460,7 @@
       "timestamp": 1673477527.6517,
       "start_timestamp": 1673477526.9043,
       "exclusive_time": 747.400045,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=48",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=48",
       "op": "http.client",
       "span_id": "a530576977ba0714",
       "parent_span_id": "829d17842d952371",
@@ -470,7 +470,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=48"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=48"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -478,7 +478,7 @@
       "timestamp": 1673477527.6335,
       "start_timestamp": 1673477526.9046,
       "exclusive_time": 728.900194,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=90",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=90",
       "op": "http.client",
       "span_id": "bd5f728e61f667cf",
       "parent_span_id": "829d17842d952371",
@@ -488,7 +488,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=90"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=90"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -496,7 +496,7 @@
       "timestamp": 1673477527.6433,
       "start_timestamp": 1673477526.9048,
       "exclusive_time": 738.500118,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=69",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=69",
       "op": "http.client",
       "span_id": "9e87e2127c3a3136",
       "parent_span_id": "829d17842d952371",
@@ -506,7 +506,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=69"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=69"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -514,7 +514,7 @@
       "timestamp": 1673477527.6485,
       "start_timestamp": 1673477526.9051,
       "exclusive_time": 743.399858,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=1",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=1",
       "op": "http.client",
       "span_id": "b38bff8a7d07b1bc",
       "parent_span_id": "829d17842d952371",
@@ -524,7 +524,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=1"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=1"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -532,7 +532,7 @@
       "timestamp": 1673477528.0083,
       "start_timestamp": 1673477526.9052,
       "exclusive_time": 1103.100061,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=36",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=36",
       "op": "http.client",
       "span_id": "ae5b2d34409bb315",
       "parent_span_id": "829d17842d952371",
@@ -542,7 +542,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=36"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=36"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -550,7 +550,7 @@
       "timestamp": 1673477528.0075,
       "start_timestamp": 1673477526.9054,
       "exclusive_time": 1102.099896,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=78",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=78",
       "op": "http.client",
       "span_id": "918be77fbfd326ca",
       "parent_span_id": "829d17842d952371",
@@ -560,7 +560,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=78"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=78"
       },
       "hash": "c2ea6c5f7ba887a2"
     },
@@ -568,7 +568,7 @@
       "timestamp": 1673477528.0129,
       "start_timestamp": 1673477526.9057,
       "exclusive_time": 1107.200146,
-      "description": "GET http://127.0.0.1:3000/book/?book_id=67",
+      "description": "GET http://127.0.0.1:3000/author/278/book?book_id=67",
       "op": "http.client",
       "span_id": "afa8a8b18afbad59",
       "parent_span_id": "829d17842d952371",
@@ -578,7 +578,7 @@
       "data": {
         "method": "GET",
         "type": "fetch",
-        "url": "http://127.0.0.1:3000/book/?book_id=67"
+        "url": "http://127.0.0.1:3000/author/278/book?book_id=67"
       },
       "hash": "c2ea6c5f7ba887a2"
     },

+ 3 - 2
src/sentry/web/frontend/debug/mail.py

@@ -45,7 +45,7 @@ from sentry.notifications.notifications.base import BaseNotification
 from sentry.notifications.notifications.digest import DigestNotification
 from sentry.notifications.types import GroupSubscriptionReason
 from sentry.notifications.utils import get_group_settings_link, get_interface_list, get_rules
-from sentry.testutils.helpers import override_options
+from sentry.testutils.helpers import Feature, override_options
 from sentry.testutils.helpers.datetime import before_now
 from sentry.testutils.helpers.notifications import TEST_ISSUE_OCCURRENCE
 from sentry.types.issues import GROUP_TYPE_TO_TEXT
@@ -193,8 +193,9 @@ def make_performance_event(project, sample_name: str):
         {
             "performance.issues.all.problem-detection": 1.0,
             "performance.issues.n_plus_one_db.problem-creation": 1.0,
+            "performance.issues.n_plus_one_api_calls.problem-creation": 1.0,
         }
-    ):
+    ), Feature({"organizations:performance-n-plus-one-api-calls-detector": True}):
         perf_data = dict(
             load_data(
                 sample_name,

+ 1 - 0
tests/acceptance/test_emails.py

@@ -20,6 +20,7 @@ EMAILS = (
     ("/debug/mail/unable-to-delete-repo/", "unable to delete repo"),
     ("/debug/mail/error-alert/", "alert"),
     ("/debug/mail/performance-alert/transaction-n-plus-one", "performance"),
+    ("/debug/mail/performance-alert/transaction-n-plus-one-api-call/", "n1 api call"),
     ("/debug/mail/digest/", "digest"),
     ("/debug/mail/invalid-identity/", "invalid identity"),
     ("/debug/mail/invitation/", "invitation"),

+ 29 - 7
tests/acceptance/test_performance_issues.py

@@ -13,6 +13,7 @@ from sentry.utils import json
 
 FEATURES = {
     "projects:performance-suspect-spans-ingestion": True,
+    "organizations:performance-n-plus-one-api-calls-detector": True,
 }
 
 
@@ -28,14 +29,13 @@ class PerformanceIssuesTest(AcceptanceTestCase, SnubaTestCase):
 
         options.set("performance.issues.all.problem-detection", 1.0)
         options.set("performance.issues.n_plus_one_db.problem-creation", 1.0)
+        options.set("performance.issues.n_plus_one_api_calls.problem-creation", 1.0)
 
         self.page = IssueDetailsPage(self.browser, self.client)
         self.dismiss_assistant()
 
-    def create_sample_event(self, start_timestamp):
-        event = json.loads(
-            self.load_fixture("events/performance_problems/n-plus-one-in-django-new-view.json")
-        )
+    def create_sample_event(self, fixture, start_timestamp):
+        event = json.loads(self.load_fixture(f"events/performance_problems/{fixture}.json"))
 
         for key in ["datetime", "location", "title"]:
             del event[key]
@@ -61,7 +61,9 @@ class PerformanceIssuesTest(AcceptanceTestCase, SnubaTestCase):
     @patch("django.utils.timezone.now")
     def test_with_one_performance_issue(self, mock_now):
         mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
-        event_data = self.create_sample_event(mock_now.return_value.timestamp())
+        event_data = self.create_sample_event(
+            "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
+        )
 
         with self.feature(FEATURES):
             event = self.store_event(data=event_data, project_id=self.project.id)
@@ -72,20 +74,40 @@ class PerformanceIssuesTest(AcceptanceTestCase, SnubaTestCase):
     @patch("django.utils.timezone.now")
     def test_multiple_events_with_one_cause_are_grouped(self, mock_now):
         mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
-        event_data = self.create_sample_event(mock_now.return_value.timestamp())
+        event_data = self.create_sample_event(
+            "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
+        )
 
         with self.feature(FEATURES):
             [self.store_event(data=event_data, project_id=self.project.id) for _ in range(3)]
 
             assert Group.objects.count() == 1
 
+    @patch("django.utils.timezone.now")
+    def test_n_one_api_call_performance_issue(self, mock_now):
+        mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
+        event_data = self.create_sample_event(
+            "n-plus-one-api-calls/n-plus-one-api-calls-in-issue-stream",
+            mock_now.return_value.timestamp(),
+        )
+
+        event_data["contexts"]["trace"]["op"] = "navigation"
+
+        with self.feature(FEATURES):
+            event = self.store_event(data=event_data, project_id=self.project.id)
+
+            self.page.visit_issue(self.org.slug, event.groups[0].id)
+            self.browser.snapshot("N+1 API Call issue details", desktop_only=True)
+
     @patch("django.utils.timezone.now")
     def test_multiple_events_with_multiple_causes_are_not_grouped(self, mock_now):
         mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
 
         # Create identical events with different parent spans
         for _ in range(3):
-            event_data = self.create_sample_event(mock_now.return_value.timestamp())
+            event_data = self.create_sample_event(
+                "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
+            )
             event_data["spans"] = [
                 self.randomize_span_description(span) if span["op"] == "django.view" else span
                 for span in event_data["spans"]