123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- from datetime import timedelta
- from django.utils import timezone
- from freezegun import freeze_time
- from sentry.issues.grouptype import ProfileFileIOGroupType
- from sentry.testutils import APITestCase, SnubaTestCase
- from sentry.testutils.cases import PerformanceIssueTestCase
- from sentry.testutils.helpers import parse_link_header
- from sentry.testutils.helpers.datetime import before_now, iso_format
- from sentry.testutils.silo import region_silo_test
- from tests.sentry.issues.test_utils import SearchIssueTestMixin
- @region_silo_test
- class GroupEventsTest(APITestCase, SnubaTestCase, SearchIssueTestMixin, PerformanceIssueTestCase):
- def setUp(self):
- super().setUp()
- self.min_ago = before_now(minutes=1)
- self.two_min_ago = before_now(minutes=2)
- self.features = {}
- def do_request(self, url):
- with self.feature(self.features):
- return self.client.get(url, format="json")
- def _parse_links(self, header):
- # links come in {url: {...attrs}}, but we need {rel: {...attrs}}
- links = {}
- for url, attrs in parse_link_header(header).items():
- links[attrs["rel"]] = attrs
- attrs["href"] = url
- return links
- def test_simple(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 2
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
- # Should default to full=false which does not include context property
- assert "context" not in response.data[0]
- assert "context" not in response.data[1]
- def test_full_false(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/?full=false"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
- # Simplified response does not have context property
- assert "context" not in response.data[0]
- assert "context" not in response.data[1]
- def test_full_true(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/?full=true"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- # Full response has context property
- assert "context" in response.data[0]
- assert "context" in response.data[1]
- def test_tags(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["1"],
- "tags": {"foo": "baz", "bar": "buz"},
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["1"],
- "tags": {"bar": "biz"},
- "timestamp": iso_format(before_now(seconds=61)),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/"
- response = self.do_request(url + "?query=foo:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == str(event_1.event_id)
- response = self.do_request(url + "?query=!foo:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == str(event_2.event_id)
- response = self.do_request(url + "?query=bar:biz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == str(event_2.event_id)
- response = self.do_request(url + "?query=bar:biz%20foo:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 0
- response = self.do_request(url + "?query=bar:buz%20foo:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == str(event_1.event_id)
- response = self.do_request(url + "?query=bar:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 0
- response = self.do_request(url + "?query=a:b")
- assert response.status_code == 200, response.content
- assert len(response.data) == 0
- response = self.do_request(url + "?query=bar:b")
- assert response.status_code == 200, response.content
- assert len(response.data) == 0
- response = self.do_request(url + "?query=bar:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 0
- response = self.do_request(url + "?query=!bar:baz")
- assert response.status_code == 200, response.content
- assert len(response.data) == 2
- assert {e["eventID"] for e in response.data} == {event_1.event_id, event_2.event_id}
- def test_search_event_by_id(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["group-1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["group-1"],
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/?query={event_1.event_id}"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == event_1.event_id
- def test_search_event_by_message(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["group-1"],
- "message": "foo bar hello world",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- group = event_1.group
- event_2 = self.store_event(
- data={
- "event_id": "b" * 32,
- "fingerprint": ["group-1"],
- "message": "this bar hello world",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- assert group == event_2.group
- query_1 = "foo"
- query_2 = "hello+world"
- # Single Word Query
- url = f"/api/0/issues/{group.id}/events/?query={query_1}"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == event_1.event_id
- # Multiple Word Query
- url = f"/api/0/issues/{group.id}/events/?query={query_2}"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 2
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
- def test_search_by_release(self):
- self.login_as(user=self.user)
- self.create_release(self.project, version="first-release")
- event_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "fingerprint": ["group-1"],
- "timestamp": iso_format(self.min_ago),
- "release": "first-release",
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event_1.group.id}/events/?query=release:latest"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == event_1.event_id
- def test_environment(self):
- self.login_as(user=self.user)
- events = {}
- for name in ["production", "development"]:
- events[name] = self.store_event(
- data={
- "fingerprint": ["put-me-in-group1"],
- "timestamp": iso_format(self.min_ago),
- "environment": name,
- },
- project_id=self.project.id,
- )
- # Asserts that all are in the same group
- (group_id,) = {e.group.id for e in events.values()}
- url = f"/api/0/issues/{group_id}/events/"
- response = self.do_request(url + "?environment=production")
- assert response.status_code == 200, response.content
- assert set(map(lambda x: x["eventID"], response.data)) == {
- str(events["production"].event_id)
- }
- response = self.client.get(
- url, data={"environment": ["production", "development"]}, format="json"
- )
- assert response.status_code == 200, response.content
- assert set(map(lambda x: x["eventID"], response.data)) == {
- str(event.event_id) for event in events.values()
- }
- response = self.do_request(url + "?environment=invalid")
- assert response.status_code == 200, response.content
- assert response.data == []
- response = self.client.get(
- url + "?environment=production&query=environment:development", format="json"
- )
- assert response.status_code == 200, response.content
- assert response.data == []
- def test_filters_based_on_retention(self):
- self.login_as(user=self.user)
- self.store_event(
- data={"fingerprint": ["group_1"], "timestamp": iso_format(before_now(days=2))},
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={"fingerprint": ["group_1"], "timestamp": iso_format(self.min_ago)},
- project_id=self.project.id,
- )
- group = event_2.group
- with self.options({"system.event-retention-days": 1}):
- response = self.client.get(f"/api/0/issues/{group.id}/events/")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted([str(event_2.event_id)])
- def test_search_event_has_tags(self):
- self.login_as(user=self.user)
- event = self.store_event(
- data={
- "timestamp": iso_format(self.min_ago),
- "message": "foo",
- "tags": {"logger": "python"},
- },
- project_id=self.project.id,
- )
- response = self.client.get(f"/api/0/issues/{event.group.id}/events/")
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert {"key": "logger", "value": "python"} in response.data[0]["tags"]
- @freeze_time()
- def test_date_filters(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={"timestamp": iso_format(before_now(days=5)), "fingerprint": ["group-1"]},
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={"timestamp": iso_format(before_now(days=1)), "fingerprint": ["group-1"]},
- project_id=self.project.id,
- )
- group = event_1.group
- assert group == event_2.group
- response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "6d"})
- assert response.status_code == 200, response.content
- assert len(response.data) == 2
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
- response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "2d"})
- assert response.status_code == 200, response.content
- assert len(response.data) == 1
- assert response.data[0]["eventID"] == str(event_2.event_id)
- def test_invalid_period(self):
- self.login_as(user=self.user)
- first_seen = timezone.now() - timedelta(days=5)
- group = self.create_group(first_seen=first_seen)
- response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "lol"})
- assert response.status_code == 400
- def test_invalid_query(self):
- self.login_as(user=self.user)
- first_seen = timezone.now() - timedelta(days=5)
- group = self.create_group(first_seen=first_seen)
- response = self.client.get(
- f"/api/0/issues/{group.id}/events/",
- data={"statsPeriod": "7d", "query": "foo(bar"},
- )
- assert response.status_code == 400
- def test_multiple_group(self):
- self.login_as(user=self.user)
- event_1 = self.store_event(
- data={
- "fingerprint": ["group_1"],
- "event_id": "a" * 32,
- "message": "foo",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- event_2 = self.store_event(
- data={
- "fingerprint": ["group_2"],
- "event_id": "b" * 32,
- "message": "group2",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- for event in (event_1, event_2):
- url = f"/api/0/issues/{event.group.id}/events/"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert len(response.data) == 1, response.data
- assert list(map(lambda x: x["eventID"], response.data)) == [str(event.event_id)]
- def test_pagination(self):
- self.login_as(user=self.user)
- for _ in range(2):
- event = self.store_event(
- data={
- "fingerprint": ["group_1"],
- "event_id": "a" * 32,
- "message": "foo",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event.group.id}/events/?per_page=1"
- response = self.do_request(url)
- links = self._parse_links(response["Link"])
- assert response.status_code == 200, response.content
- assert links["previous"]["results"] == "false"
- assert links["next"]["results"] == "true"
- assert len(response.data) == 1
- def test_orderby(self):
- self.login_as(user=self.user)
- event = self.store_event(
- data={
- "fingerprint": ["group_1"],
- "event_id": "a" * 32,
- "message": "foo",
- "timestamp": iso_format(self.min_ago),
- },
- project_id=self.project.id,
- )
- event = self.store_event(
- data={
- "fingerprint": ["group_1"],
- "event_id": "b" * 32,
- "message": "foo",
- "timestamp": iso_format(self.two_min_ago),
- },
- project_id=self.project.id,
- )
- url = f"/api/0/issues/{event.group.id}/events/"
- response = self.do_request(url)
- assert len(response.data) == 2
- assert response.data[0]["eventID"] == "a" * 32
- assert response.data[1]["eventID"] == "b" * 32
- def test_perf_issue(self):
- event_1 = self.create_performance_issue()
- event_2 = self.create_performance_issue()
- self.login_as(user=self.user)
- url = f"/api/0/issues/{event_1.group.id}/events/"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
- def test_generic_issue(self):
- event_1, _, group_info = self.store_search_issue(
- self.project.id,
- self.user.id,
- [f"{ProfileFileIOGroupType.type_id}-group1"],
- "prod",
- before_now(hours=1).replace(tzinfo=timezone.utc),
- )
- event_2, _, _ = self.store_search_issue(
- self.project.id,
- self.user.id,
- [f"{ProfileFileIOGroupType.type_id}-group1"],
- "prod",
- before_now(hours=1).replace(tzinfo=timezone.utc),
- )
- self.login_as(user=self.user)
- url = f"/api/0/issues/{group_info.group.id}/events/"
- response = self.do_request(url)
- assert response.status_code == 200, response.content
- assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
- [str(event_1.event_id), str(event_2.event_id)]
- )
|