Browse Source

ref(perf-trends): Creating a separate endpoint for trends (#20900)

* ref(perf-trends): Creating a separate endpoint for trends

- The existing endpoint is now `event-trends-stats` and continues to
  return both stats data and events data
- The "new" endpoint is just `event-trends` and only returns events
  data, and the most project widgets will now use this instead
William Mak 4 years ago
parent
commit
17b4a661b1

+ 28 - 12
src/sentry/api/endpoints/organization_events_trends.py

@@ -13,7 +13,7 @@ from sentry.api.paginator import GenericOffsetPaginator
 from sentry.snuba import discover
 
 
-class OrganizationEventsTrendsEndpoint(OrganizationEventsV2EndpointBase):
+class OrganizationEventsTrendsEndpointBase(OrganizationEventsV2EndpointBase):
     trend_columns = {
         "p50": {
             "format": "percentile_range(transaction.duration, 0.5, {start}, {end}, {index})",
@@ -57,9 +57,7 @@ class OrganizationEventsTrendsEndpoint(OrganizationEventsV2EndpointBase):
         except NoProjects:
             return Response([])
 
-        with sentry_sdk.start_span(op="discover.endpoint", description="trend_dates") as span:
-            span.set_tag("organization", organization)
-
+        with sentry_sdk.start_span(op="discover.endpoint", description="trend_dates"):
             middle = params["start"] + timedelta(
                 seconds=(params["end"] - params["start"]).total_seconds() * 0.5
             )
@@ -104,6 +102,22 @@ class OrganizationEventsTrendsEndpoint(OrganizationEventsV2EndpointBase):
                 use_aggregate_conditions=True,
             )
 
+        with self.handle_query_errors():
+            return self.paginate(
+                request=request,
+                paginator=GenericOffsetPaginator(data_fn=data_fn),
+                on_results=self.build_result_handler(
+                    request, organization, params, trend_function, selected_columns, orderby
+                ),
+                default_per_page=5,
+                max_per_page=5,
+            )
+
+
+class OrganizationEventsTrendsStatsEndpoint(OrganizationEventsTrendsEndpointBase):
+    def build_result_handler(
+        self, request, organization, params, trend_function, selected_columns, orderby
+    ):
         def on_results(events_results):
             def get_event_stats(query_columns, query, params, rollup):
                 return discover.top_events_timeseries(
@@ -139,11 +153,13 @@ class OrganizationEventsTrendsEndpoint(OrganizationEventsV2EndpointBase):
                 "stats": stats_results,
             }
 
-        with self.handle_query_errors():
-            return self.paginate(
-                request=request,
-                paginator=GenericOffsetPaginator(data_fn=data_fn),
-                on_results=on_results,
-                default_per_page=5,
-                max_per_page=5,
-            )
+        return on_results
+
+
+class OrganizationEventsTrendsEndpoint(OrganizationEventsTrendsEndpointBase):
+    def build_result_handler(
+        self, request, organization, params, trend_function, selected_columns, orderby
+    ):
+        return lambda events_results: self.handle_results_with_meta(
+            request, organization, params["project_id"], events_results
+        )

+ 9 - 1
src/sentry/api/urls.py

@@ -94,7 +94,10 @@ from .endpoints.organization_environments import OrganizationEnvironmentsEndpoin
 from .endpoints.organization_event_details import OrganizationEventDetailsEndpoint
 from .endpoints.organization_eventid import EventIdLookupEndpoint
 from .endpoints.organization_events import OrganizationEventsEndpoint, OrganizationEventsV2Endpoint
-from .endpoints.organization_events_trends import OrganizationEventsTrendsEndpoint
+from .endpoints.organization_events_trends import (
+    OrganizationEventsTrendsEndpoint,
+    OrganizationEventsTrendsStatsEndpoint,
+)
 from .endpoints.organization_events_facets import OrganizationEventsFacetsEndpoint
 from .endpoints.organization_events_meta import (
     OrganizationEventsMetaEndpoint,
@@ -841,6 +844,11 @@ urlpatterns = [
                     OrganizationEventsTrendsEndpoint.as_view(),
                     name="sentry-api-0-organization-events-trends",
                 ),
+                url(
+                    r"^(?P<organization_slug>[^\/]+)/events-trends-stats/$",
+                    OrganizationEventsTrendsStatsEndpoint.as_view(),
+                    name="sentry-api-0-organization-events-trends-stats",
+                ),
                 url(
                     r"^(?P<organization_slug>[^\/]+)/event-baseline/$",
                     OrganizationEventBaseline.as_view(),

+ 9 - 3
src/sentry/static/sentry/app/utils/discover/discoverQuery.tsx

@@ -45,6 +45,7 @@ type Props = {
   orgSlug: string;
   keyTransactions?: boolean;
   trendChangeType?: TrendChangeType;
+  trendStats?: boolean;
   /**
    * Record limit to get.
    */
@@ -132,12 +133,16 @@ class DiscoverQuery extends React.Component<Props, State> {
     return !isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
   };
 
-  getRoute(keyTransactions, trendsType) {
+  getRoute(keyTransactions, trendsType, trendStats) {
     if (keyTransactions) {
       return 'key-transactions';
     }
     if (trendsType) {
-      return 'events-trends';
+      if (trendStats) {
+        return 'events-trends-stats';
+      } else {
+        return 'events-trends';
+      }
     }
     return 'eventsv2';
   }
@@ -151,13 +156,14 @@ class DiscoverQuery extends React.Component<Props, State> {
       cursor,
       keyTransactions,
       trendChangeType,
+      trendStats,
     } = this.props;
 
     if (!eventView.isValid()) {
       return;
     }
 
-    const route = this.getRoute(keyTransactions, trendChangeType);
+    const route = this.getRoute(keyTransactions, trendChangeType, trendStats);
 
     const url = `/organizations/${orgSlug}/${route}/`;
     const tableFetchID = Symbol(`tableFetchID`);

+ 3 - 3
src/sentry/static/sentry/app/views/performance/trends/changedProjects.tsx

@@ -24,7 +24,7 @@ import {
   TrendChangeType,
   TrendFunctionField,
   TrendView,
-  ProjectTrendsData,
+  ProjectTrendsDataEvents,
   NormalizedProjectTrend,
 } from './types';
 import {modifyTrendView, normalizeTrends, trendToColor, getTrendProjectId} from './utils';
@@ -135,8 +135,8 @@ function ChangedProjects(props: Props) {
       limit={1}
     >
       {({isLoading, tableData}) => {
-        const eventsTrendsData = (tableData as unknown) as ProjectTrendsData;
-        const trends = eventsTrendsData?.events?.data || [];
+        const eventsTrendsData = (tableData as unknown) as ProjectTrendsDataEvents;
+        const trends = eventsTrendsData?.data || [];
         const events = normalizeTrends(trends);
 
         const transactionsList = events && events.slice ? events.slice(0, 5) : [];

+ 1 - 0
src/sentry/static/sentry/app/views/performance/trends/changedTransactions.tsx

@@ -173,6 +173,7 @@ function ChangedTransactions(props: Props) {
       trendChangeType={trendChangeType}
       cursor={cursor}
       limit={5}
+      trendStats
     >
       {({isLoading, tableData, pageLinks}) => {
         const eventsTrendsData = (tableData as unknown) as TrendsData;

+ 7 - 0
tests/js/spec/views/performance/landing.spec.jsx

@@ -133,6 +133,13 @@ describe('Performance > Landing', function() {
         events: {meta: {}, data: []},
       },
     });
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/events-trends-stats/',
+      body: {
+        stats: {},
+        events: {meta: {}, data: []},
+      },
+    });
   });
 
   afterEach(function() {

+ 60 - 16
tests/js/spec/views/performance/trends.spec.jsx

@@ -58,6 +58,7 @@ function initializeData(projects, query) {
 
 describe('Performance > Trends', function() {
   let trendsMock;
+  let trendsStatsMock;
   let baselineMock;
   beforeEach(function() {
     MockApiClient.addMockResponse({
@@ -85,8 +86,8 @@ describe('Performance > Trends', function() {
       url: '/organizations/org-slug/releases/',
       body: [],
     });
-    trendsMock = MockApiClient.addMockResponse({
-      url: '/organizations/org-slug/events-trends/',
+    trendsStatsMock = MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/events-trends-stats/',
       body: {
         stats: {
           'internal,/organizations/:orgId/performance/': {
@@ -134,6 +135,47 @@ describe('Performance > Trends', function() {
         },
       },
     });
+    trendsMock = MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/events-trends/',
+      body: {
+        meta: {
+          count_range_1: 'integer',
+          count_range_2: 'integer',
+          percentage_count_range_2_count_range_1: 'percentage',
+          percentage_percentile_range_2_percentile_range_1: 'percentage',
+          minus_percentile_range_2_percentile_range_1: 'number',
+          percentile_range_1: 'duration',
+          percentile_range_2: 'duration',
+          transaction: 'string',
+        },
+        data: [
+          {
+            count: 8,
+            project: 'internal',
+            count_range_1: 2,
+            count_range_2: 6,
+            percentage_count_range_2_count_range_1: 3,
+            percentage_percentile_range_2_percentile_range_1: 1.9235225955967554,
+            minus_percentile_range_2_percentile_range_1: 797,
+            percentile_range_1: 863,
+            percentile_range_2: 1660,
+            transaction: '/organizations/:orgId/performance/',
+          },
+          {
+            count: 60,
+            project: 'internal',
+            count_range_1: 20,
+            count_range_2: 40,
+            percentage_count_range_2_count_range_1: 2,
+            percentage_percentile_range_2_percentile_range_1: 1.204968944099379,
+            minus_percentile_range_2_percentile_range_1: 66,
+            percentile_range_1: 322,
+            percentile_range_2: 388,
+            transaction: '/api/0/internal/health/',
+          },
+        ],
+      },
+    });
     baselineMock = MockApiClient.addMockResponse({
       url: '/organizations/org-slug/event-baseline/',
       body: {
@@ -407,13 +449,15 @@ describe('Performance > Trends', function() {
 
     for (const trendFunction of TRENDS_FUNCTIONS) {
       trendsMock.mockReset();
+      trendsStatsMock.mockReset();
       wrapper.setProps({
         location: {query: {...trendsViewQuery, trendFunction: trendFunction.field}},
       });
       wrapper.update();
       await tick();
 
-      expect(trendsMock).toHaveBeenCalledTimes(4);
+      expect(trendsMock).toHaveBeenCalledTimes(2);
+      expect(trendsStatsMock).toHaveBeenCalledTimes(2);
 
       const aliasedFieldDivide = getTrendAliasedFieldPercentage(trendFunction.alias);
       const aliasedQueryDivide = getTrendAliasedQueryPercentage(trendFunction.alias);
@@ -452,41 +496,41 @@ describe('Performance > Trends', function() {
         })
       );
 
-      // Regression projects call
-      expect(trendsMock).toHaveBeenNthCalledWith(
-        2,
+      // Improved transactions call
+      expect(trendsStatsMock).toHaveBeenNthCalledWith(
+        1,
         expect.anything(),
         expect.objectContaining({
           query: expect.objectContaining({
             trendFunction: trendFunction.field,
-            sort: '-' + sort,
-            query: expect.stringContaining(aliasedQueryDivide + ':>1'),
+            sort,
+            query: expect.stringContaining(aliasedQueryDivide + ':<1'),
             interval: '12h',
-            field: projectFields,
+            field: transactionFields,
             statsPeriod: '14d',
           }),
         })
       );
 
-      // Improved transactions call
+      // Regression projects call
       expect(trendsMock).toHaveBeenNthCalledWith(
-        3,
+        2,
         expect.anything(),
         expect.objectContaining({
           query: expect.objectContaining({
             trendFunction: trendFunction.field,
-            sort,
-            query: expect.stringContaining(aliasedQueryDivide + ':<1'),
+            sort: '-' + sort,
+            query: expect.stringContaining(aliasedQueryDivide + ':>1'),
             interval: '12h',
-            field: transactionFields,
+            field: projectFields,
             statsPeriod: '14d',
           }),
         })
       );
 
       // Regression transactions call
-      expect(trendsMock).toHaveBeenNthCalledWith(
-        4,
+      expect(trendsStatsMock).toHaveBeenNthCalledWith(
+        2,
         expect.anything(),
         expect.objectContaining({
           query: expect.objectContaining({

+ 279 - 54
tests/snuba/api/endpoints/test_organization_events_trends.py

@@ -11,14 +11,12 @@ from sentry.testutils.helpers import parse_link_header
 from sentry.testutils.helpers.datetime import before_now, iso_format
 
 
-class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
+class OrganizationEventsTrendsBase(APITestCase, SnubaTestCase):
     def setUp(self):
-        super(OrganizationEventsTrendsEndpointTest, self).setUp()
+        super(OrganizationEventsTrendsBase, self).setUp()
         self.login_as(user=self.user)
 
         self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
-
-        self.project = self.create_project()
         self.prototype = load_data("transaction")
         data = self.prototype.copy()
         data["start_timestamp"] = iso_format(self.day_ago + timedelta(minutes=30))
@@ -36,14 +34,274 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
             data["user"] = {"email": "foo{}@example.com".format(i)}
             self.store_event(data, project_id=self.project.id)
 
+
+class OrganizationEventsTrendsEndpointTest(OrganizationEventsTrendsBase):
+    def setUp(self):
+        super(OrganizationEventsTrendsEndpointTest, self).setUp()
+        self.url = reverse(
+            "sentry-api-0-organization-events-trends",
+            kwargs={"organization_slug": self.project.organization.slug},
+        )
+
     def test_simple(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                },
+            )
+
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_1": 1,
+            "count_range_2": 3,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "percentile_range_1": 2000,
+            "percentile_range_2": 2000,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_percentile_range_2_percentile_range_1": 0.0,
+            "percentage_percentile_range_2_percentile_range_1": 1.0,
+        }
+
+    def test_p75(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "p75()",
+                },
+            )
+
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_1": 1,
+            "count_range_2": 3,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "percentile_range_1": 2000,
+            "percentile_range_2": 6000,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_percentile_range_2_percentile_range_1": 4000.0,
+            "percentage_percentile_range_2_percentile_range_1": 3.0,
+        }
+
+    def test_p95(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "p95()",
+                },
+            )
+
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_1": 1,
+            "count_range_2": 3,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "percentile_range_1": 2000,
+            "percentile_range_2": 9200,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_percentile_range_2_percentile_range_1": 7200.0,
+            "percentage_percentile_range_2_percentile_range_1": 4.6,
+        }
+
+    def test_p99(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "p99()",
+                },
+            )
+
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_1": 1,
+            "count_range_2": 3,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "percentile_range_1": 2000,
+            "percentile_range_2": 9840,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_percentile_range_2_percentile_range_1": 7840.0,
+            "percentage_percentile_range_2_percentile_range_1": 4.92,
+        }
+
+    def test_avg_trend_function(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "avg(transaction.duration)",
+                    "project": [self.project.id],
+                },
+            )
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_2": 3,
+            "count_range_1": 1,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "avg_range_1": 2000,
+            "avg_range_2": 4000,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_avg_range_2_avg_range_1": 2000.0,
+            "percentage_avg_range_2_avg_range_1": 2.0,
+        }
+
+    def test_misery_trend_function(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "user_misery(300)",
+                    "project": [self.project.id],
+                },
+            )
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_2": 3,
+            "count_range_1": 1,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "user_misery_range_1": 1,
+            "user_misery_range_2": 2,
+            "percentage_count_range_2_count_range_1": 3.0,
+            "minus_user_misery_range_2_user_misery_range_1": 1.0,
+            "percentage_user_misery_range_2_user_misery_range_1": 2.0,
+        }
+
+    def test_invalid_trend_function(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "trendFunction": "apdex(450)",
+                    "project": [self.project.id],
+                },
             )
+            assert response.status_code == 400
+
+    def test_divide_by_zero(self):
+        with self.feature("organizations:trends"):
+            response = self.client.get(
+                self.url,
+                format="json",
+                data={
+                    # Set the timeframe to where the second range has no transactions so all the counts/percentile are 0
+                    "end": iso_format(self.day_ago + timedelta(hours=2)),
+                    "start": iso_format(self.day_ago - timedelta(hours=2)),
+                    "field": ["project", "transaction"],
+                    "query": "event.type:transaction",
+                    "project": [self.project.id],
+                },
+            )
+        assert response.status_code == 200, response.content
+
+        events = response.data
+
+        assert len(events["data"]) == 1
+        # Shouldn't do an exact match here because we aren't using the stable correlation function
+        assert events["data"][0].pop("absolute_correlation") > 0.2
+        assert events["data"][0] == {
+            "count_range_2": 4,
+            "count_range_1": 0,
+            "transaction": self.prototype["transaction"],
+            "project": self.project.slug,
+            "percentile_range_1": 0,
+            "percentile_range_2": 2000.0,
+            "percentage_count_range_2_count_range_1": None,
+            "minus_percentile_range_2_percentile_range_1": 0,
+            "percentage_percentile_range_2_percentile_range_1": None,
+        }
+
+
+class OrganizationEventsTrendsStatsEndpointTest(OrganizationEventsTrendsBase):
+    def setUp(self):
+        super(OrganizationEventsTrendsStatsEndpointTest, self).setUp()
+        self.url = reverse(
+            "sentry-api-0-organization-events-trends-stats",
+            kwargs={"organization_slug": self.project.organization.slug},
+        )
+
+    def test_simple(self):
+        with self.feature("organizations:trends"):
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -81,12 +339,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_p75(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -125,12 +379,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_p95(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -169,12 +419,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_p99(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -213,12 +459,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_avg_trend_function(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -257,12 +499,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_misery_trend_function(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -301,12 +539,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_invalid_trend_function(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     "end": iso_format(self.day_ago + timedelta(hours=2)),
@@ -321,12 +555,8 @@ class OrganizationEventsTrendsEndpointTest(APITestCase, SnubaTestCase):
 
     def test_divide_by_zero(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     # Set the timeframe to where the second range has no transactions so all the counts/percentile are 0
@@ -370,10 +600,13 @@ class OrganizationEventsTrendsPagingTest(APITestCase, SnubaTestCase):
     def setUp(self):
         super(OrganizationEventsTrendsPagingTest, self).setUp()
         self.login_as(user=self.user)
+        self.url = reverse(
+            "sentry-api-0-organization-events-trends-stats",
+            kwargs={"organization_slug": self.project.organization.slug},
+        )
 
         self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
 
-        self.project = self.create_project()
         self.prototype = load_data("transaction")
 
         # Make 10 transactions for paging
@@ -401,12 +634,8 @@ class OrganizationEventsTrendsPagingTest(APITestCase, SnubaTestCase):
 
     def test_pagination(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     # Set the timeframe to where the second range has no transactions so all the counts/percentile are 0
@@ -434,12 +663,8 @@ class OrganizationEventsTrendsPagingTest(APITestCase, SnubaTestCase):
 
     def test_pagination_with_query(self):
         with self.feature("organizations:trends"):
-            url = reverse(
-                "sentry-api-0-organization-events-trends",
-                kwargs={"organization_slug": self.project.organization.slug},
-            )
             response = self.client.get(
-                url,
+                self.url,
                 format="json",
                 data={
                     # Set the timeframe to where the second range has no transactions so all the counts/percentile are 0

+ 2 - 2
yarn.lock

@@ -3139,7 +3139,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-dom@^16 || ^17":
+"@types/react-dom@^16 || ^17", "@types/react-dom@^16.9.8":
   version "16.9.8"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
   integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
@@ -3214,7 +3214,7 @@
     "@types/prop-types" "*"
     csstype "^2.2.0"
 
-"@types/react@^16 || ^17":
+"@types/react@^16 || ^17", "@types/react@^16.9.49":
   version "16.9.49"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872"
   integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==