Browse Source

fix(discover) Fix project facet (#16584)

The project facet should use numeric ids as its 'value' so that clicking
on segments works. I was a bit too zealous in my previous cleanup work
and accidentally deleted the tests for the organization_events_facets
endpoint so those have been restored as well.
Mark Story 5 years ago
parent
commit
4addeab52e

+ 1 - 2
src/sentry/api/endpoints/organization_events_facets.py

@@ -51,9 +51,8 @@ class OrganizationEventsFacetsEndpoint(OrganizationEventsEndpointBase):
             projects = {p.id: p.slug for p in self.get_projects(request, organization)}
             for v in resp["project"]["topValues"]:
                 name = projects[v["value"]]
-                v.update({"name": name, "value": name})
+                v.update({"name": name})
 
-        # TODO(mark) Figure out how to keep the results ordered.
         return Response(resp.values())
 
     def _validate_project_ids(self, request, organization, params):

+ 406 - 0
tests/snuba/api/endpoints/test_organization_events_facets.py

@@ -0,0 +1,406 @@
+from __future__ import absolute_import
+
+from datetime import timedelta
+from django.utils import timezone
+from django.core.urlresolvers import reverse
+from uuid import uuid4
+
+from sentry.testutils import APITestCase, SnubaTestCase
+from sentry.testutils.helpers.datetime import before_now, iso_format
+
+
+class OrganizationEventsFacetsEndpointTest(SnubaTestCase, APITestCase):
+    feature_list = ("organizations:events-v2", "organizations:global-views")
+
+    def setUp(self):
+        super(OrganizationEventsFacetsEndpointTest, self).setUp()
+        self.min_ago = before_now(minutes=1).replace(microsecond=0)
+        self.day_ago = before_now(days=1).replace(microsecond=0)
+        self.login_as(user=self.user)
+        self.project = self.create_project()
+        self.project2 = self.create_project()
+        self.url = reverse(
+            "sentry-api-0-organization-events-facets",
+            kwargs={"organization_slug": self.project.organization.slug},
+        )
+        self.min_ago_iso = iso_format(self.min_ago)
+
+    def test_simple(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+            },
+            project_id=self.project2.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "two"},
+            },
+            project_id=self.project.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, format="json")
+
+        assert response.status_code == 200, response.content
+        expected = [
+            {"count": 2, "name": "one", "value": "one"},
+            {"count": 1, "name": "two", "value": "two"},
+        ]
+        self.assert_facet(response, "number", expected)
+
+    def test_with_message_query(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "how to make fast",
+                "tags": {"color": "green"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "Delet the Data",
+                "tags": {"color": "red"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "Data the Delet ",
+                "tags": {"color": "yellow"},
+            },
+            project_id=self.project2.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, {"query": "delet"}, format="json")
+
+        assert response.status_code == 200, response.content
+        expected = [
+            {"count": 1, "name": "yellow", "value": "yellow"},
+            {"count": 1, "name": "red", "value": "red"},
+        ]
+        self.assert_facet(response, "color", expected)
+
+    def test_with_condition(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "how to make fast",
+                "tags": {"color": "green"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "Delet the Data",
+                "tags": {"color": "red"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "message": "Data the Delet ",
+                "tags": {"color": "yellow"},
+            },
+            project_id=self.project2.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, {"query": "color:yellow"}, format="json")
+
+        assert response.status_code == 200, response.content
+        expected = [{"count": 1, "name": "yellow", "value": "yellow"}]
+        self.assert_facet(response, "color", expected)
+
+    def test_start_end(self):
+        two_days_ago = self.day_ago - timedelta(days=1)
+        hour_ago = self.min_ago - timedelta(hours=1)
+        two_hours_ago = hour_ago - timedelta(hours=1)
+
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(two_days_ago),
+                "tags": {"color": "red"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(hour_ago),
+                "tags": {"color": "red"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(two_hours_ago),
+                "tags": {"color": "red"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(timezone.now()),
+                "tags": {"color": "red"},
+            },
+            project_id=self.project2.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(
+                self.url,
+                {"start": iso_format(self.day_ago), "end": iso_format(self.min_ago)},
+                format="json",
+            )
+
+        assert response.status_code == 200, response.content
+        expected = [{"count": 2, "name": "red", "value": "red"}]
+        self.assert_facet(response, "color", expected)
+
+    def test_excluded_tag(self):
+        self.user = self.create_user()
+        self.user2 = self.create_user()
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(self.day_ago),
+                "message": "very bad",
+                "tags": {"sentry:user": self.user.email},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(self.day_ago),
+                "message": "very bad",
+                "tags": {"sentry:user": self.user2.email},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": iso_format(self.day_ago),
+                "message": "very bad",
+                "tags": {"sentry:user": self.user2.email},
+            },
+            project_id=self.project.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, format="json", data={"project": [self.project.id]})
+
+        assert response.status_code == 200, response.content
+        expected = [
+            {"count": 2, "name": self.user2.email, "value": self.user2.email},
+            {"count": 1, "name": self.user.email, "value": self.user.email},
+        ]
+        self.assert_facet(response, "user", expected)
+
+    def test_no_projects(self):
+        org = self.create_organization(owner=self.user)
+        url = reverse(
+            "sentry-api-0-organization-events-facets", kwargs={"organization_slug": org.slug}
+        )
+        with self.feature("organizations:events-v2"):
+            response = self.client.get(url, format="json")
+        assert response.status_code == 400, response.content
+        assert response.data == {"detail": "A valid project must be included."}
+
+    def test_multiple_projects_without_global_view(self):
+        self.store_event(data={"event_id": uuid4().hex}, project_id=self.project.id)
+        self.store_event(data={"event_id": uuid4().hex}, project_id=self.project2.id)
+
+        with self.feature("organizations:events-v2"):
+            response = self.client.get(self.url, format="json")
+        assert response.status_code == 400, response.content
+        assert response.data == {"detail": "You cannot view events from multiple projects."}
+
+    def test_project_selected(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "two"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+            },
+            project_id=self.project2.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, {"project": [self.project.id]}, format="json")
+
+        assert response.status_code == 200, response.content
+        expected = [{"name": "two", "value": "two", "count": 1}]
+        self.assert_facet(response, "number", expected)
+
+    def test_project_key(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"color": "green"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+            },
+            project_id=self.project2.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"color": "green"},
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": {"color": "red"}},
+            project_id=self.project.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, format="json")
+
+        assert response.status_code == 200, response.content
+        expected = [
+            {"count": 3, "name": self.project.slug, "value": self.project.id},
+            {"count": 1, "name": self.project2.slug, "value": self.project2.id},
+        ]
+        self.assert_facet(response, "project", expected)
+
+    def test_malformed_query(self):
+        self.store_event(data={"event_id": uuid4().hex}, project_id=self.project.id)
+        self.store_event(data={"event_id": uuid4().hex}, project_id=self.project2.id)
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, format="json", data={"query": "\n\n\n\n"})
+        assert response.status_code == 400, response.content
+        assert response.data == {
+            "detail": "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes."
+        }
+
+    def test_environment(self):
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+                "environment": "staging",
+            },
+            project_id=self.project2.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "one"},
+                "environment": "production",
+            },
+            project_id=self.project.id,
+        )
+        self.store_event(
+            data={
+                "event_id": uuid4().hex,
+                "timestamp": self.min_ago_iso,
+                "tags": {"number": "two"},
+            },
+            project_id=self.project.id,
+        )
+
+        with self.feature(self.feature_list):
+            response = self.client.get(self.url, format="json")
+
+            assert response.status_code == 200, response.content
+            expected = [
+                {"count": 1, "name": "production", "value": "production"},
+                {"count": 1, "name": "staging", "value": "staging"},
+                {"count": 1, "name": None, "value": None},
+            ]
+            self.assert_facet(response, "environment", expected)
+
+        with self.feature(self.feature_list):
+            # query by an environment
+            response = self.client.get(self.url, {"environment": "staging"}, format="json")
+
+            assert response.status_code == 200, response.content
+            expected = [{"count": 1, "name": "staging", "value": "staging"}]
+            self.assert_facet(response, "environment", expected)
+
+        with self.feature(self.feature_list):
+            # query by multiple environments
+            response = self.client.get(
+                self.url, {"environment": ["staging", "production"]}, format="json"
+            )
+
+            assert response.status_code == 200, response.content
+
+            expected = [
+                {"count": 1, "name": "production", "value": "production"},
+                {"count": 1, "name": "staging", "value": "staging"},
+            ]
+            self.assert_facet(response, "environment", expected)
+
+        with self.feature(self.feature_list):
+            # query by multiple environments, including the "no environment" environment
+            response = self.client.get(
+                self.url, {"environment": ["staging", "production", ""]}, format="json"
+            )
+            assert response.status_code == 200, response.content
+            expected = [
+                {"count": 1, "name": "production", "value": "production"},
+                {"count": 1, "name": "staging", "value": "staging"},
+                {"count": 1, "name": None, "value": None},
+            ]
+            self.assert_facet(response, "environment", expected)
+
+    def assert_facet(self, response, key, expected):
+        actual = None
+        for facet in response.data:
+            if facet["key"] == key:
+                actual = facet
+                break
+        assert actual is not None, "Could not find {} facet in {}".format(key, response.data)
+        assert "topValues" in actual
+        assert sorted(expected) == sorted(actual["topValues"])