test_backend.py 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005
  1. import uuid
  2. from datetime import datetime, timedelta
  3. from hashlib import md5
  4. import pytest
  5. import pytz
  6. from django.utils import timezone
  7. from sentry import options
  8. from sentry.api.issue_search import convert_query_values, issue_search_config, parse_search_query
  9. from sentry.exceptions import InvalidSearchQuery
  10. from sentry.models import (
  11. Environment,
  12. Group,
  13. GroupAssignee,
  14. GroupBookmark,
  15. GroupEnvironment,
  16. GroupStatus,
  17. GroupSubscription,
  18. Integration,
  19. )
  20. from sentry.models.groupinbox import GroupInboxReason, add_group_to_inbox
  21. from sentry.models.groupowner import GroupOwner
  22. from sentry.search.snuba.backend import EventsDatasetSnubaSearchBackend
  23. from sentry.testutils import SnubaTestCase, TestCase, xfail_if_not_postgres
  24. from sentry.testutils.helpers.datetime import before_now, iso_format
  25. from sentry.utils.compat import mock
  26. from sentry.utils.snuba import SENTRY_SNUBA_MAP, Dataset, SnubaError
  27. def date_to_query_format(date):
  28. return date.strftime("%Y-%m-%dT%H:%M:%S")
  29. class EventsSnubaSearchTest(TestCase, SnubaTestCase):
  30. @property
  31. def backend(self):
  32. return EventsDatasetSnubaSearchBackend()
  33. def setUp(self):
  34. super().setUp()
  35. self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc)
  36. event1_timestamp = iso_format(self.base_datetime - timedelta(days=21))
  37. self.event1 = self.store_event(
  38. data={
  39. "fingerprint": ["put-me-in-group1"],
  40. "event_id": "a" * 32,
  41. "message": "foo. Also, this message is intended to be greater than 256 characters so that we can put some unique string identifier after that point in the string. The purpose of this is in order to verify we are using snuba to search messages instead of Postgres (postgres truncates at 256 characters and clickhouse does not). santryrox.",
  42. "environment": "production",
  43. "tags": {"server": "example.com", "sentry:user": "event1@example.com"},
  44. "timestamp": event1_timestamp,
  45. "stacktrace": {"frames": [{"module": "group1"}]},
  46. },
  47. project_id=self.project.id,
  48. )
  49. self.event3 = self.store_event(
  50. data={
  51. "fingerprint": ["put-me-in-group1"],
  52. "event_id": "c" * 32,
  53. "message": "group1",
  54. "environment": "production",
  55. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  56. "timestamp": iso_format(self.base_datetime),
  57. "stacktrace": {"frames": [{"module": "group1"}]},
  58. },
  59. project_id=self.project.id,
  60. )
  61. self.group1 = Group.objects.get(id=self.event1.group.id)
  62. assert self.group1.id == self.event1.group.id
  63. assert self.group1.id == self.event3.group.id
  64. assert self.group1.first_seen == self.event1.datetime
  65. assert self.group1.last_seen == self.event3.datetime
  66. self.group1.times_seen = 5
  67. self.group1.status = GroupStatus.UNRESOLVED
  68. self.group1.save()
  69. self.store_group(self.group1)
  70. self.event2 = self.store_event(
  71. data={
  72. "fingerprint": ["put-me-in-group2"],
  73. "event_id": "b" * 32,
  74. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  75. "message": "bar",
  76. "stacktrace": {"frames": [{"module": "group2"}]},
  77. "environment": "staging",
  78. "tags": {
  79. "server": "example.com",
  80. "url": "http://example.com",
  81. "sentry:user": "event2@example.com",
  82. },
  83. },
  84. project_id=self.project.id,
  85. )
  86. self.group2 = Group.objects.get(id=self.event2.group.id)
  87. assert self.group2.id == self.event2.group.id
  88. assert self.group2.first_seen == self.group2.last_seen == self.event2.datetime
  89. self.group2.status = GroupStatus.RESOLVED
  90. self.group2.times_seen = 10
  91. self.group2.save()
  92. self.store_group(self.group2)
  93. GroupBookmark.objects.create(user=self.user, group=self.group2, project=self.group2.project)
  94. GroupAssignee.objects.create(user=self.user, group=self.group2, project=self.group2.project)
  95. GroupSubscription.objects.create(
  96. user=self.user, group=self.group1, project=self.group1.project, is_active=True
  97. )
  98. GroupSubscription.objects.create(
  99. user=self.user, group=self.group2, project=self.group2.project, is_active=False
  100. )
  101. self.environments = {
  102. "production": self.event1.get_environment(),
  103. "staging": self.event2.get_environment(),
  104. }
  105. def store_event(self, data, *args, **kwargs):
  106. event = super().store_event(data, *args, **kwargs)
  107. environment_name = data.get("environment")
  108. if environment_name:
  109. GroupEnvironment.objects.filter(
  110. group_id=event.group_id,
  111. environment__name=environment_name,
  112. first_seen__gt=event.datetime,
  113. ).update(first_seen=event.datetime)
  114. return event
  115. def set_up_multi_project(self):
  116. self.project2 = self.create_project(organization=self.project.organization)
  117. self.event_p2 = self.store_event(
  118. data={
  119. "event_id": "a" * 32,
  120. "fingerprint": ["put-me-in-groupP2"],
  121. "timestamp": iso_format(self.base_datetime - timedelta(days=21)),
  122. "message": "foo",
  123. "stacktrace": {"frames": [{"module": "group_p2"}]},
  124. "tags": {"server": "example.com"},
  125. "environment": "production",
  126. },
  127. project_id=self.project2.id,
  128. )
  129. self.group_p2 = Group.objects.get(id=self.event_p2.group.id)
  130. self.group_p2.times_seen = 6
  131. self.group_p2.last_seen = self.base_datetime - timedelta(days=1)
  132. self.group_p2.save()
  133. self.store_group(self.group_p2)
  134. def create_group_with_integration_external_issue(self, environment="production"):
  135. event = self.store_event(
  136. data={
  137. "fingerprint": ["linked_group1"],
  138. "event_id": uuid.uuid4().hex,
  139. "timestamp": iso_format(self.base_datetime),
  140. "environment": environment,
  141. },
  142. project_id=self.project.id,
  143. )
  144. integration = Integration.objects.create(provider="example", name="Example")
  145. integration.add_organization(event.group.organization, self.user)
  146. self.create_integration_external_issue(
  147. group=event.group,
  148. integration=integration,
  149. key="APP-123",
  150. )
  151. return event.group
  152. def create_group_with_platform_external_issue(self, environment="production"):
  153. event = self.store_event(
  154. data={
  155. "fingerprint": ["linked_group2"],
  156. "event_id": uuid.uuid4().hex,
  157. "timestamp": iso_format(self.base_datetime),
  158. "environment": environment,
  159. },
  160. project_id=self.project.id,
  161. )
  162. self.create_platform_external_issue(
  163. group=event.group,
  164. service_type="sentry-app",
  165. display_name="App#issue-1",
  166. web_url="https://example.com/app/issues/1",
  167. )
  168. return event.group
  169. def build_search_filter(self, query, projects=None, user=None, environments=None):
  170. user = user if user is not None else self.user
  171. projects = projects if projects is not None else [self.project]
  172. return convert_query_values(parse_search_query(query), projects, user, environments)
  173. def make_query(
  174. self,
  175. projects=None,
  176. search_filter_query=None,
  177. environments=None,
  178. sort_by="date",
  179. limit=None,
  180. count_hits=False,
  181. date_from=None,
  182. date_to=None,
  183. ):
  184. search_filters = []
  185. projects = projects if projects is not None else [self.project]
  186. if search_filter_query is not None:
  187. search_filters = self.build_search_filter(
  188. search_filter_query, projects, environments=environments
  189. )
  190. kwargs = {}
  191. if limit is not None:
  192. kwargs["limit"] = limit
  193. return self.backend.query(
  194. projects,
  195. search_filters=search_filters,
  196. environments=environments,
  197. count_hits=count_hits,
  198. sort_by=sort_by,
  199. date_from=date_from,
  200. date_to=date_to,
  201. **kwargs,
  202. )
  203. def run_test_query_in_syntax(
  204. self, query, expected_groups, expected_negative_groups=None, environments=None
  205. ):
  206. results = self.make_query(search_filter_query=query, environments=environments)
  207. sort_key = lambda result: result.id
  208. print("results", results.results)
  209. assert sorted(results, key=sort_key) == sorted(expected_groups, key=sort_key)
  210. if expected_negative_groups is not None:
  211. results = self.make_query(search_filter_query=f"!{query}")
  212. assert sorted(results, key=sort_key) == sorted(expected_negative_groups, key=sort_key)
  213. def test_query(self):
  214. results = self.make_query(search_filter_query="foo")
  215. assert set(results) == {self.group1}
  216. results = self.make_query(search_filter_query="bar")
  217. assert set(results) == {self.group2}
  218. def test_query_multi_project(self):
  219. self.set_up_multi_project()
  220. results = self.make_query([self.project, self.project2], search_filter_query="foo")
  221. assert set(results) == {self.group1, self.group_p2}
  222. def test_query_with_environment(self):
  223. results = self.make_query(
  224. environments=[self.environments["production"]], search_filter_query="foo"
  225. )
  226. assert set(results) == {self.group1}
  227. results = self.make_query(
  228. environments=[self.environments["production"]], search_filter_query="bar"
  229. )
  230. assert set(results) == set()
  231. results = self.make_query(
  232. environments=[self.environments["staging"]], search_filter_query="bar"
  233. )
  234. assert set(results) == {self.group2}
  235. def test_query_for_text_in_long_message(self):
  236. results = self.make_query(
  237. [self.project],
  238. environments=[self.environments["production"]],
  239. search_filter_query="santryrox",
  240. )
  241. assert set(results) == {self.group1}
  242. def test_multi_environments(self):
  243. self.set_up_multi_project()
  244. results = self.make_query(
  245. [self.project, self.project2],
  246. environments=[self.environments["production"], self.environments["staging"]],
  247. )
  248. assert set(results) == {self.group1, self.group2, self.group_p2}
  249. def test_query_with_environment_multi_project(self):
  250. self.set_up_multi_project()
  251. results = self.make_query(
  252. [self.project, self.project2],
  253. environments=[self.environments["production"]],
  254. search_filter_query="foo",
  255. )
  256. assert set(results) == {self.group1, self.group_p2}
  257. results = self.make_query(
  258. [self.project, self.project2],
  259. environments=[self.environments["production"]],
  260. search_filter_query="bar",
  261. )
  262. assert set(results) == set()
  263. def test_sort(self):
  264. results = self.make_query(sort_by="date")
  265. assert list(results) == [self.group1, self.group2]
  266. results = self.make_query(sort_by="new")
  267. assert list(results) == [self.group2, self.group1]
  268. results = self.make_query(sort_by="freq")
  269. assert list(results) == [self.group1, self.group2]
  270. results = self.make_query(sort_by="priority")
  271. assert list(results) == [self.group1, self.group2]
  272. results = self.make_query(sort_by="user")
  273. assert list(results) == [self.group1, self.group2]
  274. def test_sort_with_environment(self):
  275. for dt in [
  276. self.group1.first_seen + timedelta(days=1),
  277. self.group1.first_seen + timedelta(days=2),
  278. self.group1.last_seen + timedelta(days=1),
  279. ]:
  280. self.store_event(
  281. data={
  282. "fingerprint": ["put-me-in-group2"],
  283. "timestamp": iso_format(dt),
  284. "stacktrace": {"frames": [{"module": "group2"}]},
  285. "environment": "production",
  286. "message": "group2",
  287. },
  288. project_id=self.project.id,
  289. )
  290. results = self.make_query(environments=[self.environments["production"]], sort_by="date")
  291. assert list(results) == [self.group2, self.group1]
  292. results = self.make_query(environments=[self.environments["production"]], sort_by="new")
  293. assert list(results) == [self.group2, self.group1]
  294. results = self.make_query(environments=[self.environments["production"]], sort_by="freq")
  295. assert list(results) == [self.group2, self.group1]
  296. results = self.make_query(
  297. environments=[self.environments["production"]], sort_by="priority"
  298. )
  299. assert list(results) == [self.group2, self.group1]
  300. results = self.make_query(environments=[self.environments["production"]], sort_by="user")
  301. assert list(results) == [self.group1, self.group2]
  302. def test_status(self):
  303. results = self.make_query(search_filter_query="is:unresolved")
  304. assert set(results) == {self.group1}
  305. results = self.make_query(search_filter_query="is:resolved")
  306. assert set(results) == {self.group2}
  307. event_3 = self.store_event(
  308. data={
  309. "fingerprint": ["put-me-in-group3"],
  310. "event_id": "c" * 32,
  311. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  312. },
  313. project_id=self.project.id,
  314. )
  315. group_3 = event_3.group
  316. group_3.status = GroupStatus.MUTED
  317. group_3.save()
  318. self.run_test_query_in_syntax(
  319. "status:[unresolved, resolved]", [self.group1, self.group2], [group_3]
  320. )
  321. self.run_test_query_in_syntax(
  322. "status:[resolved, muted]", [self.group2, group_3], [self.group1]
  323. )
  324. def test_status_with_environment(self):
  325. results = self.make_query(
  326. environments=[self.environments["production"]], search_filter_query="is:unresolved"
  327. )
  328. assert set(results) == {self.group1}
  329. results = self.make_query(
  330. environments=[self.environments["staging"]], search_filter_query="is:resolved"
  331. )
  332. assert set(results) == {self.group2}
  333. results = self.make_query(
  334. environments=[self.environments["production"]], search_filter_query="is:resolved"
  335. )
  336. assert set(results) == set()
  337. def test_tags(self):
  338. results = self.make_query(search_filter_query="environment:staging")
  339. assert set(results) == {self.group2}
  340. results = self.make_query(search_filter_query="environment:example.com")
  341. assert set(results) == set()
  342. results = self.make_query(search_filter_query="has:environment")
  343. assert set(results) == {self.group2, self.group1}
  344. results = self.make_query(search_filter_query="environment:staging server:example.com")
  345. assert set(results) == {self.group2}
  346. results = self.make_query(search_filter_query='url:"http://example.com"')
  347. assert set(results) == {self.group2}
  348. results = self.make_query(search_filter_query="environment:staging has:server")
  349. assert set(results) == {self.group2}
  350. results = self.make_query(search_filter_query="environment:staging server:bar.example.com")
  351. assert set(results) == set()
  352. def test_tags_with_environment(self):
  353. results = self.make_query(
  354. environments=[self.environments["production"]], search_filter_query="server:example.com"
  355. )
  356. assert set(results) == {self.group1}
  357. results = self.make_query(
  358. environments=[self.environments["staging"]], search_filter_query="server:example.com"
  359. )
  360. assert set(results) == {self.group2}
  361. results = self.make_query(
  362. environments=[self.environments["staging"]], search_filter_query="has:server"
  363. )
  364. assert set(results) == {self.group2}
  365. results = self.make_query(
  366. environments=[self.environments["production"]],
  367. search_filter_query='url:"http://example.com"',
  368. )
  369. assert set(results) == set()
  370. results = self.make_query(
  371. environments=[self.environments["staging"]],
  372. search_filter_query='url:"http://example.com"',
  373. )
  374. assert set(results) == {self.group2}
  375. results = self.make_query(
  376. environments=[self.environments["staging"]],
  377. search_filter_query="server:bar.example.com",
  378. )
  379. assert set(results) == set()
  380. def test_bookmarked_by(self):
  381. results = self.make_query(search_filter_query="bookmarks:%s" % self.user.username)
  382. assert set(results) == {self.group2}
  383. def test_bookmarked_by_in_syntax(self):
  384. self.run_test_query_in_syntax(
  385. f"bookmarks:[{self.user.username}]", [self.group2], [self.group1]
  386. )
  387. user_2 = self.create_user()
  388. GroupBookmark.objects.create(user=user_2, group=self.group1, project=self.group2.project)
  389. self.run_test_query_in_syntax(
  390. f"bookmarks:[{self.user.username}, {user_2.username}]", [self.group2, self.group1], []
  391. )
  392. def test_bookmarked_by_with_environment(self):
  393. results = self.make_query(
  394. environments=[self.environments["staging"]],
  395. search_filter_query="bookmarks:%s" % self.user.username,
  396. )
  397. assert set(results) == {self.group2}
  398. results = self.make_query(
  399. environments=[self.environments["production"]],
  400. search_filter_query="bookmarks:%s" % self.user.username,
  401. )
  402. assert set(results) == set()
  403. def test_search_filter_query_with_custom_priority_tag(self):
  404. priority = "high"
  405. self.store_event(
  406. data={
  407. "fingerprint": ["put-me-in-group2"],
  408. "timestamp": iso_format(self.group2.first_seen + timedelta(days=1)),
  409. "stacktrace": {"frames": [{"module": "group2"}]},
  410. "message": "group2",
  411. "tags": {"priority": priority},
  412. },
  413. project_id=self.project.id,
  414. )
  415. results = self.make_query(search_filter_query="priority:%s" % priority)
  416. assert set(results) == {self.group2}
  417. def test_search_filter_query_with_custom_priority_tag_and_priority_sort(self):
  418. priority = "high"
  419. for i in range(1, 3):
  420. self.store_event(
  421. data={
  422. "fingerprint": ["put-me-in-group1"],
  423. "timestamp": iso_format(self.group2.last_seen + timedelta(days=i)),
  424. "stacktrace": {"frames": [{"module": "group1"}]},
  425. "message": "group1",
  426. "tags": {"priority": priority},
  427. },
  428. project_id=self.project.id,
  429. )
  430. self.store_event(
  431. data={
  432. "fingerprint": ["put-me-in-group2"],
  433. "timestamp": iso_format(self.group2.last_seen + timedelta(days=2)),
  434. "stacktrace": {"frames": [{"module": "group2"}]},
  435. "message": "group2",
  436. "tags": {"priority": priority},
  437. },
  438. project_id=self.project.id,
  439. )
  440. results = self.make_query(search_filter_query="priority:%s" % priority, sort_by="priority")
  441. assert list(results) == [self.group1, self.group2]
  442. def test_search_tag_overlapping_with_internal_fields(self):
  443. # Using a tag of email overlaps with the promoted user.email column in events.
  444. # We don't want to bypass public schema limits in issue search.
  445. self.store_event(
  446. data={
  447. "fingerprint": ["put-me-in-group2"],
  448. "timestamp": iso_format(self.group2.first_seen + timedelta(days=1)),
  449. "stacktrace": {"frames": [{"module": "group2"}]},
  450. "message": "group2",
  451. "tags": {"email": "tags@example.com"},
  452. },
  453. project_id=self.project.id,
  454. )
  455. results = self.make_query(search_filter_query="email:tags@example.com")
  456. assert set(results) == {self.group2}
  457. def test_project(self):
  458. results = self.make_query([self.create_project(name="other")])
  459. assert set(results) == set()
  460. def test_pagination(self):
  461. for options_set in [
  462. {"snuba.search.min-pre-snuba-candidates": None},
  463. {"snuba.search.min-pre-snuba-candidates": 500},
  464. ]:
  465. with self.options(options_set):
  466. results = self.backend.query([self.project], limit=1, sort_by="date")
  467. assert set(results) == {self.group1}
  468. assert not results.prev.has_results
  469. assert results.next.has_results
  470. results = self.backend.query(
  471. [self.project], cursor=results.next, limit=1, sort_by="date"
  472. )
  473. assert set(results) == {self.group2}
  474. assert results.prev.has_results
  475. assert not results.next.has_results
  476. # note: previous cursor
  477. results = self.backend.query(
  478. [self.project], cursor=results.prev, limit=1, sort_by="date"
  479. )
  480. assert set(results) == {self.group1}
  481. assert results.prev.has_results
  482. assert results.next.has_results
  483. # note: previous cursor, paging too far into 0 results
  484. results = self.backend.query(
  485. [self.project], cursor=results.prev, limit=1, sort_by="date"
  486. )
  487. assert set(results) == set()
  488. assert not results.prev.has_results
  489. assert results.next.has_results
  490. results = self.backend.query(
  491. [self.project], cursor=results.next, limit=1, sort_by="date"
  492. )
  493. assert set(results) == {self.group1}
  494. assert results.prev.has_results
  495. assert results.next.has_results
  496. results = self.backend.query(
  497. [self.project], cursor=results.next, limit=1, sort_by="date"
  498. )
  499. assert set(results) == {self.group2}
  500. assert results.prev.has_results
  501. assert not results.next.has_results
  502. results = self.backend.query(
  503. [self.project], cursor=results.next, limit=1, sort_by="date"
  504. )
  505. assert set(results) == set()
  506. assert results.prev.has_results
  507. assert not results.next.has_results
  508. def test_pagination_with_environment(self):
  509. for dt in [
  510. self.group1.first_seen + timedelta(days=1),
  511. self.group1.first_seen + timedelta(days=2),
  512. self.group1.last_seen + timedelta(days=1),
  513. ]:
  514. self.store_event(
  515. data={
  516. "fingerprint": ["put-me-in-group2"],
  517. "timestamp": iso_format(dt),
  518. "environment": "production",
  519. "message": "group2",
  520. "stacktrace": {"frames": [{"module": "group2"}]},
  521. },
  522. project_id=self.project.id,
  523. )
  524. results = self.backend.query(
  525. [self.project],
  526. environments=[self.environments["production"]],
  527. sort_by="date",
  528. limit=1,
  529. count_hits=True,
  530. )
  531. assert list(results) == [self.group2]
  532. assert results.hits == 2
  533. results = self.backend.query(
  534. [self.project],
  535. environments=[self.environments["production"]],
  536. sort_by="date",
  537. limit=1,
  538. cursor=results.next,
  539. count_hits=True,
  540. )
  541. assert list(results) == [self.group1]
  542. assert results.hits == 2
  543. results = self.backend.query(
  544. [self.project],
  545. environments=[self.environments["production"]],
  546. sort_by="date",
  547. limit=1,
  548. cursor=results.next,
  549. count_hits=True,
  550. )
  551. assert list(results) == []
  552. assert results.hits == 2
  553. def test_active_at_filter(self):
  554. results = self.make_query(
  555. search_filter_query="activeSince:>=%s" % date_to_query_format(self.group2.active_at)
  556. )
  557. assert set(results) == {self.group2}
  558. results = self.make_query(
  559. search_filter_query="activeSince:<=%s"
  560. % date_to_query_format(self.group1.active_at + timedelta(minutes=1))
  561. )
  562. assert set(results) == {self.group1}
  563. results = self.make_query(
  564. search_filter_query="activeSince:>=%s activeSince:<=%s"
  565. % (
  566. date_to_query_format(self.group1.active_at),
  567. date_to_query_format(self.group1.active_at + timedelta(minutes=1)),
  568. )
  569. )
  570. assert set(results) == {self.group1}
  571. def test_age_filter(self):
  572. results = self.make_query(
  573. search_filter_query="firstSeen:>=%s" % date_to_query_format(self.group2.first_seen)
  574. )
  575. assert set(results) == {self.group2}
  576. results = self.make_query(
  577. search_filter_query="firstSeen:<=%s"
  578. % date_to_query_format(self.group1.first_seen + timedelta(minutes=1))
  579. )
  580. assert set(results) == {self.group1}
  581. results = self.make_query(
  582. search_filter_query="firstSeen:>=%s firstSeen:<=%s"
  583. % (
  584. date_to_query_format(self.group1.first_seen),
  585. date_to_query_format(self.group1.first_seen + timedelta(minutes=1)),
  586. )
  587. )
  588. assert set(results) == {self.group1}
  589. def test_age_filter_with_environment(self):
  590. # add time instead to make it greater than or less than as needed.
  591. group1_first_seen = GroupEnvironment.objects.get(
  592. environment=self.environments["production"], group=self.group1
  593. ).first_seen
  594. results = self.make_query(
  595. environments=[self.environments["production"]],
  596. search_filter_query="firstSeen:>=%s" % date_to_query_format(group1_first_seen),
  597. )
  598. assert set(results) == {self.group1}
  599. results = self.make_query(
  600. environments=[self.environments["production"]],
  601. search_filter_query="firstSeen:<=%s" % date_to_query_format(group1_first_seen),
  602. )
  603. assert set(results) == {self.group1}
  604. results = self.make_query(
  605. environments=[self.environments["production"]],
  606. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  607. )
  608. assert set(results) == set()
  609. self.store_event(
  610. data={
  611. "fingerprint": ["put-me-in-group1"],
  612. "timestamp": iso_format(group1_first_seen + timedelta(days=1)),
  613. "message": "group1",
  614. "stacktrace": {"frames": [{"module": "group1"}]},
  615. "environment": "development",
  616. },
  617. project_id=self.project.id,
  618. )
  619. results = self.make_query(
  620. environments=[self.environments["production"]],
  621. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  622. )
  623. assert set(results) == set()
  624. results = self.make_query(
  625. environments=[Environment.objects.get(name="development")],
  626. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  627. )
  628. assert set(results) == {self.group1}
  629. def test_times_seen_filter(self):
  630. results = self.make_query([self.project], search_filter_query="times_seen:2")
  631. assert set(results) == {self.group1}
  632. results = self.make_query([self.project], search_filter_query="times_seen:>=2")
  633. assert set(results) == {self.group1}
  634. results = self.make_query([self.project], search_filter_query="times_seen:<=1")
  635. assert set(results) == {self.group2}
  636. def test_last_seen_filter(self):
  637. results = self.make_query(
  638. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen)
  639. )
  640. assert set(results) == {self.group1}
  641. results = self.make_query(
  642. search_filter_query="lastSeen:>=%s lastSeen:<=%s"
  643. % (
  644. date_to_query_format(self.group1.last_seen),
  645. date_to_query_format(self.group1.last_seen + timedelta(minutes=1)),
  646. )
  647. )
  648. assert set(results) == {self.group1}
  649. def test_last_seen_filter_with_environment(self):
  650. results = self.make_query(
  651. environments=[self.environments["production"]],
  652. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  653. )
  654. assert set(results) == {self.group1}
  655. results = self.make_query(
  656. environments=[self.environments["production"]],
  657. search_filter_query="lastSeen:<=%s" % date_to_query_format(self.group1.last_seen),
  658. )
  659. assert set(results) == {self.group1}
  660. results = self.make_query(
  661. environments=[self.environments["production"]],
  662. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  663. )
  664. assert set(results) == set()
  665. self.store_event(
  666. data={
  667. "fingerprint": ["put-me-in-group1"],
  668. "timestamp": iso_format(self.group1.last_seen + timedelta(days=1)),
  669. "message": "group1",
  670. "stacktrace": {"frames": [{"module": "group1"}]},
  671. "environment": "development",
  672. },
  673. project_id=self.project.id,
  674. )
  675. self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1))
  676. results = self.make_query(
  677. environments=[self.environments["production"]],
  678. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  679. )
  680. assert set(results) == set()
  681. results = self.make_query(
  682. environments=[Environment.objects.get(name="development")],
  683. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  684. )
  685. assert set(results) == set()
  686. results = self.make_query(
  687. environments=[Environment.objects.get(name="development")],
  688. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  689. )
  690. assert set(results) == {self.group1}
  691. def test_date_filter(self):
  692. results = self.make_query(
  693. date_from=self.event2.datetime,
  694. search_filter_query="timestamp:>=%s" % date_to_query_format(self.event2.datetime),
  695. )
  696. assert set(results) == {self.group1, self.group2}
  697. results = self.make_query(
  698. date_to=self.event1.datetime + timedelta(minutes=1),
  699. search_filter_query="timestamp:<=%s"
  700. % date_to_query_format(self.event1.datetime + timedelta(minutes=1)),
  701. )
  702. assert set(results) == {self.group1}
  703. results = self.make_query(
  704. date_from=self.event1.datetime,
  705. date_to=self.event2.datetime + timedelta(minutes=1),
  706. search_filter_query="timestamp:>=%s timestamp:<=%s"
  707. % (
  708. date_to_query_format(self.event1.datetime),
  709. date_to_query_format(self.event2.datetime + timedelta(minutes=1)),
  710. ),
  711. )
  712. assert set(results) == {self.group1, self.group2}
  713. # Test with `Z` utc marker, should be equivalent
  714. results = self.make_query(
  715. date_from=self.event1.datetime,
  716. date_to=self.event2.datetime + timedelta(minutes=1),
  717. search_filter_query="timestamp:>=%s timestamp:<=%s"
  718. % (
  719. date_to_query_format(self.event1.datetime) + "Z",
  720. date_to_query_format(self.event2.datetime + timedelta(minutes=1)) + "Z",
  721. ),
  722. )
  723. assert set(results) == {self.group1, self.group2}
  724. def test_date_filter_with_environment(self):
  725. results = self.backend.query(
  726. [self.project],
  727. environments=[self.environments["production"]],
  728. date_from=self.event2.datetime,
  729. )
  730. assert set(results) == {self.group1}
  731. results = self.backend.query(
  732. [self.project],
  733. environments=[self.environments["production"]],
  734. date_to=self.event1.datetime + timedelta(minutes=1),
  735. )
  736. assert set(results) == {self.group1}
  737. results = self.backend.query(
  738. [self.project],
  739. environments=[self.environments["staging"]],
  740. date_from=self.event1.datetime,
  741. date_to=self.event2.datetime + timedelta(minutes=1),
  742. )
  743. assert set(results) == {self.group2}
  744. def test_linked(self):
  745. linked_group1 = self.create_group_with_integration_external_issue()
  746. linked_group2 = self.create_group_with_platform_external_issue()
  747. results = self.make_query(search_filter_query="is:unlinked")
  748. assert set(results) == {self.group1, self.group2}
  749. results = self.make_query(search_filter_query="is:linked")
  750. assert set(results) == {linked_group1, linked_group2}
  751. def test_linked_with_only_integration_external_issue(self):
  752. linked_group = self.create_group_with_integration_external_issue()
  753. results = self.make_query(search_filter_query="is:unlinked")
  754. assert set(results) == {self.group1, self.group2}
  755. results = self.make_query(search_filter_query="is:linked")
  756. assert set(results) == {linked_group}
  757. def test_linked_with_only_platform_external_issue(self):
  758. linked_group = self.create_group_with_platform_external_issue()
  759. results = self.make_query(search_filter_query="is:unlinked")
  760. assert set(results) == {self.group1, self.group2}
  761. results = self.make_query(search_filter_query="is:linked")
  762. assert set(results) == {linked_group}
  763. def test_linked_with_environment(self):
  764. linked_group1 = self.create_group_with_integration_external_issue(environment="production")
  765. linked_group2 = self.create_group_with_platform_external_issue(environment="staging")
  766. results = self.make_query(
  767. environments=[self.environments["production"]], search_filter_query="is:unlinked"
  768. )
  769. assert set(results) == {self.group1}
  770. results = self.make_query(
  771. environments=[self.environments["staging"]], search_filter_query="is:unlinked"
  772. )
  773. assert set(results) == {self.group2}
  774. results = self.make_query(
  775. environments=[self.environments["production"]], search_filter_query="is:linked"
  776. )
  777. assert set(results) == {linked_group1}
  778. results = self.make_query(
  779. environments=[self.environments["staging"]], search_filter_query="is:linked"
  780. )
  781. assert set(results) == {linked_group2}
  782. def test_unassigned(self):
  783. results = self.make_query(search_filter_query="is:unassigned")
  784. assert set(results) == {self.group1}
  785. results = self.make_query(search_filter_query="is:assigned")
  786. assert set(results) == {self.group2}
  787. def test_unassigned_with_environment(self):
  788. results = self.make_query(
  789. environments=[self.environments["production"]], search_filter_query="is:unassigned"
  790. )
  791. assert set(results) == {self.group1}
  792. results = self.make_query(
  793. environments=[self.environments["staging"]], search_filter_query="is:assigned"
  794. )
  795. assert set(results) == {self.group2}
  796. results = self.make_query(
  797. environments=[self.environments["production"]], search_filter_query="is:assigned"
  798. )
  799. assert set(results) == set()
  800. def test_assigned_to(self):
  801. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  802. assert set(results) == {self.group2}
  803. # test team assignee
  804. ga = GroupAssignee.objects.get(
  805. user=self.user, group=self.group2, project=self.group2.project
  806. )
  807. ga.update(team=self.team, user=None)
  808. assert GroupAssignee.objects.get(id=ga.id).user is None
  809. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  810. assert set(results) == {self.group2}
  811. # test when there should be no results
  812. other_user = self.create_user()
  813. results = self.make_query(search_filter_query="assigned:%s" % other_user.username)
  814. assert set(results) == set()
  815. owner = self.create_user()
  816. self.create_member(
  817. organization=self.project.organization, user=owner, role="owner", teams=[]
  818. )
  819. # test that owners don't see results for all teams
  820. results = self.make_query(search_filter_query="assigned:%s" % owner.username)
  821. assert set(results) == set()
  822. def test_assigned_to_in_syntax(self):
  823. group_3 = self.store_event(
  824. data={
  825. "fingerprint": ["put-me-in-group3"],
  826. "event_id": "c" * 32,
  827. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  828. },
  829. project_id=self.project.id,
  830. ).group
  831. group_3.status = GroupStatus.MUTED
  832. group_3.save()
  833. other_user = self.create_user()
  834. self.run_test_query_in_syntax(
  835. f"assigned:[{self.user.username}, {other_user.username}]",
  836. [self.group2],
  837. [self.group1, group_3],
  838. )
  839. GroupAssignee.objects.create(project=self.project, group=group_3, user=other_user)
  840. self.run_test_query_in_syntax(
  841. f"assigned:[{self.user.username}, {other_user.username}]",
  842. [self.group2, group_3],
  843. [self.group1],
  844. )
  845. self.run_test_query_in_syntax(
  846. f"assigned:[#{self.team.slug}, {other_user.username}]",
  847. [group_3],
  848. [self.group1, self.group2],
  849. )
  850. ga_2 = GroupAssignee.objects.get(
  851. user=self.user, group=self.group2, project=self.group2.project
  852. )
  853. ga_2.update(team=self.team, user=None)
  854. self.run_test_query_in_syntax(
  855. f"assigned:[{self.user.username}, {other_user.username}]",
  856. [self.group2, group_3],
  857. [self.group1],
  858. )
  859. self.run_test_query_in_syntax(
  860. f"assigned:[#{self.team.slug}, {other_user.username}]",
  861. [self.group2, group_3],
  862. [self.group1],
  863. )
  864. self.run_test_query_in_syntax(
  865. f"assigned:[me, none, {other_user.username}]",
  866. [self.group1, self.group2, group_3],
  867. [],
  868. )
  869. def test_assigned_or_suggested_in_syntax(self):
  870. Group.objects.all().delete()
  871. group = self.store_event(
  872. data={
  873. "timestamp": iso_format(before_now(seconds=180)),
  874. "fingerprint": ["group-1"],
  875. },
  876. project_id=self.project.id,
  877. ).group
  878. group1 = self.store_event(
  879. data={
  880. "timestamp": iso_format(before_now(seconds=185)),
  881. "fingerprint": ["group-2"],
  882. },
  883. project_id=self.project.id,
  884. ).group
  885. group2 = self.store_event(
  886. data={
  887. "timestamp": iso_format(before_now(seconds=190)),
  888. "fingerprint": ["group-3"],
  889. },
  890. project_id=self.project.id,
  891. ).group
  892. assigned_group = self.store_event(
  893. data={
  894. "timestamp": iso_format(before_now(seconds=195)),
  895. "fingerprint": ["group-4"],
  896. },
  897. project_id=self.project.id,
  898. ).group
  899. assigned_to_other_group = self.store_event(
  900. data={
  901. "timestamp": iso_format(before_now(seconds=195)),
  902. "fingerprint": ["group-5"],
  903. },
  904. project_id=self.project.id,
  905. ).group
  906. self.run_test_query_in_syntax(
  907. "assigned_or_suggested:[me]",
  908. [],
  909. [group, group1, group2, assigned_group, assigned_to_other_group],
  910. )
  911. GroupOwner.objects.create(
  912. group=assigned_to_other_group,
  913. project=self.project,
  914. organization=self.organization,
  915. type=0,
  916. team_id=None,
  917. user_id=self.user.id,
  918. )
  919. GroupOwner.objects.create(
  920. group=group,
  921. project=self.project,
  922. organization=self.organization,
  923. type=0,
  924. team_id=None,
  925. user_id=self.user.id,
  926. )
  927. self.run_test_query_in_syntax(
  928. "assigned_or_suggested:[me]",
  929. [group, assigned_to_other_group],
  930. [group1, group2, assigned_group],
  931. )
  932. # Because assigned_to_other_event is assigned to self.other_user, it should not show up in assigned_or_suggested search for anyone but self.other_user. (aka. they are now the only owner)
  933. other_user = self.create_user("other@user.com", is_superuser=False)
  934. GroupAssignee.objects.create(
  935. group=assigned_to_other_group,
  936. project=self.project,
  937. user=other_user,
  938. )
  939. self.run_test_query_in_syntax(
  940. "assigned_or_suggested:[me]",
  941. [group],
  942. [group1, group2, assigned_group, assigned_to_other_group],
  943. )
  944. self.run_test_query_in_syntax(
  945. f"assigned_or_suggested:[{other_user.email}]",
  946. [assigned_to_other_group],
  947. [group, group1, group2, assigned_group],
  948. )
  949. GroupAssignee.objects.create(group=assigned_group, project=self.project, user=self.user)
  950. self.run_test_query_in_syntax(
  951. f"assigned_or_suggested:[{self.user.email}]",
  952. [assigned_group, group],
  953. )
  954. GroupOwner.objects.create(
  955. group=group,
  956. project=self.project,
  957. organization=self.organization,
  958. type=0,
  959. team_id=self.team.id,
  960. user_id=None,
  961. )
  962. self.run_test_query_in_syntax(
  963. f"assigned_or_suggested:[#{self.team.slug}]",
  964. [group],
  965. )
  966. self.run_test_query_in_syntax(
  967. "assigned_or_suggested:[me, none]",
  968. [group, group1, group2, assigned_group],
  969. [assigned_to_other_group],
  970. )
  971. not_me = self.create_user(email="notme@sentry.io")
  972. GroupOwner.objects.create(
  973. group=group2,
  974. project=self.project,
  975. organization=self.organization,
  976. type=0,
  977. team_id=None,
  978. user_id=not_me.id,
  979. )
  980. self.run_test_query_in_syntax(
  981. "assigned_or_suggested:[me, none]",
  982. [group, group1, assigned_group],
  983. [assigned_to_other_group, group2],
  984. )
  985. GroupOwner.objects.filter(group=group, user=self.user).delete()
  986. self.run_test_query_in_syntax(
  987. f"assigned_or_suggested:[me, none, #{self.team.slug}]",
  988. [group, group1, assigned_group],
  989. [assigned_to_other_group, group2],
  990. )
  991. self.run_test_query_in_syntax(
  992. f"assigned_or_suggested:[me, none, #{self.team.slug}, {not_me.email}]",
  993. [group, group1, assigned_group, group2],
  994. [assigned_to_other_group],
  995. )
  996. def test_assigned_to_with_environment(self):
  997. results = self.make_query(
  998. environments=[self.environments["staging"]],
  999. search_filter_query="assigned:%s" % self.user.username,
  1000. )
  1001. assert set(results) == {self.group2}
  1002. results = self.make_query(
  1003. environments=[self.environments["production"]],
  1004. search_filter_query="assigned:%s" % self.user.username,
  1005. )
  1006. assert set(results) == set()
  1007. def test_subscribed_by(self):
  1008. results = self.make_query(
  1009. [self.group1.project], search_filter_query="subscribed:%s" % self.user.username
  1010. )
  1011. assert set(results) == {self.group1}
  1012. def test_subscribed_by_in_syntax(self):
  1013. self.run_test_query_in_syntax(
  1014. f"subscribed:[{self.user.username}]", [self.group1], [self.group2]
  1015. )
  1016. user_2 = self.create_user()
  1017. GroupSubscription.objects.create(
  1018. user=user_2, group=self.group2, project=self.project, is_active=True
  1019. )
  1020. self.run_test_query_in_syntax(
  1021. f"subscribed:[{self.user.username}, {user_2.username}]", [self.group1, self.group2], []
  1022. )
  1023. def test_subscribed_by_with_environment(self):
  1024. results = self.make_query(
  1025. [self.group1.project],
  1026. environments=[self.environments["production"]],
  1027. search_filter_query="subscribed:%s" % self.user.username,
  1028. )
  1029. assert set(results) == {self.group1}
  1030. results = self.make_query(
  1031. [self.group1.project],
  1032. environments=[self.environments["staging"]],
  1033. search_filter_query="subscribed:%s" % self.user.username,
  1034. )
  1035. assert set(results) == set()
  1036. @mock.patch("sentry.utils.snuba.raw_query")
  1037. def test_snuba_not_called_optimization(self, query_mock):
  1038. assert self.make_query(search_filter_query="status:unresolved").results == [self.group1]
  1039. assert not query_mock.called
  1040. assert (
  1041. self.make_query(
  1042. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1043. sort_by="date",
  1044. ).results
  1045. == []
  1046. )
  1047. assert query_mock.called
  1048. @mock.patch("sentry.utils.snuba.raw_query")
  1049. def test_optimized_aggregates(self, query_mock):
  1050. # TODO this test is annoyingly fragile and breaks in hard-to-see ways
  1051. # any time anything about the snuba query changes
  1052. query_mock.return_value = {"data": [], "totals": {"total": 0}}
  1053. def Any(cls):
  1054. class Any:
  1055. def __eq__(self, other):
  1056. return isinstance(other, cls)
  1057. return Any()
  1058. DEFAULT_LIMIT = 100
  1059. chunk_growth = options.get("snuba.search.chunk-growth-rate")
  1060. limit = int(DEFAULT_LIMIT * chunk_growth)
  1061. common_args = {
  1062. "arrayjoin": None,
  1063. "dataset": Dataset.Events,
  1064. "start": Any(datetime),
  1065. "end": Any(datetime),
  1066. "filter_keys": {
  1067. "project_id": [self.project.id],
  1068. "group_id": [self.group1.id, self.group2.id],
  1069. },
  1070. "referrer": "search",
  1071. "groupby": ["group_id"],
  1072. "conditions": [[["positionCaseInsensitive", ["message", "'foo'"]], "!=", 0]],
  1073. "selected_columns": [],
  1074. "limit": limit,
  1075. "offset": 0,
  1076. "totals": True,
  1077. "turbo": False,
  1078. "sample": 1,
  1079. }
  1080. self.make_query(search_filter_query="status:unresolved")
  1081. assert not query_mock.called
  1082. self.make_query(
  1083. search_filter_query="last_seen:>=%s foo" % date_to_query_format(timezone.now()),
  1084. sort_by="date",
  1085. )
  1086. query_mock.call_args[1]["aggregations"].sort()
  1087. assert query_mock.call_args == mock.call(
  1088. orderby=["-last_seen", "group_id"],
  1089. aggregations=[
  1090. ["multiply(toUInt64(max(timestamp)), 1000)", "", "last_seen"],
  1091. ["uniq", "group_id", "total"],
  1092. ],
  1093. having=[["last_seen", ">=", Any(int)]],
  1094. **common_args,
  1095. )
  1096. self.make_query(search_filter_query="foo", sort_by="priority")
  1097. query_mock.call_args[1]["aggregations"].sort()
  1098. assert query_mock.call_args == mock.call(
  1099. orderby=["-priority", "group_id"],
  1100. aggregations=[
  1101. ["count()", "", "times_seen"],
  1102. ["multiply(toUInt64(max(timestamp)), 1000)", "", "last_seen"],
  1103. ["toUInt64(plus(multiply(log(times_seen), 600), last_seen))", "", "priority"],
  1104. ["uniq", "group_id", "total"],
  1105. ],
  1106. having=[],
  1107. **common_args,
  1108. )
  1109. self.make_query(search_filter_query="times_seen:5 foo", sort_by="freq")
  1110. query_mock.call_args[1]["aggregations"].sort()
  1111. assert query_mock.call_args == mock.call(
  1112. orderby=["-times_seen", "group_id"],
  1113. aggregations=[["count()", "", "times_seen"], ["uniq", "group_id", "total"]],
  1114. having=[["times_seen", "=", 5]],
  1115. **common_args,
  1116. )
  1117. self.make_query(search_filter_query="foo", sort_by="user")
  1118. query_mock.call_args[1]["aggregations"].sort()
  1119. assert query_mock.call_args == mock.call(
  1120. orderby=["-user_count", "group_id"],
  1121. aggregations=[
  1122. ["uniq", "group_id", "total"],
  1123. ["uniq", "tags[sentry:user]", "user_count"],
  1124. ],
  1125. having=[],
  1126. **common_args,
  1127. )
  1128. def test_pre_and_post_filtering(self):
  1129. prev_max_pre = options.get("snuba.search.max-pre-snuba-candidates")
  1130. options.set("snuba.search.max-pre-snuba-candidates", 1)
  1131. try:
  1132. # normal queries work as expected
  1133. results = self.make_query(search_filter_query="foo")
  1134. assert set(results) == {self.group1}
  1135. results = self.make_query(search_filter_query="bar")
  1136. assert set(results) == {self.group2}
  1137. # no candidate matches in Sentry, immediately return empty paginator
  1138. results = self.make_query(search_filter_query="NO MATCHES IN SENTRY")
  1139. assert set(results) == set()
  1140. # too many candidates, skip pre-filter, requires >1 postfilter queries
  1141. results = self.make_query()
  1142. assert set(results) == {self.group1, self.group2}
  1143. finally:
  1144. options.set("snuba.search.max-pre-snuba-candidates", prev_max_pre)
  1145. def test_optimizer_enabled(self):
  1146. prev_optimizer_enabled = options.get("snuba.search.pre-snuba-candidates-optimizer")
  1147. options.set("snuba.search.pre-snuba-candidates-optimizer", True)
  1148. try:
  1149. results = self.make_query(
  1150. search_filter_query="server:example.com",
  1151. environments=[self.environments["production"]],
  1152. )
  1153. assert set(results) == {self.group1}
  1154. finally:
  1155. options.set("snuba.search.pre-snuba-candidates-optimizer", prev_optimizer_enabled)
  1156. def test_search_out_of_range(self):
  1157. the_date = datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc)
  1158. results = self.make_query(
  1159. search_filter_query=f"event.timestamp:>{the_date} event.timestamp:<{the_date}",
  1160. date_from=the_date,
  1161. date_to=the_date,
  1162. )
  1163. assert set(results) == set()
  1164. def test_hits_estimate(self):
  1165. # 400 Groups/Events
  1166. # Every 3rd one is Unresolved
  1167. # Every 2nd one has tag match=1
  1168. for i in range(400):
  1169. event = self.store_event(
  1170. data={
  1171. "event_id": md5(f"event {i}".encode("utf-8")).hexdigest(),
  1172. "fingerprint": [f"put-me-in-group{i}"],
  1173. "timestamp": iso_format(self.base_datetime - timedelta(days=21)),
  1174. "message": f"group {i} event",
  1175. "stacktrace": {"frames": [{"module": f"module {i}"}]},
  1176. "tags": {"match": f"{i % 2}"},
  1177. "environment": "production",
  1178. },
  1179. project_id=self.project.id,
  1180. )
  1181. group = event.group
  1182. group.times_seen = 5
  1183. group.status = GroupStatus.UNRESOLVED if i % 3 == 0 else GroupStatus.RESOLVED
  1184. group.save()
  1185. self.store_group(group)
  1186. # Sample should estimate there are roughly 66 overall matching groups
  1187. # based on a random sample of 100 (or $sample_size) of the total 200
  1188. # snuba matches, of which 33% should pass the postgres filter.
  1189. with self.options(
  1190. {
  1191. # Too small to pass all django candidates down to snuba
  1192. "snuba.search.max-pre-snuba-candidates": 5,
  1193. "snuba.search.hits-sample-size": 50,
  1194. }
  1195. ):
  1196. first_results = self.make_query(
  1197. search_filter_query="is:unresolved match:1", limit=10, count_hits=True
  1198. )
  1199. # Deliberately do not assert that the value is within some margin
  1200. # of error, as this will fail tests at some rate corresponding to
  1201. # our confidence interval.
  1202. assert first_results.hits > 10
  1203. # When searching for the same tags, we should get the same set of
  1204. # hits as the sampling is based on the hash of the query.
  1205. second_results = self.make_query(
  1206. search_filter_query="is:unresolved match:1", limit=10, count_hits=True
  1207. )
  1208. assert first_results.results == second_results.results
  1209. # When using a different search, we should get a different sample
  1210. # but still should have some hits.
  1211. third_results = self.make_query(
  1212. search_filter_query="is:unresolved match:0", limit=10, count_hits=True
  1213. )
  1214. assert third_results.hits > 10
  1215. assert third_results.results != second_results.results
  1216. def test_first_release(self):
  1217. # expect no groups within the results since there are no releases
  1218. results = self.make_query(search_filter_query="first_release:%s" % "fake")
  1219. assert set(results) == set()
  1220. # expect no groups even though there is a release; since no group
  1221. # is attached to a release
  1222. release_1 = self.create_release(self.project)
  1223. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1224. assert set(results) == set()
  1225. # Create a new event so that we get a group in this release
  1226. group = self.store_event(
  1227. data={
  1228. "fingerprint": ["put-me-in-group9001"],
  1229. "event_id": "a" * 32,
  1230. "message": "hello",
  1231. "environment": "production",
  1232. "tags": {"server": "example.com"},
  1233. "release": release_1.version,
  1234. "stacktrace": {"frames": [{"module": "group1"}]},
  1235. },
  1236. project_id=self.project.id,
  1237. ).group
  1238. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1239. assert set(results) == {group}
  1240. def test_first_release_in_syntax(self):
  1241. # expect no groups within the results since there are no releases
  1242. self.run_test_query_in_syntax("first_release:[fake, fake2]", [])
  1243. # expect no groups even though there is a release; since no group
  1244. # is attached to a release
  1245. release_1 = self.create_release(self.project)
  1246. release_2 = self.create_release(self.project)
  1247. self.run_test_query_in_syntax(
  1248. f"first_release:[{release_1.version}, {release_2.version}]", []
  1249. )
  1250. # Create a new event so that we get a group in this release
  1251. group = self.store_event(
  1252. data={
  1253. "fingerprint": ["put-me-in-group9001"],
  1254. "event_id": "a" * 32,
  1255. "message": "hello",
  1256. "environment": "production",
  1257. "tags": {"server": "example.com"},
  1258. "release": release_1.version,
  1259. "stacktrace": {"frames": [{"module": "group1"}]},
  1260. },
  1261. project_id=self.project.id,
  1262. ).group
  1263. self.run_test_query_in_syntax(
  1264. f"first_release:[{release_1.version}, {release_2.version}]",
  1265. [group],
  1266. [self.group1, self.group2],
  1267. )
  1268. # Create a new event so that we get a group in this release
  1269. group_2 = self.store_event(
  1270. data={
  1271. "fingerprint": ["put-me-in-group9002"],
  1272. "event_id": "a" * 32,
  1273. "message": "hello",
  1274. "environment": "production",
  1275. "tags": {"server": "example.com"},
  1276. "release": release_2.version,
  1277. "stacktrace": {"frames": [{"module": "group1"}]},
  1278. },
  1279. project_id=self.project.id,
  1280. ).group
  1281. self.run_test_query_in_syntax(
  1282. f"first_release:[{release_1.version}, {release_2.version}]",
  1283. [group, group_2],
  1284. )
  1285. def test_first_release_environments(self):
  1286. results = self.make_query(
  1287. environments=[self.environments["production"]],
  1288. search_filter_query="first_release:fake",
  1289. )
  1290. assert set(results) == set()
  1291. release = self.create_release(self.project)
  1292. group_env = GroupEnvironment.get_or_create(
  1293. group_id=self.group1.id, environment_id=self.environments["production"].id
  1294. )[0]
  1295. results = self.make_query(
  1296. environments=[self.environments["production"]],
  1297. search_filter_query=f"first_release:{release.version}",
  1298. )
  1299. assert set(results) == set()
  1300. group_env.first_release = release
  1301. group_env.save()
  1302. results = self.make_query(
  1303. environments=[self.environments["production"]],
  1304. search_filter_query=f"first_release:{release.version}",
  1305. )
  1306. assert set(results) == {self.group1}
  1307. def test_first_release_environments_in_syntax(self):
  1308. self.run_test_query_in_syntax(
  1309. "first_release:[fake, fake2]",
  1310. [],
  1311. [self.group1, self.group2],
  1312. environments=[self.environments["production"]],
  1313. )
  1314. release = self.create_release(self.project)
  1315. group_1_env = GroupEnvironment.objects.get(
  1316. group_id=self.group1.id, environment_id=self.environments["production"].id
  1317. )
  1318. group_1_env.update(first_release=release)
  1319. self.run_test_query_in_syntax(
  1320. f"first_release:[{release.version}, fake2]",
  1321. [self.group1],
  1322. [self.group2],
  1323. environments=[self.environments["production"]],
  1324. )
  1325. group_2_env = GroupEnvironment.objects.get(
  1326. group_id=self.group2.id, environment_id=self.environments["staging"].id
  1327. )
  1328. group_2_env.update(first_release=release)
  1329. self.run_test_query_in_syntax(
  1330. f"first_release:[{release.version}, fake2]",
  1331. [self.group1, self.group2],
  1332. [],
  1333. environments=[self.environments["production"], self.environments["staging"]],
  1334. )
  1335. # Make sure we don't get duplicate groups
  1336. GroupEnvironment.objects.create(
  1337. group_id=self.group1.id,
  1338. environment_id=self.environments["staging"].id,
  1339. first_release=release,
  1340. )
  1341. self.run_test_query_in_syntax(
  1342. f"first_release:[{release.version}, fake2]",
  1343. [self.group1, self.group2],
  1344. [],
  1345. environments=[self.environments["production"], self.environments["staging"]],
  1346. )
  1347. def test_query_enclosed_in_quotes(self):
  1348. results = self.make_query(search_filter_query='"foo"')
  1349. assert set(results) == {self.group1}
  1350. results = self.make_query(search_filter_query='"bar"')
  1351. assert set(results) == {self.group2}
  1352. @xfail_if_not_postgres("Wildcard searching only supported in Postgres")
  1353. def test_wildcard(self):
  1354. escaped_event = self.store_event(
  1355. data={
  1356. "fingerprint": ["hello-there"],
  1357. "event_id": "f" * 32,
  1358. "message": "somet[hing]",
  1359. "environment": "production",
  1360. "tags": {"server": "example.net"},
  1361. "timestamp": iso_format(self.base_datetime),
  1362. "stacktrace": {"frames": [{"module": "group1"}]},
  1363. },
  1364. project_id=self.project.id,
  1365. )
  1366. # Note: Adding in `environment:production` so that we make sure we query
  1367. # in both snuba and postgres
  1368. results = self.make_query(search_filter_query="environment:production so*t")
  1369. assert set(results) == {escaped_event.group}
  1370. # Make sure it's case insensitive
  1371. results = self.make_query(search_filter_query="environment:production SO*t")
  1372. assert set(results) == {escaped_event.group}
  1373. results = self.make_query(search_filter_query="environment:production so*zz")
  1374. assert set(results) == set()
  1375. results = self.make_query(search_filter_query="environment:production [hing]")
  1376. assert set(results) == {escaped_event.group}
  1377. results = self.make_query(search_filter_query="environment:production s*]")
  1378. assert set(results) == {escaped_event.group}
  1379. results = self.make_query(search_filter_query="environment:production server:example.*")
  1380. assert set(results) == {self.group1, escaped_event.group}
  1381. results = self.make_query(search_filter_query="environment:production !server:*net")
  1382. assert set(results) == {self.group1}
  1383. # TODO: Disabling tests that use [] syntax for the moment. Re-enable
  1384. # these if we decide to add back in, or remove if this comment has been
  1385. # here a while.
  1386. # results = self.make_query(
  1387. # search_filter_query='environment:production [s][of][mz]',
  1388. # )
  1389. # assert set(results) == set([escaped_event.group])
  1390. # results = self.make_query(
  1391. # search_filter_query='environment:production [z][of][mz]',
  1392. # )
  1393. # assert set(results) == set()
  1394. def test_null_tags(self):
  1395. tag_event = self.store_event(
  1396. data={
  1397. "fingerprint": ["hello-there"],
  1398. "event_id": "f" * 32,
  1399. "message": "something",
  1400. "environment": "production",
  1401. "tags": {"server": "example.net"},
  1402. "timestamp": iso_format(self.base_datetime),
  1403. "stacktrace": {"frames": [{"module": "group1"}]},
  1404. },
  1405. project_id=self.project.id,
  1406. )
  1407. no_tag_event = self.store_event(
  1408. data={
  1409. "fingerprint": ["hello-there-2"],
  1410. "event_id": "5" * 32,
  1411. "message": "something",
  1412. "environment": "production",
  1413. "timestamp": iso_format(self.base_datetime),
  1414. "stacktrace": {"frames": [{"module": "group2"}]},
  1415. },
  1416. project_id=self.project.id,
  1417. )
  1418. results = self.make_query(search_filter_query="environment:production !server:*net")
  1419. assert set(results) == {self.group1, no_tag_event.group}
  1420. results = self.make_query(search_filter_query="environment:production server:*net")
  1421. assert set(results) == {tag_event.group}
  1422. results = self.make_query(search_filter_query="environment:production !server:example.net")
  1423. assert set(results) == {self.group1, no_tag_event.group}
  1424. results = self.make_query(search_filter_query="environment:production server:example.net")
  1425. assert set(results) == {tag_event.group}
  1426. results = self.make_query(search_filter_query="environment:production has:server")
  1427. assert set(results) == {self.group1, tag_event.group}
  1428. results = self.make_query(search_filter_query="environment:production !has:server")
  1429. assert set(results) == {no_tag_event.group}
  1430. def test_null_promoted_tags(self):
  1431. tag_event = self.store_event(
  1432. data={
  1433. "fingerprint": ["hello-there"],
  1434. "event_id": "f" * 32,
  1435. "message": "something",
  1436. "environment": "production",
  1437. "tags": {"logger": "csp"},
  1438. "timestamp": iso_format(self.base_datetime),
  1439. "stacktrace": {"frames": [{"module": "group1"}]},
  1440. },
  1441. project_id=self.project.id,
  1442. )
  1443. no_tag_event = self.store_event(
  1444. data={
  1445. "fingerprint": ["hello-there-2"],
  1446. "event_id": "5" * 32,
  1447. "message": "something",
  1448. "environment": "production",
  1449. "timestamp": iso_format(self.base_datetime),
  1450. "stacktrace": {"frames": [{"module": "group2"}]},
  1451. },
  1452. project_id=self.project.id,
  1453. )
  1454. results = self.make_query(search_filter_query="environment:production !logger:*sp")
  1455. assert set(results) == {self.group1, no_tag_event.group}
  1456. results = self.make_query(search_filter_query="environment:production logger:*sp")
  1457. assert set(results) == {tag_event.group}
  1458. results = self.make_query(search_filter_query="environment:production !logger:csp")
  1459. assert set(results) == {self.group1, no_tag_event.group}
  1460. results = self.make_query(search_filter_query="environment:production logger:csp")
  1461. assert set(results) == {tag_event.group}
  1462. results = self.make_query(search_filter_query="environment:production has:logger")
  1463. assert set(results) == {tag_event.group}
  1464. results = self.make_query(search_filter_query="environment:production !has:logger")
  1465. assert set(results) == {self.group1, no_tag_event.group}
  1466. def test_sort_multi_project(self):
  1467. self.set_up_multi_project()
  1468. results = self.make_query([self.project, self.project2], sort_by="date")
  1469. assert list(results) == [self.group1, self.group_p2, self.group2]
  1470. results = self.make_query([self.project, self.project2], sort_by="new")
  1471. assert list(results) == [self.group2, self.group_p2, self.group1]
  1472. results = self.make_query([self.project, self.project2], sort_by="freq")
  1473. assert list(results) == [self.group1, self.group_p2, self.group2]
  1474. results = self.make_query([self.project, self.project2], sort_by="priority")
  1475. assert list(results) == [self.group1, self.group2, self.group_p2]
  1476. results = self.make_query([self.project, self.project2], sort_by="user")
  1477. assert list(results) == [self.group1, self.group2, self.group_p2]
  1478. def test_sort_trend(self):
  1479. start = self.group1.first_seen - timedelta(days=1)
  1480. end = before_now(days=1).replace(tzinfo=pytz.utc)
  1481. middle = start + ((end - start) / 2)
  1482. self.store_event(
  1483. data={
  1484. "fingerprint": ["put-me-in-group1"],
  1485. "event_id": "2" * 32,
  1486. "message": "something",
  1487. "timestamp": iso_format(self.base_datetime),
  1488. },
  1489. project_id=self.project.id,
  1490. )
  1491. self.store_event(
  1492. data={
  1493. "fingerprint": ["put-me-in-group1"],
  1494. "event_id": "3" * 32,
  1495. "message": "something",
  1496. "timestamp": iso_format(self.base_datetime),
  1497. },
  1498. project_id=self.project.id,
  1499. )
  1500. fewer_events_group = self.store_event(
  1501. data={
  1502. "fingerprint": ["put-me-in-group4"],
  1503. "event_id": "4" * 32,
  1504. "message": "something",
  1505. "timestamp": iso_format(middle - timedelta(days=1)),
  1506. },
  1507. project_id=self.project.id,
  1508. ).group
  1509. self.store_event(
  1510. data={
  1511. "fingerprint": ["put-me-in-group4"],
  1512. "event_id": "5" * 32,
  1513. "message": "something",
  1514. "timestamp": iso_format(middle - timedelta(days=1)),
  1515. },
  1516. project_id=self.project.id,
  1517. )
  1518. self.store_event(
  1519. data={
  1520. "fingerprint": ["put-me-in-group4"],
  1521. "event_id": "6" * 32,
  1522. "message": "something",
  1523. "timestamp": iso_format(self.base_datetime),
  1524. },
  1525. project_id=self.project.id,
  1526. )
  1527. no_before_group = self.store_event(
  1528. data={
  1529. "fingerprint": ["put-me-in-group5"],
  1530. "event_id": "3" * 32,
  1531. "message": "something",
  1532. "timestamp": iso_format(self.base_datetime),
  1533. },
  1534. project_id=self.project.id,
  1535. ).group
  1536. no_after_group = self.store_event(
  1537. data={
  1538. "fingerprint": ["put-me-in-group6"],
  1539. "event_id": "4" * 32,
  1540. "message": "something",
  1541. "timestamp": iso_format(middle - timedelta(days=1)),
  1542. },
  1543. project_id=self.project.id,
  1544. ).group
  1545. self.set_up_multi_project()
  1546. results = self.make_query([self.project], sort_by="trend", date_from=start, date_to=end)
  1547. assert results[:2] == [self.group1, fewer_events_group]
  1548. # These will be arbitrarily ordered since their trend values are all 0
  1549. assert set(results[2:]) == {self.group2, no_before_group, no_after_group}
  1550. def test_sort_inbox(self):
  1551. start = self.group1.first_seen - timedelta(days=1)
  1552. inbox_group_1 = self.store_event(
  1553. data={
  1554. "fingerprint": ["put-me-in-group1"],
  1555. "event_id": "2" * 32,
  1556. "message": "something",
  1557. "timestamp": iso_format(self.base_datetime),
  1558. },
  1559. project_id=self.project.id,
  1560. ).group
  1561. inbox_1 = add_group_to_inbox(inbox_group_1, GroupInboxReason.NEW)
  1562. self.store_event(
  1563. data={
  1564. "fingerprint": ["put-me-in-group2"],
  1565. "event_id": "3" * 32,
  1566. "message": "something",
  1567. "timestamp": iso_format(self.base_datetime),
  1568. },
  1569. project_id=self.project.id,
  1570. ).group
  1571. inbox_group_2 = self.store_event(
  1572. data={
  1573. "fingerprint": ["put-me-in-group3"],
  1574. "event_id": "4" * 32,
  1575. "message": "something",
  1576. "timestamp": iso_format(self.base_datetime),
  1577. },
  1578. project_id=self.project.id,
  1579. ).group
  1580. inbox_2 = add_group_to_inbox(inbox_group_2, GroupInboxReason.NEW)
  1581. inbox_2.update(date_added=inbox_1.date_added - timedelta(hours=1))
  1582. results = self.make_query(
  1583. [self.project, self.create_project()],
  1584. sort_by="inbox",
  1585. date_from=start,
  1586. search_filter_query="is:unresolved is:for_review",
  1587. )
  1588. assert results.results == [inbox_group_1, inbox_group_2]
  1589. def test_sort_inbox_invalid(self):
  1590. with pytest.raises(InvalidSearchQuery):
  1591. self.make_query([self.project], sort_by="inbox")
  1592. with pytest.raises(InvalidSearchQuery):
  1593. self.make_query(
  1594. [self.project], sort_by="inbox", search_filter_query="is:for_review abc:no_tags"
  1595. )
  1596. with pytest.raises(InvalidSearchQuery):
  1597. self.make_query([self.project], sort_by="inbox", search_filter_query="!is:for_review")
  1598. def test_in_syntax_is_invalid(self):
  1599. with pytest.raises(InvalidSearchQuery, match='"in" syntax invalid for "is" search'):
  1600. self.make_query(search_filter_query="is:[unresolved, resolved]")
  1601. def test_first_release_any_or_no_environments(self):
  1602. # test scenarios for tickets:
  1603. # SEN-571
  1604. # ISSUE-432
  1605. # given the following setup:
  1606. #
  1607. # groups table:
  1608. # group first_release
  1609. # A 1
  1610. # B 1
  1611. # C 2
  1612. #
  1613. # groupenvironments table:
  1614. # group environment first_release
  1615. # A staging 1
  1616. # A production 2
  1617. #
  1618. # when querying by first release, the appropriate set of groups should be displayed:
  1619. #
  1620. # first_release: 1
  1621. # env=[]: A, B
  1622. # env=[production, staging]: A
  1623. # env=[staging]: A
  1624. # env=[production]: nothing
  1625. #
  1626. # first_release: 2
  1627. # env=[]: A, C
  1628. # env=[production, staging]: A
  1629. # env=[staging]: nothing
  1630. # env=[production]: A
  1631. # create an issue/group whose events that occur in 2 distinct environments
  1632. group_a_event_1 = self.store_event(
  1633. data={
  1634. "fingerprint": ["group_a"],
  1635. "event_id": "aaa" + ("1" * 29),
  1636. "environment": "example_staging",
  1637. "release": "release_1",
  1638. },
  1639. project_id=self.project.id,
  1640. )
  1641. group_a_event_2 = self.store_event(
  1642. data={
  1643. "fingerprint": ["group_a"],
  1644. "event_id": "aaa" + ("2" * 29),
  1645. "environment": "example_production",
  1646. "release": "release_2",
  1647. },
  1648. project_id=self.project.id,
  1649. )
  1650. group_a = group_a_event_1.group
  1651. # get the environments for group_a
  1652. prod_env = group_a_event_2.get_environment()
  1653. staging_env = group_a_event_1.get_environment()
  1654. # create an issue/group whose event that occur in no environments
  1655. # but will be tied to release release_1
  1656. group_b_event_1 = self.store_event(
  1657. data={
  1658. "fingerprint": ["group_b"],
  1659. "event_id": "bbb" + ("1" * 29),
  1660. "release": "release_1",
  1661. },
  1662. project_id=self.project.id,
  1663. )
  1664. assert group_b_event_1.get_environment().name == "" # has no environment
  1665. group_b = group_b_event_1.group
  1666. # create an issue/group whose event that occur in no environments
  1667. # but will be tied to release release_2
  1668. group_c_event_1 = self.store_event(
  1669. data={
  1670. "fingerprint": ["group_c"],
  1671. "event_id": "ccc" + ("1" * 29),
  1672. "release": "release_2",
  1673. },
  1674. project_id=self.project.id,
  1675. )
  1676. assert group_c_event_1.get_environment().name == "" # has no environment
  1677. group_c = group_c_event_1.group
  1678. # query by release release_1
  1679. results = self.make_query(search_filter_query="first_release:%s" % "release_1")
  1680. assert set(results) == {group_a, group_b}
  1681. results = self.make_query(
  1682. environments=[staging_env, prod_env],
  1683. search_filter_query="first_release:%s" % "release_1",
  1684. )
  1685. assert set(results) == {group_a}
  1686. results = self.make_query(
  1687. environments=[staging_env], search_filter_query="first_release:%s" % "release_1"
  1688. )
  1689. assert set(results) == {group_a}
  1690. results = self.make_query(
  1691. environments=[prod_env], search_filter_query="first_release:%s" % "release_1"
  1692. )
  1693. assert set(results) == set()
  1694. # query by release release_2
  1695. results = self.make_query(search_filter_query="first_release:%s" % "release_2")
  1696. assert set(results) == {group_a, group_c}
  1697. results = self.make_query(
  1698. environments=[staging_env, prod_env],
  1699. search_filter_query="first_release:%s" % "release_2",
  1700. )
  1701. assert set(results) == {group_a}
  1702. results = self.make_query(
  1703. environments=[staging_env], search_filter_query="first_release:%s" % "release_2"
  1704. )
  1705. assert set(results) == set()
  1706. results = self.make_query(
  1707. environments=[prod_env], search_filter_query="first_release:%s" % "release_2"
  1708. )
  1709. assert set(results) == {group_a}
  1710. def test_all_fields_do_not_error(self):
  1711. # Just a sanity check to make sure that all fields can be successfully
  1712. # searched on without returning type errors and other schema related
  1713. # issues.
  1714. def test_query(query):
  1715. try:
  1716. self.make_query(search_filter_query=query)
  1717. except SnubaError as e:
  1718. self.fail(f"Query {query} errored. Error info: {e}")
  1719. for key in SENTRY_SNUBA_MAP:
  1720. if key in ["project.id", "issue.id"]:
  1721. continue
  1722. test_query("has:%s" % key)
  1723. test_query("!has:%s" % key)
  1724. if key == "error.handled":
  1725. val = 1
  1726. elif key in issue_search_config.numeric_keys:
  1727. val = "123"
  1728. elif key in issue_search_config.date_keys:
  1729. val = "2019-01-01"
  1730. elif key in issue_search_config.boolean_keys:
  1731. val = "true"
  1732. else:
  1733. val = "abadcafedeadbeefdeaffeedabadfeed"
  1734. test_query(f"!{key}:{val}")
  1735. test_query(f"{key}:{val}")