test_backend.py 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911
  1. import uuid
  2. from datetime import datetime, timedelta
  3. from unittest import mock
  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.issues.grouptype import (
  11. ErrorGroupType,
  12. NoiseConfig,
  13. PerformanceNPlusOneGroupType,
  14. PerformanceRenderBlockingAssetSpanGroupType,
  15. )
  16. from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence
  17. from sentry.models import (
  18. Environment,
  19. Group,
  20. GroupAssignee,
  21. GroupBookmark,
  22. GroupEnvironment,
  23. GroupHistoryStatus,
  24. GroupStatus,
  25. GroupSubscription,
  26. Integration,
  27. record_group_history,
  28. )
  29. from sentry.models.groupowner import GroupOwner
  30. from sentry.search.snuba.backend import (
  31. CdcEventsDatasetSnubaSearchBackend,
  32. EventsDatasetSnubaSearchBackend,
  33. )
  34. from sentry.search.snuba.executors import InvalidQueryForExecutor
  35. from sentry.testutils import SnubaTestCase, TestCase, xfail_if_not_postgres
  36. from sentry.testutils.helpers import Feature
  37. from sentry.testutils.helpers.datetime import before_now, iso_format
  38. from sentry.utils.snuba import SENTRY_SNUBA_MAP, SnubaError
  39. from tests.sentry.issues.test_utils import OccurrenceTestMixin
  40. def date_to_query_format(date):
  41. return date.strftime("%Y-%m-%dT%H:%M:%S")
  42. class SharedSnubaTest(TestCase, SnubaTestCase):
  43. def build_search_filter(self, query, projects=None, user=None, environments=None):
  44. user = user if user is not None else self.user
  45. projects = projects if projects is not None else [self.project]
  46. return convert_query_values(parse_search_query(query), projects, user, environments)
  47. def make_query(
  48. self,
  49. projects=None,
  50. search_filter_query=None,
  51. environments=None,
  52. sort_by="date",
  53. limit=None,
  54. count_hits=False,
  55. date_from=None,
  56. date_to=None,
  57. cursor=None,
  58. ):
  59. search_filters = []
  60. projects = projects if projects is not None else [self.project]
  61. if search_filter_query is not None:
  62. search_filters = self.build_search_filter(
  63. search_filter_query, projects, environments=environments
  64. )
  65. kwargs = {}
  66. if limit is not None:
  67. kwargs["limit"] = limit
  68. return self.backend.query(
  69. projects,
  70. search_filters=search_filters,
  71. environments=environments,
  72. count_hits=count_hits,
  73. sort_by=sort_by,
  74. date_from=date_from,
  75. date_to=date_to,
  76. cursor=cursor,
  77. **kwargs,
  78. )
  79. def store_event(self, data, *args, **kwargs):
  80. event = super().store_event(data, *args, **kwargs)
  81. environment_name = data.get("environment")
  82. if environment_name:
  83. GroupEnvironment.objects.filter(
  84. group_id=event.group_id,
  85. environment__name=environment_name,
  86. first_seen__gt=event.datetime,
  87. ).update(first_seen=event.datetime)
  88. return event
  89. class EventsSnubaSearchTest(SharedSnubaTest):
  90. @property
  91. def backend(self):
  92. return EventsDatasetSnubaSearchBackend()
  93. def setUp(self):
  94. super().setUp()
  95. self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc)
  96. event1_timestamp = iso_format(self.base_datetime - timedelta(days=21))
  97. self.event1 = self.store_event(
  98. data={
  99. "fingerprint": ["put-me-in-group1"],
  100. "event_id": "a" * 32,
  101. "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.",
  102. "environment": "production",
  103. "tags": {"server": "example.com", "sentry:user": "event1@example.com"},
  104. "timestamp": event1_timestamp,
  105. "stacktrace": {"frames": [{"module": "group1"}]},
  106. },
  107. project_id=self.project.id,
  108. )
  109. self.event3 = self.store_event(
  110. data={
  111. "fingerprint": ["put-me-in-group1"],
  112. "event_id": "c" * 32,
  113. "message": "group1",
  114. "environment": "production",
  115. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  116. "timestamp": iso_format(self.base_datetime),
  117. "stacktrace": {"frames": [{"module": "group1"}]},
  118. },
  119. project_id=self.project.id,
  120. )
  121. self.group1 = Group.objects.get(id=self.event1.group.id)
  122. assert self.group1.id == self.event1.group.id
  123. assert self.group1.id == self.event3.group.id
  124. assert self.group1.first_seen == self.event1.datetime
  125. assert self.group1.last_seen == self.event3.datetime
  126. self.group1.times_seen = 5
  127. self.group1.status = GroupStatus.UNRESOLVED
  128. self.group1.update(type=ErrorGroupType.type_id)
  129. self.group1.save()
  130. self.store_group(self.group1)
  131. self.event2 = self.store_event(
  132. data={
  133. "fingerprint": ["put-me-in-group2"],
  134. "event_id": "b" * 32,
  135. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  136. "message": "bar",
  137. "stacktrace": {"frames": [{"module": "group2"}]},
  138. "environment": "staging",
  139. "tags": {
  140. "server": "example.com",
  141. "url": "http://example.com",
  142. "sentry:user": "event2@example.com",
  143. },
  144. },
  145. project_id=self.project.id,
  146. )
  147. self.group2 = Group.objects.get(id=self.event2.group.id)
  148. assert self.group2.id == self.event2.group.id
  149. assert self.group2.first_seen == self.group2.last_seen == self.event2.datetime
  150. self.group2.status = GroupStatus.RESOLVED
  151. self.group2.times_seen = 10
  152. self.group2.update(type=ErrorGroupType.type_id)
  153. self.group2.save()
  154. self.store_group(self.group2)
  155. GroupBookmark.objects.create(
  156. user_id=self.user.id, group=self.group2, project=self.group2.project
  157. )
  158. GroupAssignee.objects.create(
  159. user_id=self.user.id, group=self.group2, project=self.group2.project
  160. )
  161. GroupSubscription.objects.create(
  162. user_id=self.user.id, group=self.group1, project=self.group1.project, is_active=True
  163. )
  164. GroupSubscription.objects.create(
  165. user_id=self.user.id, group=self.group2, project=self.group2.project, is_active=False
  166. )
  167. self.environments = {
  168. "production": self.event1.get_environment(),
  169. "staging": self.event2.get_environment(),
  170. }
  171. def set_up_multi_project(self):
  172. self.project2 = self.create_project(organization=self.project.organization)
  173. self.event_p2 = self.store_event(
  174. data={
  175. "event_id": "a" * 32,
  176. "fingerprint": ["put-me-in-groupP2"],
  177. "timestamp": iso_format(self.base_datetime - timedelta(days=21)),
  178. "message": "foo",
  179. "stacktrace": {"frames": [{"module": "group_p2"}]},
  180. "tags": {"server": "example.com"},
  181. "environment": "production",
  182. },
  183. project_id=self.project2.id,
  184. )
  185. self.group_p2 = Group.objects.get(id=self.event_p2.group.id)
  186. self.group_p2.times_seen = 6
  187. self.group_p2.last_seen = self.base_datetime - timedelta(days=1)
  188. self.group_p2.save()
  189. self.store_group(self.group_p2)
  190. def create_group_with_integration_external_issue(self, environment="production"):
  191. event = self.store_event(
  192. data={
  193. "fingerprint": ["linked_group1"],
  194. "event_id": uuid.uuid4().hex,
  195. "timestamp": iso_format(self.base_datetime),
  196. "environment": environment,
  197. },
  198. project_id=self.project.id,
  199. )
  200. integration = Integration.objects.create(provider="example", name="Example")
  201. integration.add_organization(event.group.organization, self.user)
  202. self.create_integration_external_issue(
  203. group=event.group,
  204. integration=integration,
  205. key="APP-123",
  206. )
  207. return event.group
  208. def create_group_with_platform_external_issue(self, environment="production"):
  209. event = self.store_event(
  210. data={
  211. "fingerprint": ["linked_group2"],
  212. "event_id": uuid.uuid4().hex,
  213. "timestamp": iso_format(self.base_datetime),
  214. "environment": environment,
  215. },
  216. project_id=self.project.id,
  217. )
  218. self.create_platform_external_issue(
  219. group=event.group,
  220. service_type="sentry-app",
  221. display_name="App#issue-1",
  222. web_url="https://example.com/app/issues/1",
  223. )
  224. return event.group
  225. def run_test_query_in_syntax(
  226. self, query, expected_groups, expected_negative_groups=None, environments=None
  227. ):
  228. results = self.make_query(search_filter_query=query, environments=environments)
  229. sort_key = lambda result: result.id
  230. assert sorted(results, key=sort_key) == sorted(expected_groups, key=sort_key)
  231. if expected_negative_groups is not None:
  232. results = self.make_query(search_filter_query=f"!{query}")
  233. assert sorted(results, key=sort_key) == sorted(expected_negative_groups, key=sort_key)
  234. def test_query(self):
  235. results = self.make_query(search_filter_query="foo")
  236. assert set(results) == {self.group1}
  237. results = self.make_query(search_filter_query="bar")
  238. assert set(results) == {self.group2}
  239. def test_query_multi_project(self):
  240. self.set_up_multi_project()
  241. results = self.make_query([self.project, self.project2], search_filter_query="foo")
  242. assert set(results) == {self.group1, self.group_p2}
  243. def test_query_with_environment(self):
  244. results = self.make_query(
  245. environments=[self.environments["production"]], search_filter_query="foo"
  246. )
  247. assert set(results) == {self.group1}
  248. results = self.make_query(
  249. environments=[self.environments["production"]], search_filter_query="bar"
  250. )
  251. assert set(results) == set()
  252. results = self.make_query(
  253. environments=[self.environments["staging"]], search_filter_query="bar"
  254. )
  255. assert set(results) == {self.group2}
  256. def test_query_for_text_in_long_message(self):
  257. results = self.make_query(
  258. [self.project],
  259. environments=[self.environments["production"]],
  260. search_filter_query="santryrox",
  261. )
  262. assert set(results) == {self.group1}
  263. def test_multi_environments(self):
  264. self.set_up_multi_project()
  265. results = self.make_query(
  266. [self.project, self.project2],
  267. environments=[self.environments["production"], self.environments["staging"]],
  268. )
  269. assert set(results) == {self.group1, self.group2, self.group_p2}
  270. def test_query_with_environment_multi_project(self):
  271. self.set_up_multi_project()
  272. results = self.make_query(
  273. [self.project, self.project2],
  274. environments=[self.environments["production"]],
  275. search_filter_query="foo",
  276. )
  277. assert set(results) == {self.group1, self.group_p2}
  278. results = self.make_query(
  279. [self.project, self.project2],
  280. environments=[self.environments["production"]],
  281. search_filter_query="bar",
  282. )
  283. assert set(results) == set()
  284. def test_sort(self):
  285. results = self.make_query(sort_by="date")
  286. assert list(results) == [self.group1, self.group2]
  287. results = self.make_query(sort_by="new")
  288. assert list(results) == [self.group2, self.group1]
  289. results = self.make_query(sort_by="freq")
  290. assert list(results) == [self.group1, self.group2]
  291. results = self.make_query(sort_by="priority")
  292. assert list(results) == [self.group1, self.group2]
  293. results = self.make_query(sort_by="user")
  294. assert list(results) == [self.group1, self.group2]
  295. def test_sort_with_environment(self):
  296. for dt in [
  297. self.group1.first_seen + timedelta(days=1),
  298. self.group1.first_seen + timedelta(days=2),
  299. self.group1.last_seen + timedelta(days=1),
  300. ]:
  301. self.store_event(
  302. data={
  303. "fingerprint": ["put-me-in-group2"],
  304. "timestamp": iso_format(dt),
  305. "stacktrace": {"frames": [{"module": "group2"}]},
  306. "environment": "production",
  307. "message": "group2",
  308. },
  309. project_id=self.project.id,
  310. )
  311. results = self.make_query(environments=[self.environments["production"]], sort_by="date")
  312. assert list(results) == [self.group2, self.group1]
  313. results = self.make_query(environments=[self.environments["production"]], sort_by="new")
  314. assert list(results) == [self.group2, self.group1]
  315. results = self.make_query(environments=[self.environments["production"]], sort_by="freq")
  316. assert list(results) == [self.group2, self.group1]
  317. results = self.make_query(
  318. environments=[self.environments["production"]], sort_by="priority"
  319. )
  320. assert list(results) == [self.group2, self.group1]
  321. results = self.make_query(environments=[self.environments["production"]], sort_by="user")
  322. assert list(results) == [self.group1, self.group2]
  323. def test_status(self):
  324. results = self.make_query(search_filter_query="is:unresolved")
  325. assert set(results) == {self.group1}
  326. results = self.make_query(search_filter_query="is:resolved")
  327. assert set(results) == {self.group2}
  328. event_3 = self.store_event(
  329. data={
  330. "fingerprint": ["put-me-in-group3"],
  331. "event_id": "c" * 32,
  332. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  333. },
  334. project_id=self.project.id,
  335. )
  336. group_3 = event_3.group
  337. group_3.status = GroupStatus.MUTED
  338. group_3.save()
  339. self.run_test_query_in_syntax(
  340. "status:[unresolved, resolved]", [self.group1, self.group2], [group_3]
  341. )
  342. self.run_test_query_in_syntax(
  343. "status:[resolved, muted]", [self.group2, group_3], [self.group1]
  344. )
  345. def test_category(self):
  346. results = self.make_query(search_filter_query="issue.category:error")
  347. assert set(results) == {self.group1, self.group2}
  348. event_3 = self.store_event(
  349. data={
  350. "fingerprint": ["put-me-in-group3"],
  351. "event_id": "c" * 32,
  352. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  353. },
  354. project_id=self.project.id,
  355. )
  356. group_3 = event_3.group
  357. group_3.update(type=PerformanceNPlusOneGroupType.type_id)
  358. results = self.make_query(search_filter_query="issue.category:performance")
  359. assert set(results) == {group_3}
  360. results = self.make_query(search_filter_query="issue.category:[error, performance]")
  361. assert set(results) == {self.group1, self.group2, group_3}
  362. with pytest.raises(InvalidSearchQuery):
  363. self.make_query(search_filter_query="issue.category:hellboy")
  364. def test_not_perf_category(self):
  365. results = self.make_query(search_filter_query="issue.category:error foo")
  366. assert set(results) == {self.group1}
  367. not_results = self.make_query(search_filter_query="!issue.category:performance foo")
  368. assert set(not_results) == {self.group1}
  369. def test_type(self):
  370. results = self.make_query(search_filter_query="issue.type:error")
  371. assert set(results) == {self.group1, self.group2}
  372. event_3 = self.store_event(
  373. data={
  374. "fingerprint": ["put-me-in-group3"],
  375. "event_id": "c" * 32,
  376. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  377. "type": PerformanceNPlusOneGroupType.type_id,
  378. },
  379. project_id=self.project.id,
  380. )
  381. group_3 = event_3.group
  382. group_3.update(type=PerformanceNPlusOneGroupType.type_id)
  383. results = self.make_query(
  384. search_filter_query="issue.type:performance_n_plus_one_db_queries"
  385. )
  386. assert set(results) == {group_3}
  387. event_4 = self.store_event(
  388. data={
  389. "fingerprint": ["put-me-in-group4"],
  390. "event_id": "d" * 32,
  391. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  392. },
  393. project_id=self.project.id,
  394. )
  395. group_4 = event_4.group
  396. group_4.update(type=PerformanceRenderBlockingAssetSpanGroupType.type_id)
  397. results = self.make_query(
  398. search_filter_query="issue.type:performance_render_blocking_asset_span"
  399. )
  400. assert set(results) == {group_4}
  401. results = self.make_query(
  402. search_filter_query="issue.type:[performance_render_blocking_asset_span, performance_n_plus_one_db_queries, error]"
  403. )
  404. assert set(results) == {self.group1, self.group2, group_3, group_4}
  405. with pytest.raises(InvalidSearchQuery):
  406. self.make_query(search_filter_query="issue.type:performance_i_dont_exist")
  407. def test_status_with_environment(self):
  408. results = self.make_query(
  409. environments=[self.environments["production"]], search_filter_query="is:unresolved"
  410. )
  411. assert set(results) == {self.group1}
  412. results = self.make_query(
  413. environments=[self.environments["staging"]], search_filter_query="is:resolved"
  414. )
  415. assert set(results) == {self.group2}
  416. results = self.make_query(
  417. environments=[self.environments["production"]], search_filter_query="is:resolved"
  418. )
  419. assert set(results) == set()
  420. def test_tags(self):
  421. results = self.make_query(search_filter_query="environment:staging")
  422. assert set(results) == {self.group2}
  423. results = self.make_query(search_filter_query="environment:example.com")
  424. assert set(results) == set()
  425. results = self.make_query(search_filter_query="has:environment")
  426. assert set(results) == {self.group2, self.group1}
  427. results = self.make_query(search_filter_query="environment:staging server:example.com")
  428. assert set(results) == {self.group2}
  429. results = self.make_query(search_filter_query='url:"http://example.com"')
  430. assert set(results) == {self.group2}
  431. results = self.make_query(search_filter_query="environment:staging has:server")
  432. assert set(results) == {self.group2}
  433. results = self.make_query(search_filter_query="environment:staging server:bar.example.com")
  434. assert set(results) == set()
  435. def test_tags_with_environment(self):
  436. results = self.make_query(
  437. environments=[self.environments["production"]], search_filter_query="server:example.com"
  438. )
  439. assert set(results) == {self.group1}
  440. results = self.make_query(
  441. environments=[self.environments["staging"]], search_filter_query="server:example.com"
  442. )
  443. assert set(results) == {self.group2}
  444. results = self.make_query(
  445. environments=[self.environments["staging"]], search_filter_query="has:server"
  446. )
  447. assert set(results) == {self.group2}
  448. results = self.make_query(
  449. environments=[self.environments["production"]],
  450. search_filter_query='url:"http://example.com"',
  451. )
  452. assert set(results) == set()
  453. results = self.make_query(
  454. environments=[self.environments["staging"]],
  455. search_filter_query='url:"http://example.com"',
  456. )
  457. assert set(results) == {self.group2}
  458. results = self.make_query(
  459. environments=[self.environments["staging"]],
  460. search_filter_query="server:bar.example.com",
  461. )
  462. assert set(results) == set()
  463. def test_bookmarked_by(self):
  464. results = self.make_query(search_filter_query="bookmarks:%s" % self.user.username)
  465. assert set(results) == {self.group2}
  466. def test_bookmarked_by_in_syntax(self):
  467. self.run_test_query_in_syntax(
  468. f"bookmarks:[{self.user.username}]", [self.group2], [self.group1]
  469. )
  470. user_2 = self.create_user()
  471. GroupBookmark.objects.create(
  472. user_id=user_2.id, group=self.group1, project=self.group2.project
  473. )
  474. self.run_test_query_in_syntax(
  475. f"bookmarks:[{self.user.username}, {user_2.username}]", [self.group2, self.group1], []
  476. )
  477. def test_bookmarked_by_with_environment(self):
  478. results = self.make_query(
  479. environments=[self.environments["staging"]],
  480. search_filter_query="bookmarks:%s" % self.user.username,
  481. )
  482. assert set(results) == {self.group2}
  483. results = self.make_query(
  484. environments=[self.environments["production"]],
  485. search_filter_query="bookmarks:%s" % self.user.username,
  486. )
  487. assert set(results) == set()
  488. def test_search_filter_query_with_custom_priority_tag(self):
  489. priority = "high"
  490. self.store_event(
  491. data={
  492. "fingerprint": ["put-me-in-group2"],
  493. "timestamp": iso_format(self.group2.first_seen + timedelta(days=1)),
  494. "stacktrace": {"frames": [{"module": "group2"}]},
  495. "message": "group2",
  496. "tags": {"priority": priority},
  497. },
  498. project_id=self.project.id,
  499. )
  500. results = self.make_query(search_filter_query="priority:%s" % priority)
  501. assert set(results) == {self.group2}
  502. def test_search_filter_query_with_custom_priority_tag_and_priority_sort(self):
  503. priority = "high"
  504. for i in range(1, 3):
  505. self.store_event(
  506. data={
  507. "fingerprint": ["put-me-in-group1"],
  508. "timestamp": iso_format(self.group2.last_seen + timedelta(days=i)),
  509. "stacktrace": {"frames": [{"module": "group1"}]},
  510. "message": "group1",
  511. "tags": {"priority": priority},
  512. },
  513. project_id=self.project.id,
  514. )
  515. self.store_event(
  516. data={
  517. "fingerprint": ["put-me-in-group2"],
  518. "timestamp": iso_format(self.group2.last_seen + timedelta(days=2)),
  519. "stacktrace": {"frames": [{"module": "group2"}]},
  520. "message": "group2",
  521. "tags": {"priority": priority},
  522. },
  523. project_id=self.project.id,
  524. )
  525. results = self.make_query(search_filter_query="priority:%s" % priority, sort_by="priority")
  526. assert list(results) == [self.group1, self.group2]
  527. def test_search_tag_overlapping_with_internal_fields(self):
  528. # Using a tag of email overlaps with the promoted user.email column in events.
  529. # We don't want to bypass public schema limits in issue search.
  530. self.store_event(
  531. data={
  532. "fingerprint": ["put-me-in-group2"],
  533. "timestamp": iso_format(self.group2.first_seen + timedelta(days=1)),
  534. "stacktrace": {"frames": [{"module": "group2"}]},
  535. "message": "group2",
  536. "tags": {"email": "tags@example.com"},
  537. },
  538. project_id=self.project.id,
  539. )
  540. results = self.make_query(search_filter_query="email:tags@example.com")
  541. assert set(results) == {self.group2}
  542. def test_project(self):
  543. results = self.make_query([self.create_project(name="other")])
  544. assert set(results) == set()
  545. def test_pagination(self):
  546. for options_set in [
  547. {"snuba.search.min-pre-snuba-candidates": None},
  548. {"snuba.search.min-pre-snuba-candidates": 500},
  549. ]:
  550. with self.options(options_set):
  551. results = self.backend.query([self.project], limit=1, sort_by="date")
  552. assert set(results) == {self.group1}
  553. assert not results.prev.has_results
  554. assert results.next.has_results
  555. results = self.backend.query(
  556. [self.project], cursor=results.next, limit=1, sort_by="date"
  557. )
  558. assert set(results) == {self.group2}
  559. assert results.prev.has_results
  560. assert not results.next.has_results
  561. # note: previous cursor
  562. results = self.backend.query(
  563. [self.project], cursor=results.prev, limit=1, sort_by="date"
  564. )
  565. assert set(results) == {self.group1}
  566. assert results.prev.has_results
  567. assert results.next.has_results
  568. # note: previous cursor, paging too far into 0 results
  569. results = self.backend.query(
  570. [self.project], cursor=results.prev, limit=1, sort_by="date"
  571. )
  572. assert set(results) == set()
  573. assert not results.prev.has_results
  574. assert results.next.has_results
  575. results = self.backend.query(
  576. [self.project], cursor=results.next, limit=1, sort_by="date"
  577. )
  578. assert set(results) == {self.group1}
  579. assert results.prev.has_results
  580. assert results.next.has_results
  581. results = self.backend.query(
  582. [self.project], cursor=results.next, limit=1, sort_by="date"
  583. )
  584. assert set(results) == {self.group2}
  585. assert results.prev.has_results
  586. assert not results.next.has_results
  587. results = self.backend.query(
  588. [self.project], cursor=results.next, limit=1, sort_by="date"
  589. )
  590. assert set(results) == set()
  591. assert results.prev.has_results
  592. assert not results.next.has_results
  593. def test_pagination_with_environment(self):
  594. for dt in [
  595. self.group1.first_seen + timedelta(days=1),
  596. self.group1.first_seen + timedelta(days=2),
  597. self.group1.last_seen + timedelta(days=1),
  598. ]:
  599. self.store_event(
  600. data={
  601. "fingerprint": ["put-me-in-group2"],
  602. "timestamp": iso_format(dt),
  603. "environment": "production",
  604. "message": "group2",
  605. "stacktrace": {"frames": [{"module": "group2"}]},
  606. },
  607. project_id=self.project.id,
  608. )
  609. results = self.backend.query(
  610. [self.project],
  611. environments=[self.environments["production"]],
  612. sort_by="date",
  613. limit=1,
  614. count_hits=True,
  615. )
  616. assert list(results) == [self.group2]
  617. assert results.hits == 2
  618. results = self.backend.query(
  619. [self.project],
  620. environments=[self.environments["production"]],
  621. sort_by="date",
  622. limit=1,
  623. cursor=results.next,
  624. count_hits=True,
  625. )
  626. assert list(results) == [self.group1]
  627. assert results.hits == 2
  628. results = self.backend.query(
  629. [self.project],
  630. environments=[self.environments["production"]],
  631. sort_by="date",
  632. limit=1,
  633. cursor=results.next,
  634. count_hits=True,
  635. )
  636. assert list(results) == []
  637. assert results.hits == 2
  638. def test_age_filter(self):
  639. results = self.make_query(
  640. search_filter_query="firstSeen:>=%s" % date_to_query_format(self.group2.first_seen)
  641. )
  642. assert set(results) == {self.group2}
  643. results = self.make_query(
  644. search_filter_query="firstSeen:<=%s"
  645. % date_to_query_format(self.group1.first_seen + timedelta(minutes=1))
  646. )
  647. assert set(results) == {self.group1}
  648. results = self.make_query(
  649. search_filter_query="firstSeen:>=%s firstSeen:<=%s"
  650. % (
  651. date_to_query_format(self.group1.first_seen),
  652. date_to_query_format(self.group1.first_seen + timedelta(minutes=1)),
  653. )
  654. )
  655. assert set(results) == {self.group1}
  656. def test_age_filter_with_environment(self):
  657. # add time instead to make it greater than or less than as needed.
  658. group1_first_seen = GroupEnvironment.objects.get(
  659. environment=self.environments["production"], group=self.group1
  660. ).first_seen
  661. results = self.make_query(
  662. environments=[self.environments["production"]],
  663. search_filter_query="firstSeen:>=%s" % date_to_query_format(group1_first_seen),
  664. )
  665. assert set(results) == {self.group1}
  666. results = self.make_query(
  667. environments=[self.environments["production"]],
  668. search_filter_query="firstSeen:<=%s" % date_to_query_format(group1_first_seen),
  669. )
  670. assert set(results) == {self.group1}
  671. results = self.make_query(
  672. environments=[self.environments["production"]],
  673. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  674. )
  675. assert set(results) == set()
  676. self.store_event(
  677. data={
  678. "fingerprint": ["put-me-in-group1"],
  679. "timestamp": iso_format(group1_first_seen + timedelta(days=1)),
  680. "message": "group1",
  681. "stacktrace": {"frames": [{"module": "group1"}]},
  682. "environment": "development",
  683. },
  684. project_id=self.project.id,
  685. )
  686. results = self.make_query(
  687. environments=[self.environments["production"]],
  688. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  689. )
  690. assert set(results) == set()
  691. results = self.make_query(
  692. environments=[Environment.objects.get(name="development")],
  693. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  694. )
  695. assert set(results) == {self.group1}
  696. def test_times_seen_filter(self):
  697. results = self.make_query([self.project], search_filter_query="times_seen:2")
  698. assert set(results) == {self.group1}
  699. results = self.make_query([self.project], search_filter_query="times_seen:>=2")
  700. assert set(results) == {self.group1}
  701. results = self.make_query([self.project], search_filter_query="times_seen:<=1")
  702. assert set(results) == {self.group2}
  703. def test_last_seen_filter(self):
  704. results = self.make_query(
  705. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen)
  706. )
  707. assert set(results) == {self.group1}
  708. results = self.make_query(
  709. search_filter_query="lastSeen:>=%s lastSeen:<=%s"
  710. % (
  711. date_to_query_format(self.group1.last_seen),
  712. date_to_query_format(self.group1.last_seen + timedelta(minutes=1)),
  713. )
  714. )
  715. assert set(results) == {self.group1}
  716. def test_last_seen_filter_with_environment(self):
  717. results = self.make_query(
  718. environments=[self.environments["production"]],
  719. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  720. )
  721. assert set(results) == {self.group1}
  722. results = self.make_query(
  723. environments=[self.environments["production"]],
  724. search_filter_query="lastSeen:<=%s" % date_to_query_format(self.group1.last_seen),
  725. )
  726. assert set(results) == {self.group1}
  727. results = self.make_query(
  728. environments=[self.environments["production"]],
  729. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  730. )
  731. assert set(results) == set()
  732. self.store_event(
  733. data={
  734. "fingerprint": ["put-me-in-group1"],
  735. "timestamp": iso_format(self.group1.last_seen + timedelta(days=1)),
  736. "message": "group1",
  737. "stacktrace": {"frames": [{"module": "group1"}]},
  738. "environment": "development",
  739. },
  740. project_id=self.project.id,
  741. )
  742. self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1))
  743. results = self.make_query(
  744. environments=[self.environments["production"]],
  745. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  746. )
  747. assert set(results) == set()
  748. results = self.make_query(
  749. environments=[Environment.objects.get(name="development")],
  750. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  751. )
  752. assert set(results) == set()
  753. results = self.make_query(
  754. environments=[Environment.objects.get(name="development")],
  755. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  756. )
  757. assert set(results) == {self.group1}
  758. def test_date_filter(self):
  759. results = self.make_query(
  760. date_from=self.event2.datetime,
  761. search_filter_query="timestamp:>=%s" % date_to_query_format(self.event2.datetime),
  762. )
  763. assert set(results) == {self.group1, self.group2}
  764. results = self.make_query(
  765. date_to=self.event1.datetime + timedelta(minutes=1),
  766. search_filter_query="timestamp:<=%s"
  767. % date_to_query_format(self.event1.datetime + timedelta(minutes=1)),
  768. )
  769. assert set(results) == {self.group1}
  770. results = self.make_query(
  771. date_from=self.event1.datetime,
  772. date_to=self.event2.datetime + timedelta(minutes=1),
  773. search_filter_query="timestamp:>=%s timestamp:<=%s"
  774. % (
  775. date_to_query_format(self.event1.datetime),
  776. date_to_query_format(self.event2.datetime + timedelta(minutes=1)),
  777. ),
  778. )
  779. assert set(results) == {self.group1, self.group2}
  780. # Test with `Z` utc marker, should be equivalent
  781. results = self.make_query(
  782. date_from=self.event1.datetime,
  783. date_to=self.event2.datetime + timedelta(minutes=1),
  784. search_filter_query="timestamp:>=%s timestamp:<=%s"
  785. % (
  786. date_to_query_format(self.event1.datetime) + "Z",
  787. date_to_query_format(self.event2.datetime + timedelta(minutes=1)) + "Z",
  788. ),
  789. )
  790. assert set(results) == {self.group1, self.group2}
  791. def test_date_filter_with_environment(self):
  792. results = self.backend.query(
  793. [self.project],
  794. environments=[self.environments["production"]],
  795. date_from=self.event2.datetime,
  796. )
  797. assert set(results) == {self.group1}
  798. results = self.backend.query(
  799. [self.project],
  800. environments=[self.environments["production"]],
  801. date_to=self.event1.datetime + timedelta(minutes=1),
  802. )
  803. assert set(results) == {self.group1}
  804. results = self.backend.query(
  805. [self.project],
  806. environments=[self.environments["staging"]],
  807. date_from=self.event1.datetime,
  808. date_to=self.event2.datetime + timedelta(minutes=1),
  809. )
  810. assert set(results) == {self.group2}
  811. def test_linked(self):
  812. linked_group1 = self.create_group_with_integration_external_issue()
  813. linked_group2 = self.create_group_with_platform_external_issue()
  814. results = self.make_query(search_filter_query="is:unlinked")
  815. assert set(results) == {self.group1, self.group2}
  816. results = self.make_query(search_filter_query="is:linked")
  817. assert set(results) == {linked_group1, linked_group2}
  818. def test_linked_with_only_integration_external_issue(self):
  819. linked_group = self.create_group_with_integration_external_issue()
  820. results = self.make_query(search_filter_query="is:unlinked")
  821. assert set(results) == {self.group1, self.group2}
  822. results = self.make_query(search_filter_query="is:linked")
  823. assert set(results) == {linked_group}
  824. def test_linked_with_only_platform_external_issue(self):
  825. linked_group = self.create_group_with_platform_external_issue()
  826. results = self.make_query(search_filter_query="is:unlinked")
  827. assert set(results) == {self.group1, self.group2}
  828. results = self.make_query(search_filter_query="is:linked")
  829. assert set(results) == {linked_group}
  830. def test_linked_with_environment(self):
  831. linked_group1 = self.create_group_with_integration_external_issue(environment="production")
  832. linked_group2 = self.create_group_with_platform_external_issue(environment="staging")
  833. results = self.make_query(
  834. environments=[self.environments["production"]], search_filter_query="is:unlinked"
  835. )
  836. assert set(results) == {self.group1}
  837. results = self.make_query(
  838. environments=[self.environments["staging"]], search_filter_query="is:unlinked"
  839. )
  840. assert set(results) == {self.group2}
  841. results = self.make_query(
  842. environments=[self.environments["production"]], search_filter_query="is:linked"
  843. )
  844. assert set(results) == {linked_group1}
  845. results = self.make_query(
  846. environments=[self.environments["staging"]], search_filter_query="is:linked"
  847. )
  848. assert set(results) == {linked_group2}
  849. def test_unassigned(self):
  850. results = self.make_query(search_filter_query="is:unassigned")
  851. assert set(results) == {self.group1}
  852. results = self.make_query(search_filter_query="is:assigned")
  853. assert set(results) == {self.group2}
  854. def test_unassigned_with_environment(self):
  855. results = self.make_query(
  856. environments=[self.environments["production"]], search_filter_query="is:unassigned"
  857. )
  858. assert set(results) == {self.group1}
  859. results = self.make_query(
  860. environments=[self.environments["staging"]], search_filter_query="is:assigned"
  861. )
  862. assert set(results) == {self.group2}
  863. results = self.make_query(
  864. environments=[self.environments["production"]], search_filter_query="is:assigned"
  865. )
  866. assert set(results) == set()
  867. def test_assigned_to(self):
  868. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  869. assert set(results) == {self.group2}
  870. # test team assignee
  871. ga = GroupAssignee.objects.get(
  872. user_id=self.user.id, group=self.group2, project=self.group2.project
  873. )
  874. ga.update(team=self.team, user_id=None)
  875. assert GroupAssignee.objects.get(id=ga.id).user_id is None
  876. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  877. assert set(results) == {self.group2}
  878. # test when there should be no results
  879. other_user = self.create_user()
  880. results = self.make_query(search_filter_query="assigned:%s" % other_user.username)
  881. assert set(results) == set()
  882. owner = self.create_user()
  883. self.create_member(
  884. organization=self.project.organization, user=owner, role="owner", teams=[]
  885. )
  886. # test that owners don't see results for all teams
  887. results = self.make_query(search_filter_query="assigned:%s" % owner.username)
  888. assert set(results) == set()
  889. def test_assigned_to_in_syntax(self):
  890. group_3 = self.store_event(
  891. data={
  892. "fingerprint": ["put-me-in-group3"],
  893. "event_id": "c" * 32,
  894. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  895. },
  896. project_id=self.project.id,
  897. ).group
  898. group_3.status = GroupStatus.MUTED
  899. group_3.save()
  900. other_user = self.create_user()
  901. self.run_test_query_in_syntax(
  902. f"assigned:[{self.user.username}, {other_user.username}]",
  903. [self.group2],
  904. [self.group1, group_3],
  905. )
  906. GroupAssignee.objects.create(project=self.project, group=group_3, user_id=other_user.id)
  907. self.run_test_query_in_syntax(
  908. f"assigned:[{self.user.username}, {other_user.username}]",
  909. [self.group2, group_3],
  910. [self.group1],
  911. )
  912. self.run_test_query_in_syntax(
  913. f"assigned:[#{self.team.slug}, {other_user.username}]",
  914. [group_3],
  915. [self.group1, self.group2],
  916. )
  917. ga_2 = GroupAssignee.objects.get(
  918. user_id=self.user.id, group=self.group2, project=self.group2.project
  919. )
  920. ga_2.update(team=self.team, user_id=None)
  921. self.run_test_query_in_syntax(
  922. f"assigned:[{self.user.username}, {other_user.username}]",
  923. [self.group2, group_3],
  924. [self.group1],
  925. )
  926. self.run_test_query_in_syntax(
  927. f"assigned:[#{self.team.slug}, {other_user.username}]",
  928. [self.group2, group_3],
  929. [self.group1],
  930. )
  931. self.run_test_query_in_syntax(
  932. f"assigned:[me, none, {other_user.username}]",
  933. [self.group1, self.group2, group_3],
  934. [],
  935. )
  936. def test_assigned_or_suggested_in_syntax(self):
  937. Group.objects.all().delete()
  938. group = self.store_event(
  939. data={
  940. "timestamp": iso_format(before_now(seconds=180)),
  941. "fingerprint": ["group-1"],
  942. },
  943. project_id=self.project.id,
  944. ).group
  945. group1 = self.store_event(
  946. data={
  947. "timestamp": iso_format(before_now(seconds=185)),
  948. "fingerprint": ["group-2"],
  949. },
  950. project_id=self.project.id,
  951. ).group
  952. group2 = self.store_event(
  953. data={
  954. "timestamp": iso_format(before_now(seconds=190)),
  955. "fingerprint": ["group-3"],
  956. },
  957. project_id=self.project.id,
  958. ).group
  959. assigned_group = self.store_event(
  960. data={
  961. "timestamp": iso_format(before_now(seconds=195)),
  962. "fingerprint": ["group-4"],
  963. },
  964. project_id=self.project.id,
  965. ).group
  966. assigned_to_other_group = self.store_event(
  967. data={
  968. "timestamp": iso_format(before_now(seconds=195)),
  969. "fingerprint": ["group-5"],
  970. },
  971. project_id=self.project.id,
  972. ).group
  973. self.run_test_query_in_syntax(
  974. "assigned_or_suggested:[me]",
  975. [],
  976. [group, group1, group2, assigned_group, assigned_to_other_group],
  977. )
  978. GroupOwner.objects.create(
  979. group=assigned_to_other_group,
  980. project=self.project,
  981. organization=self.organization,
  982. type=0,
  983. team_id=None,
  984. user_id=self.user.id,
  985. )
  986. GroupOwner.objects.create(
  987. group=group,
  988. project=self.project,
  989. organization=self.organization,
  990. type=0,
  991. team_id=None,
  992. user_id=self.user.id,
  993. )
  994. self.run_test_query_in_syntax(
  995. "assigned_or_suggested:[me]",
  996. [group, assigned_to_other_group],
  997. [group1, group2, assigned_group],
  998. )
  999. # 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)
  1000. other_user = self.create_user("other@user.com", is_superuser=False)
  1001. GroupAssignee.objects.create(
  1002. group=assigned_to_other_group,
  1003. project=self.project,
  1004. user_id=other_user.id,
  1005. )
  1006. self.run_test_query_in_syntax(
  1007. "assigned_or_suggested:[me]",
  1008. [group],
  1009. [group1, group2, assigned_group, assigned_to_other_group],
  1010. )
  1011. self.run_test_query_in_syntax(
  1012. f"assigned_or_suggested:[{other_user.email}]",
  1013. [assigned_to_other_group],
  1014. [group, group1, group2, assigned_group],
  1015. )
  1016. GroupAssignee.objects.create(
  1017. group=assigned_group, project=self.project, user_id=self.user.id
  1018. )
  1019. self.run_test_query_in_syntax(
  1020. f"assigned_or_suggested:[{self.user.email}]",
  1021. [assigned_group, group],
  1022. )
  1023. GroupOwner.objects.create(
  1024. group=group,
  1025. project=self.project,
  1026. organization=self.organization,
  1027. type=0,
  1028. team_id=self.team.id,
  1029. user_id=None,
  1030. )
  1031. self.run_test_query_in_syntax(
  1032. f"assigned_or_suggested:[#{self.team.slug}]",
  1033. [group],
  1034. )
  1035. self.run_test_query_in_syntax(
  1036. "assigned_or_suggested:[me, none]",
  1037. [group, group1, group2, assigned_group],
  1038. [assigned_to_other_group],
  1039. )
  1040. not_me = self.create_user(email="notme@sentry.io")
  1041. GroupOwner.objects.create(
  1042. group=group2,
  1043. project=self.project,
  1044. organization=self.organization,
  1045. type=0,
  1046. team_id=None,
  1047. user_id=not_me.id,
  1048. )
  1049. self.run_test_query_in_syntax(
  1050. "assigned_or_suggested:[me, none]",
  1051. [group, group1, assigned_group],
  1052. [assigned_to_other_group, group2],
  1053. )
  1054. GroupOwner.objects.filter(group=group, user_id=self.user.id).delete()
  1055. self.run_test_query_in_syntax(
  1056. f"assigned_or_suggested:[me, none, #{self.team.slug}]",
  1057. [group, group1, assigned_group],
  1058. [assigned_to_other_group, group2],
  1059. )
  1060. self.run_test_query_in_syntax(
  1061. f"assigned_or_suggested:[me, none, #{self.team.slug}, {not_me.email}]",
  1062. [group, group1, assigned_group, group2],
  1063. [assigned_to_other_group],
  1064. )
  1065. def test_assigned_to_with_environment(self):
  1066. results = self.make_query(
  1067. environments=[self.environments["staging"]],
  1068. search_filter_query="assigned:%s" % self.user.username,
  1069. )
  1070. assert set(results) == {self.group2}
  1071. results = self.make_query(
  1072. environments=[self.environments["production"]],
  1073. search_filter_query="assigned:%s" % self.user.username,
  1074. )
  1075. assert set(results) == set()
  1076. def test_subscribed_by(self):
  1077. results = self.make_query(
  1078. [self.group1.project], search_filter_query="subscribed:%s" % self.user.username
  1079. )
  1080. assert set(results) == {self.group1}
  1081. def test_subscribed_by_in_syntax(self):
  1082. self.run_test_query_in_syntax(
  1083. f"subscribed:[{self.user.username}]", [self.group1], [self.group2]
  1084. )
  1085. user_2 = self.create_user()
  1086. GroupSubscription.objects.create(
  1087. user_id=user_2.id, group=self.group2, project=self.project, is_active=True
  1088. )
  1089. self.run_test_query_in_syntax(
  1090. f"subscribed:[{self.user.username}, {user_2.username}]", [self.group1, self.group2], []
  1091. )
  1092. def test_subscribed_by_with_environment(self):
  1093. results = self.make_query(
  1094. [self.group1.project],
  1095. environments=[self.environments["production"]],
  1096. search_filter_query="subscribed:%s" % self.user.username,
  1097. )
  1098. assert set(results) == {self.group1}
  1099. results = self.make_query(
  1100. [self.group1.project],
  1101. environments=[self.environments["staging"]],
  1102. search_filter_query="subscribed:%s" % self.user.username,
  1103. )
  1104. assert set(results) == set()
  1105. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1106. def test_snuba_not_called_optimization(self, query_mock):
  1107. assert self.make_query(search_filter_query="status:unresolved").results == [self.group1]
  1108. assert not query_mock.called
  1109. assert (
  1110. self.make_query(
  1111. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1112. sort_by="date",
  1113. ).results
  1114. == []
  1115. )
  1116. assert query_mock.called
  1117. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1118. def test_reduce_bulk_results_none_total(self, bulk_raw_query_mock):
  1119. bulk_raw_query_mock.return_value = [
  1120. {"data": [], "totals": {"total": None}},
  1121. {"data": [], "totals": {"total": None}},
  1122. ]
  1123. assert (
  1124. self.make_query(
  1125. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1126. sort_by="date",
  1127. ).results
  1128. == []
  1129. )
  1130. assert bulk_raw_query_mock.called
  1131. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1132. def test_reduce_bulk_results_none_data(self, bulk_raw_query_mock):
  1133. bulk_raw_query_mock.return_value = [
  1134. {"data": None, "totals": {"total": 0}},
  1135. {"data": None, "totals": {"total": 0}},
  1136. ]
  1137. assert (
  1138. self.make_query(
  1139. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1140. sort_by="date",
  1141. ).results
  1142. == []
  1143. )
  1144. assert bulk_raw_query_mock.called
  1145. def test_pre_and_post_filtering(self):
  1146. prev_max_pre = options.get("snuba.search.max-pre-snuba-candidates")
  1147. options.set("snuba.search.max-pre-snuba-candidates", 1)
  1148. try:
  1149. # normal queries work as expected
  1150. results = self.make_query(search_filter_query="foo")
  1151. assert set(results) == {self.group1}
  1152. results = self.make_query(search_filter_query="bar")
  1153. assert set(results) == {self.group2}
  1154. # no candidate matches in Sentry, immediately return empty paginator
  1155. results = self.make_query(search_filter_query="NO MATCHES IN SENTRY")
  1156. assert set(results) == set()
  1157. # too many candidates, skip pre-filter, requires >1 postfilter queries
  1158. results = self.make_query()
  1159. assert set(results) == {self.group1, self.group2}
  1160. finally:
  1161. options.set("snuba.search.max-pre-snuba-candidates", prev_max_pre)
  1162. def test_optimizer_enabled(self):
  1163. prev_optimizer_enabled = options.get("snuba.search.pre-snuba-candidates-optimizer")
  1164. options.set("snuba.search.pre-snuba-candidates-optimizer", True)
  1165. try:
  1166. results = self.make_query(
  1167. search_filter_query="server:example.com",
  1168. environments=[self.environments["production"]],
  1169. )
  1170. assert set(results) == {self.group1}
  1171. finally:
  1172. options.set("snuba.search.pre-snuba-candidates-optimizer", prev_optimizer_enabled)
  1173. def test_search_out_of_range(self):
  1174. the_date = datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc)
  1175. results = self.make_query(
  1176. search_filter_query=f"event.timestamp:>{the_date} event.timestamp:<{the_date}",
  1177. date_from=the_date,
  1178. date_to=the_date,
  1179. )
  1180. assert set(results) == set()
  1181. def test_regressed_in_release(self):
  1182. # expect no groups within the results since there are no releases
  1183. results = self.make_query(search_filter_query="regressed_in_release:fake")
  1184. assert set(results) == set()
  1185. # expect no groups even though there is a release; since no group regressed in this release
  1186. release_1 = self.create_release()
  1187. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1188. assert set(results) == set()
  1189. # Create a new event so that we get a group in this release
  1190. group = self.store_event(
  1191. data={
  1192. "release": release_1.version,
  1193. },
  1194. project_id=self.project.id,
  1195. ).group
  1196. # # Should still be no group since we didn't regress in this release
  1197. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1198. assert set(results) == set()
  1199. record_group_history(group, GroupHistoryStatus.REGRESSED, release=release_1)
  1200. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1201. assert set(results) == {group}
  1202. # Make sure this works correctly with multiple releases
  1203. release_2 = self.create_release()
  1204. group_2 = self.store_event(
  1205. data={
  1206. "fingerprint": ["put-me-in-group9001"],
  1207. "event_id": "a" * 32,
  1208. "release": release_2.version,
  1209. },
  1210. project_id=self.project.id,
  1211. ).group
  1212. record_group_history(group_2, GroupHistoryStatus.REGRESSED, release=release_2)
  1213. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1214. assert set(results) == {group}
  1215. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_2.version)
  1216. assert set(results) == {group_2}
  1217. def test_first_release(self):
  1218. # expect no groups within the results since there are no releases
  1219. results = self.make_query(search_filter_query="first_release:%s" % "fake")
  1220. assert set(results) == set()
  1221. # expect no groups even though there is a release; since no group
  1222. # is attached to a release
  1223. release_1 = self.create_release(self.project)
  1224. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1225. assert set(results) == set()
  1226. # Create a new event so that we get a group in this release
  1227. group = self.store_event(
  1228. data={
  1229. "fingerprint": ["put-me-in-group9001"],
  1230. "event_id": "a" * 32,
  1231. "message": "hello",
  1232. "environment": "production",
  1233. "tags": {"server": "example.com"},
  1234. "release": release_1.version,
  1235. "stacktrace": {"frames": [{"module": "group1"}]},
  1236. },
  1237. project_id=self.project.id,
  1238. ).group
  1239. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1240. assert set(results) == {group}
  1241. def test_first_release_in_syntax(self):
  1242. # expect no groups within the results since there are no releases
  1243. self.run_test_query_in_syntax("first_release:[fake, fake2]", [])
  1244. # expect no groups even though there is a release; since no group
  1245. # is attached to a release
  1246. release_1 = self.create_release(self.project)
  1247. release_2 = self.create_release(self.project)
  1248. self.run_test_query_in_syntax(
  1249. f"first_release:[{release_1.version}, {release_2.version}]", []
  1250. )
  1251. # Create a new event so that we get a group in this release
  1252. group = self.store_event(
  1253. data={
  1254. "fingerprint": ["put-me-in-group9001"],
  1255. "event_id": "a" * 32,
  1256. "message": "hello",
  1257. "environment": "production",
  1258. "tags": {"server": "example.com"},
  1259. "release": release_1.version,
  1260. "stacktrace": {"frames": [{"module": "group1"}]},
  1261. },
  1262. project_id=self.project.id,
  1263. ).group
  1264. self.run_test_query_in_syntax(
  1265. f"first_release:[{release_1.version}, {release_2.version}]",
  1266. [group],
  1267. [self.group1, self.group2],
  1268. )
  1269. # Create a new event so that we get a group in this release
  1270. group_2 = self.store_event(
  1271. data={
  1272. "fingerprint": ["put-me-in-group9002"],
  1273. "event_id": "a" * 32,
  1274. "message": "hello",
  1275. "environment": "production",
  1276. "tags": {"server": "example.com"},
  1277. "release": release_2.version,
  1278. "stacktrace": {"frames": [{"module": "group1"}]},
  1279. },
  1280. project_id=self.project.id,
  1281. ).group
  1282. self.run_test_query_in_syntax(
  1283. f"first_release:[{release_1.version}, {release_2.version}]",
  1284. [group, group_2],
  1285. )
  1286. def test_first_release_environments(self):
  1287. results = self.make_query(
  1288. environments=[self.environments["production"]],
  1289. search_filter_query="first_release:fake",
  1290. )
  1291. assert set(results) == set()
  1292. release = self.create_release(self.project)
  1293. group_env = GroupEnvironment.get_or_create(
  1294. group_id=self.group1.id, environment_id=self.environments["production"].id
  1295. )[0]
  1296. results = self.make_query(
  1297. environments=[self.environments["production"]],
  1298. search_filter_query=f"first_release:{release.version}",
  1299. )
  1300. assert set(results) == set()
  1301. group_env.first_release = release
  1302. group_env.save()
  1303. results = self.make_query(
  1304. environments=[self.environments["production"]],
  1305. search_filter_query=f"first_release:{release.version}",
  1306. )
  1307. assert set(results) == {self.group1}
  1308. def test_first_release_environments_in_syntax(self):
  1309. self.run_test_query_in_syntax(
  1310. "first_release:[fake, fake2]",
  1311. [],
  1312. [self.group1, self.group2],
  1313. environments=[self.environments["production"]],
  1314. )
  1315. release = self.create_release(self.project)
  1316. group_1_env = GroupEnvironment.objects.get(
  1317. group_id=self.group1.id, environment_id=self.environments["production"].id
  1318. )
  1319. group_1_env.update(first_release=release)
  1320. self.run_test_query_in_syntax(
  1321. f"first_release:[{release.version}, fake2]",
  1322. [self.group1],
  1323. [self.group2],
  1324. environments=[self.environments["production"]],
  1325. )
  1326. group_2_env = GroupEnvironment.objects.get(
  1327. group_id=self.group2.id, environment_id=self.environments["staging"].id
  1328. )
  1329. group_2_env.update(first_release=release)
  1330. self.run_test_query_in_syntax(
  1331. f"first_release:[{release.version}, fake2]",
  1332. [self.group1, self.group2],
  1333. [],
  1334. environments=[self.environments["production"], self.environments["staging"]],
  1335. )
  1336. # Make sure we don't get duplicate groups
  1337. GroupEnvironment.objects.create(
  1338. group_id=self.group1.id,
  1339. environment_id=self.environments["staging"].id,
  1340. first_release=release,
  1341. )
  1342. self.run_test_query_in_syntax(
  1343. f"first_release:[{release.version}, fake2]",
  1344. [self.group1, self.group2],
  1345. [],
  1346. environments=[self.environments["production"], self.environments["staging"]],
  1347. )
  1348. def test_query_enclosed_in_quotes(self):
  1349. results = self.make_query(search_filter_query='"foo"')
  1350. assert set(results) == {self.group1}
  1351. results = self.make_query(search_filter_query='"bar"')
  1352. assert set(results) == {self.group2}
  1353. @xfail_if_not_postgres("Wildcard searching only supported in Postgres")
  1354. def test_wildcard(self):
  1355. escaped_event = self.store_event(
  1356. data={
  1357. "fingerprint": ["hello-there"],
  1358. "event_id": "f" * 32,
  1359. "message": "somet[hing]",
  1360. "environment": "production",
  1361. "tags": {"server": "example.net"},
  1362. "timestamp": iso_format(self.base_datetime),
  1363. "stacktrace": {"frames": [{"module": "group1"}]},
  1364. },
  1365. project_id=self.project.id,
  1366. )
  1367. # Note: Adding in `environment:production` so that we make sure we query
  1368. # in both snuba and postgres
  1369. results = self.make_query(search_filter_query="environment:production so*t")
  1370. assert set(results) == {escaped_event.group}
  1371. # Make sure it's case insensitive
  1372. results = self.make_query(search_filter_query="environment:production SO*t")
  1373. assert set(results) == {escaped_event.group}
  1374. results = self.make_query(search_filter_query="environment:production so*zz")
  1375. assert set(results) == set()
  1376. results = self.make_query(search_filter_query="environment:production [hing]")
  1377. assert set(results) == {escaped_event.group}
  1378. results = self.make_query(search_filter_query="environment:production s*]")
  1379. assert set(results) == {escaped_event.group}
  1380. results = self.make_query(search_filter_query="environment:production server:example.*")
  1381. assert set(results) == {self.group1, escaped_event.group}
  1382. results = self.make_query(search_filter_query="environment:production !server:*net")
  1383. assert set(results) == {self.group1}
  1384. # TODO: Disabling tests that use [] syntax for the moment. Re-enable
  1385. # these if we decide to add back in, or remove if this comment has been
  1386. # here a while.
  1387. # results = self.make_query(
  1388. # search_filter_query='environment:production [s][of][mz]',
  1389. # )
  1390. # assert set(results) == set([escaped_event.group])
  1391. # results = self.make_query(
  1392. # search_filter_query='environment:production [z][of][mz]',
  1393. # )
  1394. # assert set(results) == set()
  1395. def test_null_tags(self):
  1396. tag_event = self.store_event(
  1397. data={
  1398. "fingerprint": ["hello-there"],
  1399. "event_id": "f" * 32,
  1400. "message": "something",
  1401. "environment": "production",
  1402. "tags": {"server": "example.net"},
  1403. "timestamp": iso_format(self.base_datetime),
  1404. "stacktrace": {"frames": [{"module": "group1"}]},
  1405. },
  1406. project_id=self.project.id,
  1407. )
  1408. no_tag_event = self.store_event(
  1409. data={
  1410. "fingerprint": ["hello-there-2"],
  1411. "event_id": "5" * 32,
  1412. "message": "something",
  1413. "environment": "production",
  1414. "timestamp": iso_format(self.base_datetime),
  1415. "stacktrace": {"frames": [{"module": "group2"}]},
  1416. },
  1417. project_id=self.project.id,
  1418. )
  1419. results = self.make_query(search_filter_query="environment:production !server:*net")
  1420. assert set(results) == {self.group1, no_tag_event.group}
  1421. results = self.make_query(search_filter_query="environment:production server:*net")
  1422. assert set(results) == {tag_event.group}
  1423. results = self.make_query(search_filter_query="environment:production !server:example.net")
  1424. assert set(results) == {self.group1, no_tag_event.group}
  1425. results = self.make_query(search_filter_query="environment:production server:example.net")
  1426. assert set(results) == {tag_event.group}
  1427. results = self.make_query(search_filter_query="environment:production has:server")
  1428. assert set(results) == {self.group1, tag_event.group}
  1429. results = self.make_query(search_filter_query="environment:production !has:server")
  1430. assert set(results) == {no_tag_event.group}
  1431. def test_null_promoted_tags(self):
  1432. tag_event = self.store_event(
  1433. data={
  1434. "fingerprint": ["hello-there"],
  1435. "event_id": "f" * 32,
  1436. "message": "something",
  1437. "environment": "production",
  1438. "tags": {"logger": "csp"},
  1439. "timestamp": iso_format(self.base_datetime),
  1440. "stacktrace": {"frames": [{"module": "group1"}]},
  1441. },
  1442. project_id=self.project.id,
  1443. )
  1444. no_tag_event = self.store_event(
  1445. data={
  1446. "fingerprint": ["hello-there-2"],
  1447. "event_id": "5" * 32,
  1448. "message": "something",
  1449. "environment": "production",
  1450. "timestamp": iso_format(self.base_datetime),
  1451. "stacktrace": {"frames": [{"module": "group2"}]},
  1452. },
  1453. project_id=self.project.id,
  1454. )
  1455. results = self.make_query(search_filter_query="environment:production !logger:*sp")
  1456. assert set(results) == {self.group1, no_tag_event.group}
  1457. results = self.make_query(search_filter_query="environment:production logger:*sp")
  1458. assert set(results) == {tag_event.group}
  1459. results = self.make_query(search_filter_query="environment:production !logger:csp")
  1460. assert set(results) == {self.group1, no_tag_event.group}
  1461. results = self.make_query(search_filter_query="environment:production logger:csp")
  1462. assert set(results) == {tag_event.group}
  1463. results = self.make_query(search_filter_query="environment:production has:logger")
  1464. assert set(results) == {tag_event.group}
  1465. results = self.make_query(search_filter_query="environment:production !has:logger")
  1466. assert set(results) == {self.group1, no_tag_event.group}
  1467. def test_sort_multi_project(self):
  1468. self.set_up_multi_project()
  1469. results = self.make_query([self.project, self.project2], sort_by="date")
  1470. assert list(results) == [self.group1, self.group_p2, self.group2]
  1471. results = self.make_query([self.project, self.project2], sort_by="new")
  1472. assert list(results) == [self.group2, self.group_p2, self.group1]
  1473. results = self.make_query([self.project, self.project2], sort_by="freq")
  1474. assert list(results) == [self.group1, self.group_p2, self.group2]
  1475. results = self.make_query([self.project, self.project2], sort_by="priority")
  1476. assert list(results) == [self.group1, self.group2, self.group_p2]
  1477. results = self.make_query([self.project, self.project2], sort_by="user")
  1478. assert list(results) == [self.group1, self.group2, self.group_p2]
  1479. def test_sort_trend(self):
  1480. start = self.group1.first_seen - timedelta(days=1)
  1481. end = before_now(days=1).replace(tzinfo=pytz.utc)
  1482. middle = start + ((end - start) / 2)
  1483. self.store_event(
  1484. data={
  1485. "fingerprint": ["put-me-in-group1"],
  1486. "event_id": "2" * 32,
  1487. "message": "something",
  1488. "timestamp": iso_format(self.base_datetime),
  1489. },
  1490. project_id=self.project.id,
  1491. )
  1492. self.store_event(
  1493. data={
  1494. "fingerprint": ["put-me-in-group1"],
  1495. "event_id": "3" * 32,
  1496. "message": "something",
  1497. "timestamp": iso_format(self.base_datetime),
  1498. },
  1499. project_id=self.project.id,
  1500. )
  1501. fewer_events_group = self.store_event(
  1502. data={
  1503. "fingerprint": ["put-me-in-group4"],
  1504. "event_id": "4" * 32,
  1505. "message": "something",
  1506. "timestamp": iso_format(middle - timedelta(days=1)),
  1507. },
  1508. project_id=self.project.id,
  1509. ).group
  1510. self.store_event(
  1511. data={
  1512. "fingerprint": ["put-me-in-group4"],
  1513. "event_id": "5" * 32,
  1514. "message": "something",
  1515. "timestamp": iso_format(middle - timedelta(days=1)),
  1516. },
  1517. project_id=self.project.id,
  1518. )
  1519. self.store_event(
  1520. data={
  1521. "fingerprint": ["put-me-in-group4"],
  1522. "event_id": "6" * 32,
  1523. "message": "something",
  1524. "timestamp": iso_format(self.base_datetime),
  1525. },
  1526. project_id=self.project.id,
  1527. )
  1528. no_before_group = self.store_event(
  1529. data={
  1530. "fingerprint": ["put-me-in-group5"],
  1531. "event_id": "3" * 32,
  1532. "message": "something",
  1533. "timestamp": iso_format(self.base_datetime),
  1534. },
  1535. project_id=self.project.id,
  1536. ).group
  1537. no_after_group = self.store_event(
  1538. data={
  1539. "fingerprint": ["put-me-in-group6"],
  1540. "event_id": "4" * 32,
  1541. "message": "something",
  1542. "timestamp": iso_format(middle - timedelta(days=1)),
  1543. },
  1544. project_id=self.project.id,
  1545. ).group
  1546. self.set_up_multi_project()
  1547. results = self.make_query([self.project], sort_by="trend", date_from=start, date_to=end)
  1548. assert results[:2] == [self.group1, fewer_events_group]
  1549. # These will be arbitrarily ordered since their trend values are all 0
  1550. assert set(results[2:]) == {self.group2, no_before_group, no_after_group}
  1551. def test_in_syntax_is_invalid(self):
  1552. with pytest.raises(InvalidSearchQuery, match='"in" syntax invalid for "is" search'):
  1553. self.make_query(search_filter_query="is:[unresolved, resolved]")
  1554. def test_first_release_any_or_no_environments(self):
  1555. # test scenarios for tickets:
  1556. # SEN-571
  1557. # ISSUE-432
  1558. # given the following setup:
  1559. #
  1560. # groups table:
  1561. # group first_release
  1562. # A 1
  1563. # B 1
  1564. # C 2
  1565. #
  1566. # groupenvironments table:
  1567. # group environment first_release
  1568. # A staging 1
  1569. # A production 2
  1570. #
  1571. # when querying by first release, the appropriate set of groups should be displayed:
  1572. #
  1573. # first_release: 1
  1574. # env=[]: A, B
  1575. # env=[production, staging]: A
  1576. # env=[staging]: A
  1577. # env=[production]: nothing
  1578. #
  1579. # first_release: 2
  1580. # env=[]: A, C
  1581. # env=[production, staging]: A
  1582. # env=[staging]: nothing
  1583. # env=[production]: A
  1584. # create an issue/group whose events that occur in 2 distinct environments
  1585. group_a_event_1 = self.store_event(
  1586. data={
  1587. "fingerprint": ["group_a"],
  1588. "event_id": "aaa" + ("1" * 29),
  1589. "environment": "example_staging",
  1590. "release": "release_1",
  1591. },
  1592. project_id=self.project.id,
  1593. )
  1594. group_a_event_2 = self.store_event(
  1595. data={
  1596. "fingerprint": ["group_a"],
  1597. "event_id": "aaa" + ("2" * 29),
  1598. "environment": "example_production",
  1599. "release": "release_2",
  1600. },
  1601. project_id=self.project.id,
  1602. )
  1603. group_a = group_a_event_1.group
  1604. # get the environments for group_a
  1605. prod_env = group_a_event_2.get_environment()
  1606. staging_env = group_a_event_1.get_environment()
  1607. # create an issue/group whose event that occur in no environments
  1608. # but will be tied to release release_1
  1609. group_b_event_1 = self.store_event(
  1610. data={
  1611. "fingerprint": ["group_b"],
  1612. "event_id": "bbb" + ("1" * 29),
  1613. "release": "release_1",
  1614. },
  1615. project_id=self.project.id,
  1616. )
  1617. assert group_b_event_1.get_environment().name == "" # has no environment
  1618. group_b = group_b_event_1.group
  1619. # create an issue/group whose event that occur in no environments
  1620. # but will be tied to release release_2
  1621. group_c_event_1 = self.store_event(
  1622. data={
  1623. "fingerprint": ["group_c"],
  1624. "event_id": "ccc" + ("1" * 29),
  1625. "release": "release_2",
  1626. },
  1627. project_id=self.project.id,
  1628. )
  1629. assert group_c_event_1.get_environment().name == "" # has no environment
  1630. group_c = group_c_event_1.group
  1631. # query by release release_1
  1632. results = self.make_query(search_filter_query="first_release:%s" % "release_1")
  1633. assert set(results) == {group_a, group_b}
  1634. results = self.make_query(
  1635. environments=[staging_env, prod_env],
  1636. search_filter_query="first_release:%s" % "release_1",
  1637. )
  1638. assert set(results) == {group_a}
  1639. results = self.make_query(
  1640. environments=[staging_env], search_filter_query="first_release:%s" % "release_1"
  1641. )
  1642. assert set(results) == {group_a}
  1643. results = self.make_query(
  1644. environments=[prod_env], search_filter_query="first_release:%s" % "release_1"
  1645. )
  1646. assert set(results) == set()
  1647. # query by release release_2
  1648. results = self.make_query(search_filter_query="first_release:%s" % "release_2")
  1649. assert set(results) == {group_a, group_c}
  1650. results = self.make_query(
  1651. environments=[staging_env, prod_env],
  1652. search_filter_query="first_release:%s" % "release_2",
  1653. )
  1654. assert set(results) == {group_a}
  1655. results = self.make_query(
  1656. environments=[staging_env], search_filter_query="first_release:%s" % "release_2"
  1657. )
  1658. assert set(results) == set()
  1659. results = self.make_query(
  1660. environments=[prod_env], search_filter_query="first_release:%s" % "release_2"
  1661. )
  1662. assert set(results) == {group_a}
  1663. def test_all_fields_do_not_error(self):
  1664. # Just a sanity check to make sure that all fields can be successfully
  1665. # searched on without returning type errors and other schema related
  1666. # issues.
  1667. def test_query(query):
  1668. try:
  1669. self.make_query(search_filter_query=query)
  1670. except SnubaError as e:
  1671. self.fail(f"Query {query} errored. Error info: {e}")
  1672. for key in SENTRY_SNUBA_MAP:
  1673. if key in ["project.id", "issue.id", "performance.issue_ids"]:
  1674. continue
  1675. test_query("has:%s" % key)
  1676. test_query("!has:%s" % key)
  1677. if key == "error.handled":
  1678. val = 1
  1679. elif key in issue_search_config.numeric_keys:
  1680. val = "123"
  1681. elif key in issue_search_config.date_keys:
  1682. val = self.base_datetime.isoformat()
  1683. elif key in issue_search_config.boolean_keys:
  1684. val = "true"
  1685. elif key in {"trace.span", "trace.parent_span"}:
  1686. val = "abcdef1234abcdef"
  1687. test_query(f"!{key}:{val}")
  1688. else:
  1689. val = "abadcafedeadbeefdeaffeedabadfeed"
  1690. test_query(f"!{key}:{val}")
  1691. test_query(f"{key}:{val}")
  1692. def test_message_negation(self):
  1693. self.store_event(
  1694. data={
  1695. "fingerprint": ["put-me-in-group1"],
  1696. "event_id": "2" * 32,
  1697. "message": "something",
  1698. "timestamp": iso_format(self.base_datetime),
  1699. },
  1700. project_id=self.project.id,
  1701. )
  1702. results = self.make_query(search_filter_query="!message:else")
  1703. results2 = self.make_query(search_filter_query="!message:else")
  1704. assert list(results) == list(results2)
  1705. def test_error_main_thread_true(self):
  1706. myProject = self.create_project(
  1707. name="Foo", slug="foo", teams=[self.team], fire_project_created=True
  1708. )
  1709. event = self.store_event(
  1710. data={
  1711. "event_id": "1" * 32,
  1712. "message": "something",
  1713. "timestamp": iso_format(self.base_datetime),
  1714. "exception": {
  1715. "values": [
  1716. {
  1717. "type": "SyntaxError",
  1718. "value": "hello world",
  1719. "thread_id": 1,
  1720. },
  1721. ],
  1722. },
  1723. "threads": {
  1724. "values": [
  1725. {
  1726. "id": 1,
  1727. "main": True,
  1728. },
  1729. ],
  1730. },
  1731. },
  1732. project_id=myProject.id,
  1733. )
  1734. myGroup = event.groups[0]
  1735. results = self.make_query(
  1736. projects=[myProject],
  1737. search_filter_query="error.main_thread:1",
  1738. sort_by="date",
  1739. )
  1740. assert list(results) == [myGroup]
  1741. def test_error_main_thread_false(self):
  1742. myProject = self.create_project(
  1743. name="Foo2", slug="foo2", teams=[self.team], fire_project_created=True
  1744. )
  1745. event = self.store_event(
  1746. data={
  1747. "event_id": "2" * 32,
  1748. "message": "something",
  1749. "timestamp": iso_format(self.base_datetime),
  1750. "exception": {
  1751. "values": [
  1752. {
  1753. "type": "SyntaxError",
  1754. "value": "hello world",
  1755. "thread_id": 1,
  1756. },
  1757. ],
  1758. },
  1759. "threads": {
  1760. "values": [
  1761. {
  1762. "id": 1,
  1763. "main": False,
  1764. },
  1765. ],
  1766. },
  1767. },
  1768. project_id=myProject.id,
  1769. )
  1770. myGroup = event.groups[0]
  1771. results = self.make_query(
  1772. projects=[myProject],
  1773. search_filter_query="error.main_thread:0",
  1774. sort_by="date",
  1775. )
  1776. assert list(results) == [myGroup]
  1777. def test_error_main_thread_no_results(self):
  1778. myProject = self.create_project(
  1779. name="Foo3", slug="foo3", teams=[self.team], fire_project_created=True
  1780. )
  1781. self.store_event(
  1782. data={
  1783. "event_id": "3" * 32,
  1784. "message": "something",
  1785. "timestamp": iso_format(self.base_datetime),
  1786. "exception": {
  1787. "values": [
  1788. {
  1789. "type": "SyntaxError",
  1790. "value": "hello world",
  1791. "thread_id": 1,
  1792. },
  1793. ],
  1794. },
  1795. "threads": {
  1796. "values": [
  1797. {
  1798. "id": 1,
  1799. },
  1800. ],
  1801. },
  1802. },
  1803. project_id=myProject.id,
  1804. )
  1805. results = self.make_query(
  1806. projects=[myProject],
  1807. search_filter_query="error.main_thread:1",
  1808. sort_by="date",
  1809. )
  1810. assert len(results) == 0
  1811. class EventsTransactionsSnubaSearchTest(SharedSnubaTest):
  1812. @property
  1813. def backend(self):
  1814. return EventsDatasetSnubaSearchBackend()
  1815. def setUp(self):
  1816. super().setUp()
  1817. self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc)
  1818. transaction_event_data = {
  1819. "level": "info",
  1820. "message": "ayoo",
  1821. "type": "transaction",
  1822. "culprit": "app/components/events/eventEntries in map",
  1823. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  1824. }
  1825. transaction_event_1 = self.store_event(
  1826. data={
  1827. **transaction_event_data,
  1828. "event_id": "a" * 32,
  1829. "timestamp": iso_format(before_now(minutes=1)),
  1830. "start_timestamp": iso_format(before_now(minutes=1, seconds=5)),
  1831. "tags": {"my_tag": 1},
  1832. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
  1833. },
  1834. project_id=self.project.id,
  1835. )
  1836. self.perf_group_1 = transaction_event_1.groups[0]
  1837. transaction_event_2 = self.store_event(
  1838. data={
  1839. **transaction_event_data,
  1840. "event_id": "a" * 32,
  1841. "timestamp": iso_format(before_now(minutes=2)),
  1842. "start_timestamp": iso_format(before_now(minutes=2, seconds=5)),
  1843. "tags": {"my_tag": 1},
  1844. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group2"],
  1845. },
  1846. project_id=self.project.id,
  1847. )
  1848. self.perf_group_2 = transaction_event_2.groups[0]
  1849. error_event_data = {
  1850. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  1851. "message": "bar",
  1852. "environment": "staging",
  1853. "tags": {
  1854. "server": "example.com",
  1855. "url": "http://example.com",
  1856. "sentry:user": "event2@example.com",
  1857. "my_tag": 1,
  1858. },
  1859. }
  1860. error_event = self.store_event(
  1861. data={
  1862. **error_event_data,
  1863. "fingerprint": ["put-me-in-error_group_1"],
  1864. "event_id": "c" * 32,
  1865. "stacktrace": {"frames": [{"module": "error_group_1"}]},
  1866. },
  1867. project_id=self.project.id,
  1868. )
  1869. self.error_group_1 = error_event.group
  1870. error_event_2 = self.store_event(
  1871. data={
  1872. **error_event_data,
  1873. "fingerprint": ["put-me-in-error_group_2"],
  1874. "event_id": "d" * 32,
  1875. "stacktrace": {"frames": [{"module": "error_group_2"}]},
  1876. },
  1877. project_id=self.project.id,
  1878. )
  1879. self.error_group_2 = error_event_2.group
  1880. def test_performance_query(self):
  1881. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  1882. assert list(results) == [self.perf_group_1, self.perf_group_2]
  1883. results = self.make_query(
  1884. search_filter_query="issue.type:[performance_n_plus_one_db_queries, performance_render_blocking_asset_span] my_tag:1"
  1885. )
  1886. assert list(results) == [self.perf_group_1, self.perf_group_2]
  1887. def test_performance_issue_search_feature_off(self):
  1888. with Feature({"organizations:performance-issues-search": False}):
  1889. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  1890. assert list(results) == []
  1891. with Feature({"organizations:performance-issues-search": True}):
  1892. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  1893. assert list(results) == [self.perf_group_1, self.perf_group_2]
  1894. def test_error_performance_query(self):
  1895. results = self.make_query(search_filter_query="my_tag:1")
  1896. assert list(results) == [
  1897. self.perf_group_1,
  1898. self.perf_group_2,
  1899. self.error_group_2,
  1900. self.error_group_1,
  1901. ]
  1902. results = self.make_query(
  1903. search_filter_query="issue.category:[performance, error] my_tag:1"
  1904. )
  1905. assert list(results) == [
  1906. self.perf_group_1,
  1907. self.perf_group_2,
  1908. self.error_group_2,
  1909. self.error_group_1,
  1910. ]
  1911. results = self.make_query(
  1912. search_filter_query="issue.type:[performance_render_blocking_asset_span, error] my_tag:1"
  1913. )
  1914. assert list(results) == [
  1915. self.perf_group_1,
  1916. self.perf_group_2,
  1917. self.error_group_2,
  1918. self.error_group_1,
  1919. ]
  1920. def test_cursor_performance_issues(self):
  1921. results = self.make_query(
  1922. projects=[self.project],
  1923. search_filter_query="issue.category:performance my_tag:1",
  1924. sort_by="date",
  1925. limit=1,
  1926. count_hits=True,
  1927. )
  1928. assert list(results) == [self.perf_group_1]
  1929. assert results.hits == 2
  1930. results = self.make_query(
  1931. projects=[self.project],
  1932. search_filter_query="issue.category:performance my_tag:1",
  1933. sort_by="date",
  1934. limit=1,
  1935. cursor=results.next,
  1936. count_hits=True,
  1937. )
  1938. assert list(results) == [self.perf_group_2]
  1939. assert results.hits == 2
  1940. results = self.make_query(
  1941. projects=[self.project],
  1942. search_filter_query="issue.category:performance my_tag:1",
  1943. sort_by="date",
  1944. limit=1,
  1945. cursor=results.next,
  1946. count_hits=True,
  1947. )
  1948. assert list(results) == []
  1949. assert results.hits == 2
  1950. def test_perf_issue_search_message_term_queries_postgres(self):
  1951. from django.db.models import Q
  1952. from sentry.utils import snuba
  1953. transaction_name = "im a little tea pot"
  1954. tx = self.store_event(
  1955. data={
  1956. "level": "info",
  1957. "culprit": "app/components/events/eventEntries in map",
  1958. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  1959. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"],
  1960. "event_id": "e" * 32,
  1961. "timestamp": iso_format(self.base_datetime),
  1962. "start_timestamp": iso_format(self.base_datetime),
  1963. "type": "transaction",
  1964. "transaction": transaction_name,
  1965. },
  1966. project_id=self.project.id,
  1967. )
  1968. assert "tea" in tx.search_message
  1969. created_group = tx.groups[0]
  1970. find_group = Group.objects.filter(
  1971. Q(type=PerformanceRenderBlockingAssetSpanGroupType.type_id, message__icontains="tea")
  1972. ).first()
  1973. assert created_group == find_group
  1974. result = snuba.raw_query(
  1975. dataset=snuba.Dataset.Transactions,
  1976. start=self.base_datetime - timedelta(hours=1),
  1977. end=self.base_datetime + timedelta(hours=1),
  1978. selected_columns=[
  1979. "event_id",
  1980. "group_ids",
  1981. "transaction_name",
  1982. ],
  1983. groupby=None,
  1984. filter_keys={"project_id": [self.project.id], "event_id": [tx.event_id]},
  1985. referrer="_insert_transaction.verify_transaction",
  1986. )
  1987. assert result["data"][0]["transaction_name"] == transaction_name
  1988. assert result["data"][0]["group_ids"] == [created_group.id]
  1989. results = self.make_query(search_filter_query="issue.category:performance tea")
  1990. assert set(results) == {created_group}
  1991. results2 = self.make_query(search_filter_query="tea")
  1992. assert set(results2) == {created_group}
  1993. def test_search_message_error_and_perf_issues(self):
  1994. tx = self.store_event(
  1995. data={
  1996. "level": "info",
  1997. "culprit": "app/components/events/eventEntries in map",
  1998. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  1999. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"],
  2000. "event_id": "e" * 32,
  2001. "timestamp": iso_format(self.base_datetime),
  2002. "start_timestamp": iso_format(self.base_datetime),
  2003. "type": "transaction",
  2004. "transaction": "/api/0/events",
  2005. },
  2006. project_id=self.project.id,
  2007. )
  2008. perf_issue = tx.groups[0]
  2009. assert perf_issue
  2010. error = self.store_event(
  2011. data={
  2012. "fingerprint": ["another-random-group"],
  2013. "event_id": "d" * 32,
  2014. "message": "Uncaught exception on api /api/0/events",
  2015. "environment": "production",
  2016. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  2017. "timestamp": iso_format(self.base_datetime),
  2018. "stacktrace": {"frames": [{"module": "group1"}]},
  2019. },
  2020. project_id=self.project.id,
  2021. )
  2022. error_issue = error.group
  2023. assert error_issue
  2024. assert error_issue != perf_issue
  2025. assert set(self.make_query(search_filter_query="is:unresolved /api/0/events")) == {
  2026. perf_issue,
  2027. error_issue,
  2028. }
  2029. assert set(self.make_query(search_filter_query="/api/0/events")) == {
  2030. error_issue,
  2031. perf_issue,
  2032. }
  2033. def test_compound_message_negation(self):
  2034. self.store_event(
  2035. data={
  2036. "fingerprint": ["put-me-in-group1"],
  2037. "event_id": "2" * 32,
  2038. "message": "something",
  2039. "timestamp": iso_format(self.base_datetime),
  2040. },
  2041. project_id=self.project.id,
  2042. )
  2043. self.store_event(
  2044. data={
  2045. "level": "info",
  2046. "culprit": "app/components/events/eventEntries in map",
  2047. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  2048. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"],
  2049. "event_id": "e" * 32,
  2050. "timestamp": iso_format(self.base_datetime),
  2051. "start_timestamp": iso_format(self.base_datetime),
  2052. "type": "transaction",
  2053. "transaction": "something",
  2054. },
  2055. project_id=self.project.id,
  2056. )
  2057. error_issues_only = self.make_query(
  2058. search_filter_query="!message:else group.category:error"
  2059. )
  2060. error_and_perf_issues = self.make_query(search_filter_query="!message:else")
  2061. assert set(error_and_perf_issues) > set(error_issues_only)
  2062. class EventsGenericSnubaSearchTest(SharedSnubaTest, OccurrenceTestMixin):
  2063. @property
  2064. def backend(self):
  2065. return EventsDatasetSnubaSearchBackend()
  2066. def setUp(self):
  2067. super().setUp()
  2068. self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc)
  2069. event_id_1 = uuid.uuid4().hex
  2070. _, group_info = process_event_and_issue_occurrence(
  2071. self.build_occurrence_data(event_id=event_id_1),
  2072. {
  2073. "event_id": event_id_1,
  2074. "project_id": self.project.id,
  2075. "title": "some problem",
  2076. "platform": "python",
  2077. "tags": {"my_tag": "1"},
  2078. "timestamp": before_now(minutes=1).isoformat(),
  2079. "received": before_now(minutes=1).isoformat(),
  2080. },
  2081. )
  2082. self.profile_group_1 = group_info.group
  2083. event_id_2 = uuid.uuid4().hex
  2084. _, group_info = process_event_and_issue_occurrence(
  2085. self.build_occurrence_data(event_id=event_id_2, fingerprint=["put-me-in-group-2"]),
  2086. {
  2087. "event_id": event_id_2,
  2088. "project_id": self.project.id,
  2089. "title": "some other problem",
  2090. "platform": "python",
  2091. "tags": {"my_tag": "1"},
  2092. "timestamp": before_now(minutes=2).isoformat(),
  2093. "received": before_now(minutes=2).isoformat(),
  2094. },
  2095. )
  2096. self.profile_group_2 = group_info.group
  2097. event_id_3 = uuid.uuid4().hex
  2098. process_event_and_issue_occurrence(
  2099. self.build_occurrence_data(event_id=event_id_3, fingerprint=["put-me-in-group-3"]),
  2100. {
  2101. "event_id": event_id_3,
  2102. "project_id": self.project.id,
  2103. "title": "some other problem",
  2104. "platform": "python",
  2105. "tags": {"my_tag": "2"},
  2106. "timestamp": before_now(minutes=2).isoformat(),
  2107. "message_timestamp": before_now(minutes=2).isoformat(),
  2108. },
  2109. )
  2110. error_event_data = {
  2111. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  2112. "message": "bar",
  2113. "environment": "staging",
  2114. "tags": {
  2115. "server": "example.com",
  2116. "url": "http://example.com",
  2117. "sentry:user": "event2@example.com",
  2118. "my_tag": 1,
  2119. },
  2120. }
  2121. error_event = self.store_event(
  2122. data={
  2123. **error_event_data,
  2124. "fingerprint": ["put-me-in-error_group_1"],
  2125. "event_id": "c" * 32,
  2126. "stacktrace": {"frames": [{"module": "error_group_1"}]},
  2127. },
  2128. project_id=self.project.id,
  2129. )
  2130. self.error_group_1 = error_event.group
  2131. error_event_2 = self.store_event(
  2132. data={
  2133. **error_event_data,
  2134. "fingerprint": ["put-me-in-error_group_2"],
  2135. "event_id": "d" * 32,
  2136. "stacktrace": {"frames": [{"module": "error_group_2"}]},
  2137. },
  2138. project_id=self.project.id,
  2139. )
  2140. self.error_group_2 = error_event_2.group
  2141. def test_no_feature(self):
  2142. results = self.make_query(search_filter_query="issue.category:profile my_tag:1")
  2143. assert list(results) == []
  2144. def test_generic_query(self):
  2145. with self.feature("organizations:issue-platform"):
  2146. results = self.make_query(search_filter_query="issue.category:profile my_tag:1")
  2147. assert list(results) == [self.profile_group_1, self.profile_group_2]
  2148. with self.feature("organizations:issue-platform"):
  2149. results = self.make_query(
  2150. search_filter_query="issue.type:profile_file_io_main_thread my_tag:1"
  2151. )
  2152. assert list(results) == [self.profile_group_1, self.profile_group_2]
  2153. def test_generic_query_perf(self):
  2154. event_id = uuid.uuid4().hex
  2155. group_type = PerformanceNPlusOneGroupType
  2156. self.project.update_option("sentry:performance_issue_create_issue_through_platform", True)
  2157. with self.options(
  2158. {"performance.issues.create_issues_through_platform": True}
  2159. ), mock.patch.object(
  2160. PerformanceNPlusOneGroupType, "noise_config", new=NoiseConfig(0, timedelta(minutes=1))
  2161. ):
  2162. with self.feature(group_type.build_ingest_feature_name()):
  2163. _, group_info = process_event_and_issue_occurrence(
  2164. self.build_occurrence_data(
  2165. event_id=event_id, type=group_type.type_id, fingerprint=["some perf issue"]
  2166. ),
  2167. {
  2168. "event_id": event_id,
  2169. "project_id": self.project.id,
  2170. "title": "some problem",
  2171. "platform": "python",
  2172. "tags": {"my_tag": "2"},
  2173. "timestamp": before_now(minutes=1).isoformat(),
  2174. "received": before_now(minutes=1).isoformat(),
  2175. },
  2176. )
  2177. results = self.make_query(search_filter_query="issue.category:performance my_tag:2")
  2178. assert list(results) == []
  2179. with self.feature(
  2180. [
  2181. "organizations:issue-platform",
  2182. group_type.build_visible_feature_name(),
  2183. "organizations:performance-issues-search",
  2184. ]
  2185. ):
  2186. results = self.make_query(search_filter_query="issue.category:performance my_tag:2")
  2187. assert list(results) == [group_info.group]
  2188. def test_error_generic_query(self):
  2189. with self.feature("organizations:issue-platform"):
  2190. results = self.make_query(search_filter_query="my_tag:1")
  2191. assert list(results) == [
  2192. self.profile_group_1,
  2193. self.profile_group_2,
  2194. self.error_group_2,
  2195. self.error_group_1,
  2196. ]
  2197. with self.feature("organizations:issue-platform"):
  2198. results = self.make_query(
  2199. search_filter_query="issue.category:[profile, error] my_tag:1"
  2200. )
  2201. assert list(results) == [
  2202. self.profile_group_1,
  2203. self.profile_group_2,
  2204. self.error_group_2,
  2205. self.error_group_1,
  2206. ]
  2207. with self.feature("organizations:issue-platform"):
  2208. results = self.make_query(
  2209. search_filter_query="issue.type:[profile_file_io_main_thread, error] my_tag:1"
  2210. )
  2211. assert list(results) == [
  2212. self.profile_group_1,
  2213. self.profile_group_2,
  2214. self.error_group_2,
  2215. self.error_group_1,
  2216. ]
  2217. def test_cursor_profile_issues(self):
  2218. with self.feature("organizations:issue-platform"):
  2219. results = self.make_query(
  2220. projects=[self.project],
  2221. search_filter_query="issue.category:profile my_tag:1",
  2222. sort_by="date",
  2223. limit=1,
  2224. count_hits=True,
  2225. )
  2226. assert list(results) == [self.profile_group_1]
  2227. assert results.hits == 2
  2228. with self.feature("organizations:issue-platform"):
  2229. results = self.make_query(
  2230. projects=[self.project],
  2231. search_filter_query="issue.category:profile my_tag:1",
  2232. sort_by="date",
  2233. limit=1,
  2234. cursor=results.next,
  2235. count_hits=True,
  2236. )
  2237. assert list(results) == [self.profile_group_2]
  2238. assert results.hits == 2
  2239. with self.feature("organizations:issue-platform"):
  2240. results = self.make_query(
  2241. projects=[self.project],
  2242. search_filter_query="issue.category:profile my_tag:1",
  2243. sort_by="date",
  2244. limit=1,
  2245. cursor=results.next,
  2246. count_hits=True,
  2247. )
  2248. assert list(results) == []
  2249. assert results.hits == 2
  2250. def test_rejected_filters(self):
  2251. """
  2252. Any queries with `error.handled` or `error.unhandled` filters querying the search_issues dataset
  2253. should be rejected and return empty results.
  2254. """
  2255. with self.feature("organizations:issue-platform"):
  2256. results = self.make_query(
  2257. projects=[self.project],
  2258. search_filter_query="issue.category:profile error.unhandled:0",
  2259. sort_by="date",
  2260. limit=1,
  2261. count_hits=True,
  2262. )
  2263. results2 = self.make_query(
  2264. projects=[self.project],
  2265. search_filter_query="issue.category:profile error.unhandled:1",
  2266. sort_by="date",
  2267. limit=1,
  2268. count_hits=True,
  2269. )
  2270. result3 = self.make_query(
  2271. projects=[self.project],
  2272. search_filter_query="issue.category:profile error.handled:0",
  2273. sort_by="date",
  2274. limit=1,
  2275. count_hits=True,
  2276. )
  2277. results4 = self.make_query(
  2278. projects=[self.project],
  2279. search_filter_query="issue.category:profile error.handled:1",
  2280. sort_by="date",
  2281. limit=1,
  2282. count_hits=True,
  2283. )
  2284. results5 = self.make_query(
  2285. projects=[self.project],
  2286. search_filter_query="issue.category:profile error.main_thread:0",
  2287. sort_by="date",
  2288. limit=1,
  2289. count_hits=True,
  2290. )
  2291. results6 = self.make_query(
  2292. projects=[self.project],
  2293. search_filter_query="issue.category:profile error.main_thread:1",
  2294. sort_by="date",
  2295. limit=1,
  2296. count_hits=True,
  2297. )
  2298. assert (
  2299. list(results)
  2300. == list(results2)
  2301. == list(result3)
  2302. == list(results4)
  2303. == list(results5)
  2304. == list(results6)
  2305. == []
  2306. )
  2307. class CdcEventsSnubaSearchTest(SharedSnubaTest):
  2308. @property
  2309. def backend(self):
  2310. return CdcEventsDatasetSnubaSearchBackend()
  2311. def setUp(self):
  2312. super().setUp()
  2313. self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc)
  2314. self.event1 = self.store_event(
  2315. data={
  2316. "fingerprint": ["put-me-in-group1"],
  2317. "event_id": "a" * 32,
  2318. "environment": "production",
  2319. "timestamp": iso_format(self.base_datetime - timedelta(days=21)),
  2320. "tags": {"sentry:user": "user1"},
  2321. },
  2322. project_id=self.project.id,
  2323. )
  2324. self.env1 = self.event1.get_environment()
  2325. self.group1 = self.event1.group
  2326. self.event3 = self.store_event(
  2327. data={
  2328. "fingerprint": ["put-me-in-group1"],
  2329. "environment": "staging",
  2330. "timestamp": iso_format(self.base_datetime),
  2331. "tags": {"sentry:user": "user2"},
  2332. },
  2333. project_id=self.project.id,
  2334. )
  2335. self.event2 = self.store_event(
  2336. data={
  2337. "fingerprint": ["put-me-in-group2"],
  2338. "timestamp": iso_format(self.base_datetime - timedelta(days=20)),
  2339. "environment": "staging",
  2340. "tags": {"sentry:user": "user1"},
  2341. },
  2342. project_id=self.project.id,
  2343. )
  2344. self.group2 = self.event2.group
  2345. self.env2 = self.event2.get_environment()
  2346. def run_test(
  2347. self,
  2348. search_filter_query,
  2349. expected_groups,
  2350. expected_hits,
  2351. projects=None,
  2352. environments=None,
  2353. sort_by="date",
  2354. limit=None,
  2355. count_hits=False,
  2356. date_from=None,
  2357. date_to=None,
  2358. cursor=None,
  2359. ):
  2360. results = self.make_query(
  2361. projects=projects,
  2362. search_filter_query=search_filter_query,
  2363. environments=environments,
  2364. sort_by=sort_by,
  2365. limit=limit,
  2366. count_hits=count_hits,
  2367. date_from=date_from,
  2368. date_to=date_to,
  2369. cursor=cursor,
  2370. )
  2371. assert list(results) == expected_groups
  2372. assert results.hits == expected_hits
  2373. return results
  2374. def test(self):
  2375. self.run_test("is:unresolved", [self.group1, self.group2], None)
  2376. def test_invalid(self):
  2377. with pytest.raises(InvalidQueryForExecutor):
  2378. self.make_query(search_filter_query="is:unresolved abc:123")
  2379. def test_resolved_group(self):
  2380. self.group2.status = GroupStatus.RESOLVED
  2381. self.group2.save()
  2382. self.store_group(self.group2)
  2383. self.run_test("is:unresolved", [self.group1], None)
  2384. self.run_test("is:resolved", [self.group2], None)
  2385. self.run_test("is:unresolved is:resolved", [], None)
  2386. def test_environment(self):
  2387. self.run_test("is:unresolved", [self.group1], None, environments=[self.env1])
  2388. self.run_test("is:unresolved", [self.group1, self.group2], None, environments=[self.env2])
  2389. def test_sort_times_seen(self):
  2390. self.run_test(
  2391. "is:unresolved",
  2392. [self.group1, self.group2],
  2393. None,
  2394. sort_by="freq",
  2395. date_from=self.base_datetime - timedelta(days=30),
  2396. )
  2397. self.store_event(
  2398. data={
  2399. "fingerprint": ["put-me-in-group2"],
  2400. "timestamp": iso_format(self.base_datetime - timedelta(days=15)),
  2401. },
  2402. project_id=self.project.id,
  2403. )
  2404. self.store_event(
  2405. data={
  2406. "fingerprint": ["put-me-in-group2"],
  2407. "timestamp": iso_format(self.base_datetime - timedelta(days=10)),
  2408. "tags": {"sentry:user": "user2"},
  2409. },
  2410. project_id=self.project.id,
  2411. )
  2412. self.run_test(
  2413. "is:unresolved",
  2414. [self.group2, self.group1],
  2415. None,
  2416. sort_by="freq",
  2417. # Change the date range to bust the
  2418. date_from=self.base_datetime - timedelta(days=29),
  2419. )
  2420. def test_sort_first_seen(self):
  2421. self.run_test(
  2422. "is:unresolved",
  2423. [self.group2, self.group1],
  2424. None,
  2425. sort_by="new",
  2426. date_from=self.base_datetime - timedelta(days=30),
  2427. )
  2428. group3 = self.store_event(
  2429. data={
  2430. "fingerprint": ["put-me-in-group3"],
  2431. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2432. },
  2433. project_id=self.project.id,
  2434. ).group
  2435. self.run_test(
  2436. "is:unresolved",
  2437. [group3, self.group2, self.group1],
  2438. None,
  2439. sort_by="new",
  2440. # Change the date range to bust the
  2441. date_from=self.base_datetime - timedelta(days=29),
  2442. )
  2443. def test_sort_user(self):
  2444. self.run_test(
  2445. "is:unresolved",
  2446. [self.group1, self.group2],
  2447. None,
  2448. sort_by="user",
  2449. date_from=self.base_datetime - timedelta(days=30),
  2450. )
  2451. self.store_event(
  2452. data={
  2453. "fingerprint": ["put-me-in-group2"],
  2454. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2455. "tags": {"sentry:user": "user2"},
  2456. },
  2457. project_id=self.project.id,
  2458. )
  2459. self.store_event(
  2460. data={
  2461. "fingerprint": ["put-me-in-group2"],
  2462. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2463. "tags": {"sentry:user": "user2"},
  2464. },
  2465. project_id=self.project.id,
  2466. )
  2467. self.store_event(
  2468. data={
  2469. "fingerprint": ["put-me-in-group1"],
  2470. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2471. "tags": {"sentry:user": "user1"},
  2472. },
  2473. project_id=self.project.id,
  2474. )
  2475. self.store_event(
  2476. data={
  2477. "fingerprint": ["put-me-in-group1"],
  2478. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2479. "tags": {"sentry:user": "user1"},
  2480. },
  2481. project_id=self.project.id,
  2482. )
  2483. # Test group with no users, which can return a null count
  2484. group3 = self.store_event(
  2485. data={
  2486. "fingerprint": ["put-me-in-group3"],
  2487. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2488. },
  2489. project_id=self.project.id,
  2490. ).group
  2491. self.run_test(
  2492. "is:unresolved",
  2493. [self.group2, self.group1, group3],
  2494. None,
  2495. sort_by="user",
  2496. # Change the date range to bust the
  2497. date_from=self.base_datetime - timedelta(days=29),
  2498. )
  2499. def test_sort_priority(self):
  2500. self.run_test(
  2501. "is:unresolved",
  2502. [self.group1, self.group2],
  2503. None,
  2504. sort_by="priority",
  2505. date_from=self.base_datetime - timedelta(days=30),
  2506. )
  2507. def test_cursor(self):
  2508. group3 = self.store_event(
  2509. data={
  2510. "fingerprint": ["put-me-in-group3"],
  2511. "timestamp": iso_format(self.base_datetime + timedelta(days=1)),
  2512. "tags": {"sentry:user": "user2"},
  2513. },
  2514. project_id=self.project.id,
  2515. ).group
  2516. group4 = self.store_event(
  2517. data={
  2518. "fingerprint": ["put-me-in-group7"],
  2519. "timestamp": iso_format(self.base_datetime + timedelta(days=2)),
  2520. "tags": {"sentry:user": "user2"},
  2521. },
  2522. project_id=self.project.id,
  2523. ).group
  2524. results = self.run_test("is:unresolved", [group4], 4, limit=1, count_hits=True)
  2525. results = self.run_test(
  2526. "is:unresolved", [group3], 4, limit=1, cursor=results.next, count_hits=True
  2527. )
  2528. results = self.run_test(
  2529. "is:unresolved", [group4], 4, limit=1, cursor=results.prev, count_hits=True
  2530. )
  2531. self.run_test(
  2532. "is:unresolved", [group3, self.group1], 4, limit=2, cursor=results.next, count_hits=True
  2533. )
  2534. def test_rechecking(self):
  2535. self.group2.status = GroupStatus.RESOLVED
  2536. self.group2.save()
  2537. # Explicitly avoid calling `store_group` here. This means that Clickhouse will still see
  2538. # this group as `UNRESOLVED` and it will be returned in the snuba results. This group
  2539. # should still be filtered out by our recheck.
  2540. self.run_test("is:unresolved", [self.group1], None)