mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
955 lines
35 KiB
Python
955 lines
35 KiB
Python
from datetime import timedelta, timezone
|
|
from unittest import mock
|
|
from uuid import uuid4
|
|
|
|
import requests
|
|
from django.urls import reverse
|
|
from django.utils import timezone as django_timezone
|
|
from rest_framework.exceptions import ParseError
|
|
|
|
from sentry.testutils.cases import APITestCase, SnubaTestCase
|
|
from sentry.testutils.helpers.datetime import before_now, iso_format
|
|
from sentry.testutils.silo import region_silo_test
|
|
|
|
|
|
@region_silo_test
|
|
class OrganizationEventsFacetsEndpointTest(SnubaTestCase, APITestCase):
|
|
def setUp(self):
|
|
super().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)
|
|
self.features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
|
|
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, f"Could not find {key} facet in {response.data}"
|
|
assert "topValues" in actual
|
|
key = lambda row: row["name"] if row["name"] is not None else ""
|
|
assert sorted(expected, key=key) == sorted(actual["topValues"], key=key)
|
|
|
|
def test_performance_view_feature(self):
|
|
self.features.update(
|
|
{
|
|
"organizations:discover-basic": False,
|
|
"organizations:performance-view": True,
|
|
}
|
|
)
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, data={"project": self.project.id}, format="json")
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
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.features):
|
|
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_order_by(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"alpha": "one"},
|
|
"environment": "aaaa",
|
|
},
|
|
project_id=self.project2.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"beta": "one"},
|
|
"environment": "bbbb",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"charlie": "two"},
|
|
"environment": "cccc",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json")
|
|
|
|
assert response.status_code == 200, response.content
|
|
keys = [facet["key"] for facet in response.data]
|
|
assert ["alpha", "beta", "charlie", "environment", "level", "project"] == keys
|
|
|
|
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.features):
|
|
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.features):
|
|
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_with_conditional_filter(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.features):
|
|
response = self.client.get(
|
|
self.url, {"query": "color:yellow OR color:red"}, 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_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(django_timezone.now()),
|
|
"tags": {"color": "red"},
|
|
},
|
|
project_id=self.project2.id,
|
|
)
|
|
|
|
with self.feature(self.features):
|
|
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.features):
|
|
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:discover-basic"):
|
|
response = self.client.get(url, format="json")
|
|
assert response.status_code == 200, response.content
|
|
assert response.data == []
|
|
|
|
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:discover-basic"):
|
|
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.features):
|
|
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_filtered(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.features):
|
|
response = self.client.get(
|
|
self.url, {"query": f"project:{self.project.slug}"}, 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.features):
|
|
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_project_key_with_project_tag(self):
|
|
self.organization.flags.allow_joinleave = False
|
|
self.organization.save()
|
|
|
|
member_user = self.create_user()
|
|
team = self.create_team(members=[member_user])
|
|
private_project1 = self.create_project(organization=self.organization, teams=[team])
|
|
private_project2 = self.create_project(organization=self.organization, teams=[team])
|
|
self.login_as(member_user)
|
|
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"color": "green", "project": "%d" % private_project1.id},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"number": "one", "project": "%d" % private_project1.id},
|
|
},
|
|
project_id=private_project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"color": "green"},
|
|
},
|
|
project_id=private_project1.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": {"color": "red"}},
|
|
project_id=private_project2.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": {"color": "red"}},
|
|
project_id=private_project2.id,
|
|
)
|
|
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json")
|
|
|
|
assert response.status_code == 200, response.content
|
|
expected = [
|
|
{"count": 2, "name": private_project1.slug, "value": private_project1.id},
|
|
{"count": 2, "name": private_project2.slug, "value": private_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.features):
|
|
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"].endswith(
|
|
"(column 1). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
|
|
)
|
|
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_handling_snuba_errors(self, mock_query):
|
|
mock_query.side_effect = ParseError("test")
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json")
|
|
|
|
assert response.status_code == 400, response.content
|
|
|
|
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.features):
|
|
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.features):
|
|
# 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.features):
|
|
# 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.features):
|
|
# 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 test_out_of_retention(self):
|
|
with self.options({"system.event-retention-days": 10}):
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={
|
|
"start": iso_format(before_now(days=20)),
|
|
"end": iso_format(before_now(days=15)),
|
|
},
|
|
)
|
|
assert response.status_code == 400
|
|
|
|
@mock.patch("sentry.utils.snuba.quantize_time")
|
|
def test_quantize_dates(self, mock_quantize):
|
|
mock_quantize.return_value = before_now(days=1).replace(tzinfo=timezone.utc)
|
|
with self.feature("organizations:discover-basic"):
|
|
# Don't quantize short time periods
|
|
self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]},
|
|
)
|
|
# Don't quantize absolute date periods
|
|
self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={
|
|
"start": iso_format(before_now(days=20)),
|
|
"end": iso_format(before_now(days=15)),
|
|
"query": "",
|
|
"field": ["id", "timestamp"],
|
|
},
|
|
)
|
|
|
|
assert len(mock_quantize.mock_calls) == 0
|
|
|
|
# Quantize long date periods
|
|
self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""},
|
|
)
|
|
|
|
assert len(mock_quantize.mock_calls) == 2
|
|
|
|
def test_device_class(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"device.class": "1"},
|
|
},
|
|
project_id=self.project2.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"device.class": "2"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": uuid4().hex,
|
|
"timestamp": self.min_ago_iso,
|
|
"tags": {"device.class": "3"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json")
|
|
|
|
assert response.status_code == 200, response.content
|
|
expected = [
|
|
{"count": 1, "name": "high", "value": "high"},
|
|
{"count": 1, "name": "medium", "value": "medium"},
|
|
{"count": 1, "name": "low", "value": "low"},
|
|
]
|
|
self.assert_facet(response, "device.class", expected)
|
|
|
|
def test_with_cursor_parameter(self):
|
|
test_project = self.create_project()
|
|
test_tags = {
|
|
"a": "one",
|
|
"b": "two",
|
|
"c": "three",
|
|
"d": "four",
|
|
"e": "five",
|
|
"f": "six",
|
|
"g": "seven",
|
|
"h": "eight",
|
|
"i": "nine",
|
|
"j": "ten",
|
|
"k": "eleven",
|
|
}
|
|
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": test_tags},
|
|
project_id=test_project.id,
|
|
)
|
|
|
|
# Test the default query fetches the first 10 results
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json", data={"project": test_project.id})
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more results to be fetched
|
|
assert links[1]["cursor"] == "0:10:0"
|
|
assert len(response.data) == 10
|
|
|
|
# Loop over the first 10 tags to ensure they're in the results
|
|
for tag_key in list(test_tags.keys())[:10]:
|
|
expected = [
|
|
{"count": 1, "name": test_tags[tag_key], "value": test_tags[tag_key]},
|
|
]
|
|
self.assert_facet(response, tag_key, expected)
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url, format="json", data={"project": test_project.id, "cursor": "0:10:0"}
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "false" # There should be no more tags to fetch
|
|
assert len(response.data) == 2
|
|
expected = [
|
|
{"count": 1, "name": "eleven", "value": "eleven"},
|
|
]
|
|
self.assert_facet(response, "k", expected)
|
|
expected = [
|
|
{"count": 1, "name": "error", "value": "error"},
|
|
]
|
|
self.assert_facet(response, "level", expected)
|
|
|
|
def test_projects_data_are_injected_on_first_page_with_multiple_projects_selected(self):
|
|
test_project = self.create_project()
|
|
test_project2 = self.create_project()
|
|
test_tags = {
|
|
"a": "one",
|
|
"b": "two",
|
|
"c": "three",
|
|
"d": "four",
|
|
"e": "five",
|
|
"f": "six",
|
|
"g": "seven",
|
|
"h": "eight",
|
|
"i": "nine",
|
|
"j": "ten",
|
|
"k": "eleven",
|
|
}
|
|
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": test_tags},
|
|
project_id=test_project.id,
|
|
)
|
|
|
|
# Test the default query fetches the first 10 results
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url, format="json", data={"project": [test_project.id, test_project2.id]}
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more results to be fetched
|
|
assert links[1]["cursor"] == "0:10:0"
|
|
assert len(response.data) == 10
|
|
|
|
# Project is injected into the first page
|
|
expected = [
|
|
{"count": 1, "name": test_project.slug, "value": test_project.id},
|
|
]
|
|
self.assert_facet(response, "project", expected)
|
|
|
|
# Loop over the first 9 tags to ensure they're in the results
|
|
# in this case, the 10th key is "projects" since it was injected
|
|
for tag_key in list(test_tags.keys())[:9]:
|
|
expected = [
|
|
{"count": 1, "name": test_tags[tag_key], "value": test_tags[tag_key]},
|
|
]
|
|
self.assert_facet(response, tag_key, expected)
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"project": [test_project.id, test_project2.id], "cursor": "0:10:0"},
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "false" # There should be no more tags to fetch
|
|
assert len(response.data) == 3
|
|
expected = [
|
|
{"count": 1, "name": "ten", "value": "ten"},
|
|
]
|
|
self.assert_facet(response, "j", expected)
|
|
expected = [
|
|
{"count": 1, "name": "eleven", "value": "eleven"},
|
|
]
|
|
self.assert_facet(response, "k", expected)
|
|
expected = [
|
|
{"count": 1, "name": "error", "value": "error"},
|
|
]
|
|
self.assert_facet(response, "level", expected)
|
|
|
|
def test_multiple_pages_with_single_project(self):
|
|
test_project = self.create_project()
|
|
test_tags = {str(i): str(i) for i in range(22)}
|
|
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": test_tags},
|
|
project_id=test_project.id,
|
|
)
|
|
|
|
# Test the default query fetches the first 10 results
|
|
with self.feature(self.features):
|
|
response = self.client.get(self.url, format="json", data={"project": test_project.id})
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more results to be fetched
|
|
assert links[1]["cursor"] == "0:10:0"
|
|
assert len(response.data) == 10
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"project": test_project.id, "cursor": links[1]["cursor"]},
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more tags to fetch
|
|
assert len(response.data) == 10
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"project": test_project.id, "cursor": links[1]["cursor"]},
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "false" # There should be no more tags to fetch
|
|
assert len(response.data) == 3
|
|
|
|
def test_multiple_pages_with_multiple_projects(self):
|
|
test_project = self.create_project()
|
|
test_project2 = self.create_project()
|
|
test_tags = {str(i): str(i) for i in range(22)} # At least 3 pages worth of information
|
|
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": test_tags},
|
|
project_id=test_project.id,
|
|
)
|
|
|
|
# Test the default query fetches the first 10 results
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url, format="json", data={"project": [test_project.id, test_project2.id]}
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more results to be fetched
|
|
assert links[1]["cursor"] == "0:10:0"
|
|
assert len(response.data) == 10
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"project": [test_project.id, test_project2.id], "cursor": links[1]["cursor"]},
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "true" # There are more tags to fetch
|
|
assert len(response.data) == 10
|
|
|
|
# Get the next page
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url,
|
|
format="json",
|
|
data={"project": [test_project.id, test_project2.id], "cursor": links[1]["cursor"]},
|
|
)
|
|
links = requests.utils.parse_header_links(
|
|
response.get("link", "").rstrip(">").replace(">,<", ",<")
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert links[1]["results"] == "false" # There should be no more tags to fetch
|
|
assert len(response.data) == 4 # 4 because projects and levels were added to the base 22
|
|
|
|
def test_get_all_tags(self):
|
|
test_project = self.create_project()
|
|
test_tags = {str(i): str(i) for i in range(22)}
|
|
|
|
self.store_event(
|
|
data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": test_tags},
|
|
project_id=test_project.id,
|
|
)
|
|
|
|
# Test the default query fetches the first 10 results
|
|
with self.feature(self.features):
|
|
response = self.client.get(
|
|
self.url, format="json", data={"project": test_project.id, "includeAll": True}
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data) == 23
|
|
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_dont_turbo_trace_queries(self, mock_run):
|
|
# Need to create more projects so we'll even want to turbo in the first place
|
|
for _ in range(3):
|
|
self.create_project()
|
|
with self.feature(self.features):
|
|
self.client.get(self.url, {"query": f"trace:{'a' * 32}"}, format="json")
|
|
|
|
mock_run.assert_called_once
|
|
assert not mock_run.mock_calls[0].args[0].flags.turbo
|
|
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_use_turbo_without_trace(self, mock_run):
|
|
# Need to create more projects so we'll even want to turbo in the first place
|
|
for _ in range(3):
|
|
self.create_project()
|
|
with self.feature(self.features):
|
|
self.client.get(self.url, format="json")
|
|
|
|
mock_run.assert_called_once
|
|
assert mock_run.mock_calls[0].args[0].flags.turbo
|