test_backend.py 138 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723
  1. import time
  2. import uuid
  3. from datetime import UTC, datetime, timedelta
  4. from typing import Any
  5. from unittest import mock
  6. import pytest
  7. from django.utils import timezone
  8. from sentry_kafka_schemas.schema_types.group_attributes_v1 import GroupAttributesSnapshot
  9. from sentry import options
  10. from sentry.api.issue_search import convert_query_values, issue_search_config, parse_search_query
  11. from sentry.exceptions import InvalidSearchQuery
  12. from sentry.grouping.grouptype import ErrorGroupType
  13. from sentry.issues.grouptype import (
  14. FeedbackGroup,
  15. NoiseConfig,
  16. PerformanceNPlusOneGroupType,
  17. PerformanceRenderBlockingAssetSpanGroupType,
  18. ProfileFileIOGroupType,
  19. )
  20. from sentry.issues.ingest import send_issue_occurrence_to_eventstream
  21. from sentry.models.environment import Environment
  22. from sentry.models.group import Group, GroupStatus
  23. from sentry.models.groupassignee import GroupAssignee
  24. from sentry.models.groupbookmark import GroupBookmark
  25. from sentry.models.groupenvironment import GroupEnvironment
  26. from sentry.models.grouphistory import GroupHistoryStatus, record_group_history
  27. from sentry.models.groupowner import GroupOwner
  28. from sentry.models.groupsubscription import GroupSubscription
  29. from sentry.search.snuba.backend import EventsDatasetSnubaSearchBackend, SnubaSearchBackendBase
  30. from sentry.search.snuba.executors import TrendsSortWeights
  31. from sentry.snuba.dataset import Dataset
  32. from sentry.testutils.cases import SnubaTestCase, TestCase, TransactionTestCase
  33. from sentry.testutils.helpers import Feature, apply_feature_flag_on_cls
  34. from sentry.testutils.helpers.datetime import before_now
  35. from sentry.types.group import GroupSubStatus, PriorityLevel
  36. from sentry.utils import json
  37. from sentry.utils.snuba import SENTRY_SNUBA_MAP
  38. from tests.sentry.issues.test_utils import OccurrenceTestMixin
  39. def date_to_query_format(date):
  40. return date.strftime("%Y-%m-%dT%H:%M:%S")
  41. class SharedSnubaMixin(SnubaTestCase):
  42. @property
  43. def backend(self) -> SnubaSearchBackendBase:
  44. raise NotImplementedError(self)
  45. def build_search_filter(self, query, projects=None, user=None, environments=None):
  46. user = user if user is not None else self.user
  47. projects = projects if projects is not None else [self.project]
  48. return convert_query_values(parse_search_query(query), projects, user, environments)
  49. def make_query(
  50. self,
  51. projects=None,
  52. search_filter_query=None,
  53. user=None,
  54. environments=None,
  55. sort_by="date",
  56. limit=None,
  57. count_hits=False,
  58. date_from=None,
  59. date_to=None,
  60. cursor=None,
  61. aggregate_kwargs=None,
  62. ):
  63. search_filters = []
  64. projects = projects if projects is not None else [self.project]
  65. if search_filter_query is not None:
  66. search_filters = self.build_search_filter(
  67. search_filter_query, projects, user=user, environments=environments
  68. )
  69. kwargs = {}
  70. if limit is not None:
  71. kwargs["limit"] = limit
  72. if aggregate_kwargs:
  73. kwargs["aggregate_kwargs"] = {"trends": {**aggregate_kwargs}}
  74. return self.backend.query(
  75. projects,
  76. search_filters=search_filters,
  77. environments=environments,
  78. count_hits=count_hits,
  79. sort_by=sort_by,
  80. date_from=date_from,
  81. date_to=date_to,
  82. cursor=cursor,
  83. **kwargs,
  84. )
  85. def store_event(self, data, *args, **kwargs):
  86. event = super().store_event(data, *args, **kwargs)
  87. environment_name = data.get("environment")
  88. if environment_name:
  89. GroupEnvironment.objects.filter(
  90. group_id=event.group_id,
  91. environment__name=environment_name,
  92. first_seen__gt=event.datetime,
  93. ).update(first_seen=event.datetime)
  94. return event
  95. class EventsDatasetTestSetup(SharedSnubaMixin):
  96. @property
  97. def backend(self):
  98. return EventsDatasetSnubaSearchBackend()
  99. def setUp(self):
  100. super().setUp()
  101. self.base_datetime = before_now(days=3).replace(microsecond=0)
  102. event1_timestamp = (self.base_datetime - timedelta(days=21)).isoformat()
  103. self.event1 = self.store_event(
  104. data={
  105. "fingerprint": ["put-me-in-group1"],
  106. "event_id": "a" * 32,
  107. "message": "foo. Indeed, this message is intended to be greater than 256 characters such that we can put this 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.",
  108. "environment": "production",
  109. "tags": {"server": "example.com", "sentry:user": "event1@example.com"},
  110. "timestamp": event1_timestamp,
  111. "stacktrace": {"frames": [{"module": "group1"}]},
  112. "level": "fatal",
  113. },
  114. project_id=self.project.id,
  115. )
  116. self.event3 = self.store_event(
  117. data={
  118. "fingerprint": ["put-me-in-group1"],
  119. "event_id": "c" * 32,
  120. "message": "group1",
  121. "environment": "production",
  122. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  123. "timestamp": self.base_datetime.isoformat(),
  124. "stacktrace": {"frames": [{"module": "group1"}]},
  125. "level": "fatal",
  126. },
  127. project_id=self.project.id,
  128. )
  129. self.group1 = Group.objects.get(id=self.event1.group.id)
  130. assert self.group1.id == self.event1.group.id
  131. assert self.group1.id == self.event3.group.id
  132. assert self.group1.first_seen == self.event1.datetime
  133. assert self.group1.last_seen == self.event3.datetime
  134. self.group1.times_seen = 5
  135. self.group1.status = GroupStatus.UNRESOLVED
  136. self.group1.substatus = GroupSubStatus.ONGOING
  137. self.group1.priority = PriorityLevel.HIGH
  138. self.group1.update(type=ErrorGroupType.type_id)
  139. self.group1.save()
  140. self.store_group(self.group1)
  141. self.event2 = self.store_event(
  142. data={
  143. "fingerprint": ["put-me-in-group2"],
  144. "event_id": "b" * 32,
  145. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  146. "message": "bar",
  147. "stacktrace": {"frames": [{"module": "group2"}]},
  148. "environment": "staging",
  149. "tags": {
  150. "server": "example.com",
  151. "url": "http://example.com",
  152. "sentry:user": "event2@example.com",
  153. },
  154. "level": "error",
  155. },
  156. project_id=self.project.id,
  157. )
  158. self.group2 = Group.objects.get(id=self.event2.group.id)
  159. assert self.group2.id == self.event2.group.id
  160. assert self.group2.first_seen == self.group2.last_seen == self.event2.datetime
  161. self.group2.status = GroupStatus.RESOLVED
  162. self.group2.substatus = None
  163. self.group2.times_seen = 10
  164. self.group2.update(type=ErrorGroupType.type_id)
  165. self.group2.priority = PriorityLevel.HIGH
  166. self.group2.save()
  167. self.store_group(self.group2)
  168. GroupBookmark.objects.create(
  169. user_id=self.user.id, group=self.group2, project=self.group2.project
  170. )
  171. GroupAssignee.objects.create(
  172. user_id=self.user.id, group=self.group2, project=self.group2.project
  173. )
  174. GroupSubscription.objects.create(
  175. user_id=self.user.id, group=self.group1, project=self.group1.project, is_active=True
  176. )
  177. GroupSubscription.objects.create(
  178. user_id=self.user.id, group=self.group2, project=self.group2.project, is_active=False
  179. )
  180. self.environments = {
  181. "production": self.event1.get_environment(),
  182. "staging": self.event2.get_environment(),
  183. }
  184. def set_up_multi_project(self):
  185. self.project2 = self.create_project(organization=self.project.organization)
  186. self.event_p2 = self.store_event(
  187. data={
  188. "event_id": "a" * 32,
  189. "fingerprint": ["put-me-in-groupP2"],
  190. "timestamp": (self.base_datetime - timedelta(days=21)).isoformat(),
  191. "message": "foo",
  192. "stacktrace": {"frames": [{"module": "group_p2"}]},
  193. "tags": {"server": "example.com"},
  194. "environment": "production",
  195. },
  196. project_id=self.project2.id,
  197. )
  198. self.group_p2 = Group.objects.get(id=self.event_p2.group.id)
  199. self.group_p2.times_seen = 6
  200. self.group_p2.last_seen = self.base_datetime - timedelta(days=1)
  201. self.group_p2.save()
  202. self.store_group(self.group_p2)
  203. def create_group_with_integration_external_issue(self, environment="production"):
  204. event = self.store_event(
  205. data={
  206. "fingerprint": ["linked_group1"],
  207. "event_id": uuid.uuid4().hex,
  208. "timestamp": self.base_datetime.isoformat(),
  209. "environment": environment,
  210. },
  211. project_id=self.project.id,
  212. )
  213. integration, _ = self.create_provider_integration_for(
  214. event.group.organization, self.user, provider="example", name="Example"
  215. )
  216. self.create_integration_external_issue(
  217. group=event.group,
  218. integration=integration,
  219. key="APP-123",
  220. )
  221. return event.group
  222. def create_group_with_platform_external_issue(self, environment="production"):
  223. event = self.store_event(
  224. data={
  225. "fingerprint": ["linked_group2"],
  226. "event_id": uuid.uuid4().hex,
  227. "timestamp": self.base_datetime.isoformat(),
  228. "environment": environment,
  229. },
  230. project_id=self.project.id,
  231. )
  232. self.create_platform_external_issue(
  233. group=event.group,
  234. service_type="sentry-app",
  235. display_name="App#issue-1",
  236. web_url="https://example.com/app/issues/1",
  237. )
  238. return event.group
  239. def run_test_query(
  240. self, query, expected_groups, expected_negative_groups=None, environments=None, user=None
  241. ):
  242. results = self.make_query(search_filter_query=query, environments=environments, user=user)
  243. def sort_key(result):
  244. return result.id
  245. assert sorted(results, key=sort_key) == sorted(expected_groups, key=sort_key)
  246. if expected_negative_groups is not None:
  247. results = self.make_query(search_filter_query=f"!{query}", user=user)
  248. assert sorted(results, key=sort_key) == sorted(expected_negative_groups, key=sort_key)
  249. class EventsSnubaSearchTestCases(EventsDatasetTestSetup):
  250. def test_query(self):
  251. results = self.make_query(search_filter_query="foo")
  252. assert set(results) == {self.group1}
  253. results = self.make_query(search_filter_query="bar")
  254. assert set(results) == {self.group2}
  255. def test_query_multi_project(self):
  256. self.set_up_multi_project()
  257. results = self.make_query([self.project, self.project2], search_filter_query="foo")
  258. assert set(results) == {self.group1, self.group_p2}
  259. def test_query_with_environment(self):
  260. results = self.make_query(
  261. environments=[self.environments["production"]], search_filter_query="foo"
  262. )
  263. assert set(results) == {self.group1}
  264. results = self.make_query(
  265. environments=[self.environments["production"]], search_filter_query="bar"
  266. )
  267. assert set(results) == set()
  268. results = self.make_query(
  269. environments=[self.environments["staging"]], search_filter_query="bar"
  270. )
  271. assert set(results) == {self.group2}
  272. def test_query_for_text_in_long_message(self):
  273. results = self.make_query(
  274. [self.project],
  275. environments=[self.environments["production"]],
  276. search_filter_query="santryrox",
  277. )
  278. assert set(results) == {self.group1}
  279. def test_multi_environments(self):
  280. self.set_up_multi_project()
  281. results = self.make_query(
  282. [self.project, self.project2],
  283. environments=[self.environments["production"], self.environments["staging"]],
  284. )
  285. assert set(results) == {self.group1, self.group2, self.group_p2}
  286. def test_query_with_environment_multi_project(self):
  287. self.set_up_multi_project()
  288. results = self.make_query(
  289. [self.project, self.project2],
  290. environments=[self.environments["production"]],
  291. search_filter_query="foo",
  292. )
  293. assert set(results) == {self.group1, self.group_p2}
  294. results = self.make_query(
  295. [self.project, self.project2],
  296. environments=[self.environments["production"]],
  297. search_filter_query="bar",
  298. )
  299. assert set(results) == set()
  300. def test_query_timestamp(self):
  301. results = self.make_query(
  302. [self.project],
  303. environments=[self.environments["production"]],
  304. search_filter_query=f"timestamp:>{self.event1.datetime.isoformat()} timestamp:<{self.event3.datetime.isoformat()}",
  305. )
  306. assert set(results) == {self.group1}
  307. def test_sort(self):
  308. results = self.make_query(sort_by="date")
  309. assert list(results) == [self.group1, self.group2]
  310. results = self.make_query(sort_by="new")
  311. assert list(results) == [self.group2, self.group1]
  312. results = self.make_query(sort_by="freq")
  313. assert list(results) == [self.group1, self.group2]
  314. results = self.make_query(sort_by="trends")
  315. assert list(results) == [self.group2, self.group1]
  316. results = self.make_query(sort_by="user")
  317. assert list(results) == [self.group1, self.group2]
  318. def test_trends_sort(self):
  319. weights: TrendsSortWeights = {
  320. "log_level": 5,
  321. "has_stacktrace": 5,
  322. "relative_volume": 1,
  323. "event_halflife_hours": 4,
  324. "issue_halflife_hours": 24 * 7,
  325. "v2": False,
  326. "norm": False,
  327. }
  328. results = self.make_query(
  329. sort_by="trends",
  330. aggregate_kwargs=weights,
  331. )
  332. assert list(results) == [self.group2, self.group1]
  333. def test_sort_with_environment(self):
  334. for dt in [
  335. self.group1.first_seen + timedelta(days=1),
  336. self.group1.first_seen + timedelta(days=2),
  337. self.group1.last_seen + timedelta(days=1),
  338. ]:
  339. self.store_event(
  340. data={
  341. "fingerprint": ["put-me-in-group2"],
  342. "timestamp": dt.isoformat(),
  343. "stacktrace": {"frames": [{"module": "group2"}]},
  344. "environment": "production",
  345. "message": "group2",
  346. },
  347. project_id=self.project.id,
  348. )
  349. results = self.make_query(environments=[self.environments["production"]], sort_by="date")
  350. assert list(results) == [self.group2, self.group1]
  351. results = self.make_query(environments=[self.environments["production"]], sort_by="new")
  352. assert list(results) == [self.group2, self.group1]
  353. results = self.make_query(environments=[self.environments["production"]], sort_by="freq")
  354. assert list(results) == [self.group2, self.group1]
  355. results = self.make_query(environments=[self.environments["production"]], sort_by="trends")
  356. assert list(results) == [self.group2, self.group1]
  357. results = self.make_query(environments=[self.environments["production"]], sort_by="user")
  358. assert list(results) == [self.group1, self.group2]
  359. def test_status(self):
  360. results = self.make_query(search_filter_query="is:unresolved")
  361. assert set(results) == {self.group1}
  362. results = self.make_query(search_filter_query="is:resolved")
  363. assert set(results) == {self.group2}
  364. event_3 = self.store_event(
  365. data={
  366. "fingerprint": ["put-me-in-group3"],
  367. "event_id": "c" * 32,
  368. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  369. },
  370. project_id=self.project.id,
  371. )
  372. group_3 = event_3.group
  373. group_3.status = GroupStatus.MUTED
  374. group_3.substatus = None
  375. group_3.save()
  376. self.run_test_query("status:[unresolved, resolved]", [self.group1, self.group2], [group_3])
  377. self.run_test_query("status:[resolved, muted]", [self.group2, group_3], [self.group1])
  378. def test_substatus(self):
  379. results = self.make_query(search_filter_query="is:ongoing")
  380. assert set(results) == {self.group1}
  381. def test_category(self):
  382. results = self.make_query(search_filter_query="issue.category:error")
  383. assert set(results) == {self.group1, self.group2}
  384. event_3 = self.store_event(
  385. data={
  386. "fingerprint": ["put-me-in-group3"],
  387. "event_id": "c" * 32,
  388. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  389. },
  390. project_id=self.project.id,
  391. )
  392. group_3 = event_3.group
  393. group_3.update(type=PerformanceNPlusOneGroupType.type_id)
  394. results = self.make_query(search_filter_query="issue.category:performance")
  395. assert set(results) == {group_3}
  396. results = self.make_query(search_filter_query="issue.category:[error, performance]")
  397. assert set(results) == {self.group1, self.group2, group_3}
  398. with pytest.raises(InvalidSearchQuery):
  399. self.make_query(search_filter_query="issue.category:hellboy")
  400. def test_not_perf_category(self):
  401. results = self.make_query(search_filter_query="issue.category:error foo")
  402. assert set(results) == {self.group1}
  403. not_results = self.make_query(search_filter_query="!issue.category:performance foo")
  404. assert set(not_results) == {self.group1}
  405. def test_type(self):
  406. results = self.make_query(search_filter_query="issue.type:error")
  407. assert set(results) == {self.group1, self.group2}
  408. event_3 = self.store_event(
  409. data={
  410. "fingerprint": ["put-me-in-group3"],
  411. "event_id": "c" * 32,
  412. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  413. "type": PerformanceNPlusOneGroupType.type_id,
  414. },
  415. project_id=self.project.id,
  416. )
  417. group_3 = event_3.group
  418. group_3.update(type=PerformanceNPlusOneGroupType.type_id)
  419. results = self.make_query(
  420. search_filter_query="issue.type:performance_n_plus_one_db_queries"
  421. )
  422. assert set(results) == {group_3}
  423. event_4 = self.store_event(
  424. data={
  425. "fingerprint": ["put-me-in-group4"],
  426. "event_id": "d" * 32,
  427. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  428. },
  429. project_id=self.project.id,
  430. )
  431. group_4 = event_4.group
  432. group_4.update(type=PerformanceRenderBlockingAssetSpanGroupType.type_id)
  433. results = self.make_query(
  434. search_filter_query="issue.type:performance_render_blocking_asset_span"
  435. )
  436. assert set(results) == {group_4}
  437. results = self.make_query(
  438. search_filter_query="issue.type:[performance_render_blocking_asset_span, performance_n_plus_one_db_queries, error]"
  439. )
  440. assert set(results) == {self.group1, self.group2, group_3, group_4}
  441. with pytest.raises(InvalidSearchQuery):
  442. self.make_query(search_filter_query="issue.type:performance_i_dont_exist")
  443. def test_status_with_environment(self):
  444. results = self.make_query(
  445. environments=[self.environments["production"]], search_filter_query="is:unresolved"
  446. )
  447. assert set(results) == {self.group1}
  448. results = self.make_query(
  449. environments=[self.environments["staging"]], search_filter_query="is:resolved"
  450. )
  451. assert set(results) == {self.group2}
  452. results = self.make_query(
  453. environments=[self.environments["production"]], search_filter_query="is:resolved"
  454. )
  455. assert set(results) == set()
  456. def test_tags(self):
  457. results = self.make_query(search_filter_query="environment:staging")
  458. assert set(results) == {self.group2}
  459. results = self.make_query(search_filter_query="environment:example.com")
  460. assert set(results) == set()
  461. results = self.make_query(search_filter_query="has:environment")
  462. assert set(results) == {self.group2, self.group1}
  463. results = self.make_query(search_filter_query="environment:staging server:example.com")
  464. assert set(results) == {self.group2}
  465. results = self.make_query(search_filter_query='url:"http://example.com"')
  466. assert set(results) == {self.group2}
  467. results = self.make_query(search_filter_query="environment:staging has:server")
  468. assert set(results) == {self.group2}
  469. results = self.make_query(search_filter_query="environment:staging server:bar.example.com")
  470. assert set(results) == set()
  471. def test_tags_with_environment(self):
  472. results = self.make_query(
  473. environments=[self.environments["production"]], search_filter_query="server:example.com"
  474. )
  475. assert set(results) == {self.group1}
  476. results = self.make_query(
  477. environments=[self.environments["staging"]], search_filter_query="server:example.com"
  478. )
  479. assert set(results) == {self.group2}
  480. results = self.make_query(
  481. environments=[self.environments["staging"]], search_filter_query="has:server"
  482. )
  483. assert set(results) == {self.group2}
  484. results = self.make_query(
  485. environments=[self.environments["production"]],
  486. search_filter_query='url:"http://example.com"',
  487. )
  488. assert set(results) == set()
  489. results = self.make_query(
  490. environments=[self.environments["staging"]],
  491. search_filter_query='url:"http://example.com"',
  492. )
  493. assert set(results) == {self.group2}
  494. results = self.make_query(
  495. environments=[self.environments["staging"]],
  496. search_filter_query="server:bar.example.com",
  497. )
  498. assert set(results) == set()
  499. def test_bookmarked_by(self):
  500. results = self.make_query(search_filter_query="bookmarks:%s" % self.user.username)
  501. assert set(results) == {self.group2}
  502. def test_bookmarked_by_in_syntax(self):
  503. self.run_test_query(f"bookmarks:[{self.user.username}]", [self.group2], [self.group1])
  504. user_2 = self.create_user()
  505. GroupBookmark.objects.create(
  506. user_id=user_2.id, group=self.group1, project=self.group2.project
  507. )
  508. self.run_test_query(
  509. f"bookmarks:[{self.user.username}, {user_2.username}]", [self.group2, self.group1], []
  510. )
  511. def test_bookmarked_by_with_environment(self):
  512. results = self.make_query(
  513. environments=[self.environments["staging"]],
  514. search_filter_query="bookmarks:%s" % self.user.username,
  515. )
  516. assert set(results) == {self.group2}
  517. results = self.make_query(
  518. environments=[self.environments["production"]],
  519. search_filter_query="bookmarks:%s" % self.user.username,
  520. )
  521. assert set(results) == set()
  522. def test_search_filter_query_with_custom_trends_tag(self):
  523. trends = "high"
  524. self.store_event(
  525. data={
  526. "fingerprint": ["put-me-in-group2"],
  527. "timestamp": (self.group2.first_seen + timedelta(days=1)).isoformat(),
  528. "stacktrace": {"frames": [{"module": "group2"}]},
  529. "message": "group2",
  530. "tags": {"trends": trends},
  531. },
  532. project_id=self.project.id,
  533. )
  534. results = self.make_query(search_filter_query="trends:%s" % trends)
  535. assert set(results) == {self.group2}
  536. def test_search_filter_query_with_custom_trends_tag_and_trends_sort(self):
  537. trends = "high"
  538. for i in range(1, 3):
  539. self.store_event(
  540. data={
  541. "fingerprint": ["put-me-in-group1"],
  542. "timestamp": (self.group2.last_seen + timedelta(days=i)).isoformat(),
  543. "stacktrace": {"frames": [{"module": "group1"}]},
  544. "message": "group1",
  545. "tags": {"trends": trends},
  546. },
  547. project_id=self.project.id,
  548. )
  549. self.store_event(
  550. data={
  551. "fingerprint": ["put-me-in-group2"],
  552. "timestamp": (self.group2.last_seen + timedelta(days=2)).isoformat(),
  553. "stacktrace": {"frames": [{"module": "group2"}]},
  554. "message": "group2",
  555. "tags": {"trends": trends},
  556. },
  557. project_id=self.project.id,
  558. )
  559. results = self.make_query(search_filter_query="trends:%s" % trends, sort_by="trends")
  560. assert list(results) == [self.group2, self.group1]
  561. def test_search_tag_overlapping_with_internal_fields(self):
  562. # Using a tag of email overlaps with the promoted user.email column in events.
  563. # We don't want to bypass public schema limits in issue search.
  564. self.store_event(
  565. data={
  566. "fingerprint": ["put-me-in-group2"],
  567. "timestamp": (self.group2.first_seen + timedelta(days=1)).isoformat(),
  568. "stacktrace": {"frames": [{"module": "group2"}]},
  569. "message": "group2",
  570. "tags": {"email": "tags@example.com"},
  571. },
  572. project_id=self.project.id,
  573. )
  574. results = self.make_query(search_filter_query="email:tags@example.com")
  575. assert set(results) == {self.group2}
  576. def test_project(self):
  577. results = self.make_query([self.create_project(name="other")])
  578. assert set(results) == set()
  579. def test_pagination(self):
  580. for options_set in [
  581. {"snuba.search.min-pre-snuba-candidates": None},
  582. {"snuba.search.min-pre-snuba-candidates": 500},
  583. ]:
  584. with self.options(options_set):
  585. results = self.backend.query([self.project], limit=1, sort_by="date")
  586. assert set(results) == {self.group1}
  587. assert not results.prev.has_results
  588. assert results.next.has_results
  589. results = self.backend.query(
  590. [self.project], cursor=results.next, limit=1, sort_by="date"
  591. )
  592. assert set(results) == {self.group2}
  593. assert results.prev.has_results
  594. assert not results.next.has_results
  595. # note: previous cursor
  596. results = self.backend.query(
  597. [self.project], cursor=results.prev, limit=1, sort_by="date"
  598. )
  599. assert set(results) == {self.group1}
  600. assert results.prev.has_results
  601. assert results.next.has_results
  602. # note: previous cursor, paging too far into 0 results
  603. results = self.backend.query(
  604. [self.project], cursor=results.prev, limit=1, sort_by="date"
  605. )
  606. assert set(results) == set()
  607. assert not results.prev.has_results
  608. assert results.next.has_results
  609. results = self.backend.query(
  610. [self.project], cursor=results.next, limit=1, sort_by="date"
  611. )
  612. assert set(results) == {self.group1}
  613. assert results.prev.has_results
  614. assert results.next.has_results
  615. results = self.backend.query(
  616. [self.project], cursor=results.next, limit=1, sort_by="date"
  617. )
  618. assert set(results) == {self.group2}
  619. assert results.prev.has_results
  620. assert not results.next.has_results
  621. results = self.backend.query(
  622. [self.project], cursor=results.next, limit=1, sort_by="date"
  623. )
  624. assert set(results) == set()
  625. assert results.prev.has_results
  626. assert not results.next.has_results
  627. def test_pagination_with_environment(self):
  628. for dt in [
  629. self.group1.first_seen + timedelta(days=1),
  630. self.group1.first_seen + timedelta(days=2),
  631. self.group1.last_seen + timedelta(days=1),
  632. ]:
  633. self.store_event(
  634. data={
  635. "fingerprint": ["put-me-in-group2"],
  636. "timestamp": dt.isoformat(),
  637. "environment": "production",
  638. "message": "group2",
  639. "stacktrace": {"frames": [{"module": "group2"}]},
  640. },
  641. project_id=self.project.id,
  642. )
  643. results = self.backend.query(
  644. [self.project],
  645. environments=[self.environments["production"]],
  646. sort_by="date",
  647. limit=1,
  648. count_hits=True,
  649. )
  650. assert list(results) == [self.group2]
  651. assert results.hits == 2
  652. results = self.backend.query(
  653. [self.project],
  654. environments=[self.environments["production"]],
  655. sort_by="date",
  656. limit=1,
  657. cursor=results.next,
  658. count_hits=True,
  659. )
  660. assert list(results) == [self.group1]
  661. assert results.hits == 2
  662. results = self.backend.query(
  663. [self.project],
  664. environments=[self.environments["production"]],
  665. sort_by="date",
  666. limit=1,
  667. cursor=results.next,
  668. count_hits=True,
  669. )
  670. assert list(results) == []
  671. assert results.hits == 2
  672. def test_age_filter(self):
  673. results = self.make_query(
  674. search_filter_query="firstSeen:>=%s" % date_to_query_format(self.group2.first_seen)
  675. )
  676. assert set(results) == {self.group2}
  677. results = self.make_query(
  678. search_filter_query="firstSeen:<=%s"
  679. % date_to_query_format(self.group1.first_seen + timedelta(minutes=1))
  680. )
  681. assert set(results) == {self.group1}
  682. results = self.make_query(
  683. search_filter_query="firstSeen:>=%s firstSeen:<=%s"
  684. % (
  685. date_to_query_format(self.group1.first_seen),
  686. date_to_query_format(self.group1.first_seen + timedelta(minutes=1)),
  687. )
  688. )
  689. assert set(results) == {self.group1}
  690. def test_age_filter_with_environment(self):
  691. # add time instead to make it greater than or less than as needed.
  692. group1_first_seen = GroupEnvironment.objects.get(
  693. environment=self.environments["production"], group=self.group1
  694. ).first_seen
  695. assert group1_first_seen is not None
  696. results = self.make_query(
  697. environments=[self.environments["production"]],
  698. search_filter_query="firstSeen:>=%s" % date_to_query_format(group1_first_seen),
  699. )
  700. assert set(results) == {self.group1}
  701. results = self.make_query(
  702. environments=[self.environments["production"]],
  703. search_filter_query="firstSeen:<=%s" % date_to_query_format(group1_first_seen),
  704. )
  705. assert set(results) == {self.group1}
  706. results = self.make_query(
  707. environments=[self.environments["production"]],
  708. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  709. )
  710. assert set(results) == set()
  711. self.store_event(
  712. data={
  713. "fingerprint": ["put-me-in-group1"],
  714. "timestamp": (group1_first_seen + timedelta(days=1)).isoformat(),
  715. "message": "group1",
  716. "stacktrace": {"frames": [{"module": "group1"}]},
  717. "environment": "development",
  718. },
  719. project_id=self.project.id,
  720. )
  721. results = self.make_query(
  722. environments=[self.environments["production"]],
  723. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  724. )
  725. assert set(results) == set()
  726. results = self.make_query(
  727. environments=[Environment.objects.get(name="development")],
  728. search_filter_query="firstSeen:>%s" % date_to_query_format(group1_first_seen),
  729. )
  730. assert set(results) == {self.group1}
  731. def test_times_seen_filter(self):
  732. results = self.make_query([self.project], search_filter_query="times_seen:2")
  733. assert set(results) == {self.group1}
  734. results = self.make_query([self.project], search_filter_query="times_seen:>=2")
  735. assert set(results) == {self.group1}
  736. results = self.make_query([self.project], search_filter_query="times_seen:<=1")
  737. assert set(results) == {self.group2}
  738. def test_last_seen_filter(self):
  739. results = self.make_query(
  740. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen)
  741. )
  742. assert set(results) == {self.group1}
  743. results = self.make_query(
  744. search_filter_query="lastSeen:>=%s lastSeen:<=%s"
  745. % (
  746. date_to_query_format(self.group1.last_seen),
  747. date_to_query_format(self.group1.last_seen + timedelta(minutes=1)),
  748. )
  749. )
  750. assert set(results) == {self.group1}
  751. def test_last_seen_filter_with_environment(self):
  752. results = self.make_query(
  753. environments=[self.environments["production"]],
  754. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  755. )
  756. assert set(results) == {self.group1}
  757. results = self.make_query(
  758. environments=[self.environments["production"]],
  759. search_filter_query="lastSeen:<=%s" % date_to_query_format(self.group1.last_seen),
  760. )
  761. assert set(results) == {self.group1}
  762. results = self.make_query(
  763. environments=[self.environments["production"]],
  764. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  765. )
  766. assert set(results) == set()
  767. self.store_event(
  768. data={
  769. "fingerprint": ["put-me-in-group1"],
  770. "timestamp": (self.group1.last_seen + timedelta(days=1)).isoformat(),
  771. "message": "group1",
  772. "stacktrace": {"frames": [{"module": "group1"}]},
  773. "environment": "development",
  774. },
  775. project_id=self.project.id,
  776. )
  777. self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1))
  778. results = self.make_query(
  779. environments=[self.environments["production"]],
  780. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  781. )
  782. assert set(results) == set()
  783. results = self.make_query(
  784. environments=[Environment.objects.get(name="development")],
  785. search_filter_query="lastSeen:>%s" % date_to_query_format(self.group1.last_seen),
  786. )
  787. assert set(results) == set()
  788. results = self.make_query(
  789. environments=[Environment.objects.get(name="development")],
  790. search_filter_query="lastSeen:>=%s" % date_to_query_format(self.group1.last_seen),
  791. )
  792. assert set(results) == {self.group1}
  793. def test_date_filter(self):
  794. results = self.make_query(
  795. date_from=self.event2.datetime,
  796. search_filter_query="timestamp:>=%s" % date_to_query_format(self.event2.datetime),
  797. )
  798. assert set(results) == {self.group1, self.group2}
  799. results = self.make_query(
  800. date_to=self.event1.datetime + timedelta(minutes=1),
  801. search_filter_query="timestamp:<=%s"
  802. % date_to_query_format(self.event1.datetime + timedelta(minutes=1)),
  803. )
  804. assert set(results) == {self.group1}
  805. results = self.make_query(
  806. date_from=self.event1.datetime,
  807. date_to=self.event2.datetime + timedelta(minutes=1),
  808. search_filter_query="timestamp:>=%s timestamp:<=%s"
  809. % (
  810. date_to_query_format(self.event1.datetime),
  811. date_to_query_format(self.event2.datetime + timedelta(minutes=1)),
  812. ),
  813. )
  814. assert set(results) == {self.group1, self.group2}
  815. # Test with `Z` utc marker, should be equivalent
  816. results = self.make_query(
  817. date_from=self.event1.datetime,
  818. date_to=self.event2.datetime + timedelta(minutes=1),
  819. search_filter_query="timestamp:>=%s timestamp:<=%s"
  820. % (
  821. date_to_query_format(self.event1.datetime) + "Z",
  822. date_to_query_format(self.event2.datetime + timedelta(minutes=1)) + "Z",
  823. ),
  824. )
  825. assert set(results) == {self.group1, self.group2}
  826. def test_date_filter_with_environment(self):
  827. results = self.backend.query(
  828. [self.project],
  829. environments=[self.environments["production"]],
  830. date_from=self.event2.datetime,
  831. )
  832. assert set(results) == {self.group1}
  833. results = self.backend.query(
  834. [self.project],
  835. environments=[self.environments["production"]],
  836. date_to=self.event1.datetime + timedelta(minutes=1),
  837. )
  838. assert set(results) == {self.group1}
  839. results = self.backend.query(
  840. [self.project],
  841. environments=[self.environments["staging"]],
  842. date_from=self.event1.datetime,
  843. date_to=self.event2.datetime + timedelta(minutes=1),
  844. )
  845. assert set(results) == {self.group2}
  846. def test_linked(self):
  847. linked_group1 = self.create_group_with_integration_external_issue()
  848. linked_group2 = self.create_group_with_platform_external_issue()
  849. results = self.make_query(search_filter_query="is:unlinked")
  850. assert set(results) == {self.group1, self.group2}
  851. results = self.make_query(search_filter_query="is:linked")
  852. assert set(results) == {linked_group1, linked_group2}
  853. def test_linked_with_only_integration_external_issue(self):
  854. linked_group = self.create_group_with_integration_external_issue()
  855. results = self.make_query(search_filter_query="is:unlinked")
  856. assert set(results) == {self.group1, self.group2}
  857. results = self.make_query(search_filter_query="is:linked")
  858. assert set(results) == {linked_group}
  859. def test_linked_with_only_platform_external_issue(self):
  860. linked_group = self.create_group_with_platform_external_issue()
  861. results = self.make_query(search_filter_query="is:unlinked")
  862. assert set(results) == {self.group1, self.group2}
  863. results = self.make_query(search_filter_query="is:linked")
  864. assert set(results) == {linked_group}
  865. def test_linked_with_environment(self):
  866. linked_group1 = self.create_group_with_integration_external_issue(environment="production")
  867. linked_group2 = self.create_group_with_platform_external_issue(environment="staging")
  868. results = self.make_query(
  869. environments=[self.environments["production"]], search_filter_query="is:unlinked"
  870. )
  871. assert set(results) == {self.group1}
  872. results = self.make_query(
  873. environments=[self.environments["staging"]], search_filter_query="is:unlinked"
  874. )
  875. assert set(results) == {self.group2}
  876. results = self.make_query(
  877. environments=[self.environments["production"]], search_filter_query="is:linked"
  878. )
  879. assert set(results) == {linked_group1}
  880. results = self.make_query(
  881. environments=[self.environments["staging"]], search_filter_query="is:linked"
  882. )
  883. assert set(results) == {linked_group2}
  884. def test_unassigned(self):
  885. results = self.make_query(search_filter_query="is:unassigned")
  886. assert set(results) == {self.group1}
  887. results = self.make_query(search_filter_query="is:assigned")
  888. assert set(results) == {self.group2}
  889. def test_unassigned_with_environment(self):
  890. results = self.make_query(
  891. environments=[self.environments["production"]], search_filter_query="is:unassigned"
  892. )
  893. assert set(results) == {self.group1}
  894. results = self.make_query(
  895. environments=[self.environments["staging"]], search_filter_query="is:assigned"
  896. )
  897. assert set(results) == {self.group2}
  898. results = self.make_query(
  899. environments=[self.environments["production"]], search_filter_query="is:assigned"
  900. )
  901. assert set(results) == set()
  902. def test_assigned_to(self):
  903. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  904. assert set(results) == {self.group2}
  905. # test team assignee
  906. ga = GroupAssignee.objects.get(
  907. user_id=self.user.id, group=self.group2, project=self.group2.project
  908. )
  909. ga.update(team=self.team, user_id=None)
  910. assert GroupAssignee.objects.get(id=ga.id).user_id is None
  911. results = self.make_query(search_filter_query="assigned:%s" % self.user.username)
  912. assert set(results) == set()
  913. # test when there should be no results
  914. other_user = self.create_user()
  915. results = self.make_query(search_filter_query="assigned:%s" % other_user.username)
  916. assert set(results) == set()
  917. owner = self.create_user()
  918. self.create_member(
  919. organization=self.project.organization, user=owner, role="owner", teams=[]
  920. )
  921. # test that owners don't see results for all teams
  922. results = self.make_query(search_filter_query="assigned:%s" % owner.username)
  923. assert set(results) == set()
  924. def test_assigned_to_me_my_teams(self):
  925. my_team_group = self.store_event(
  926. data={
  927. "fingerprint": ["put-me-in-group-my-teams"],
  928. "event_id": "f" * 32,
  929. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  930. "message": "baz",
  931. "environment": "staging",
  932. "tags": {
  933. "server": "example.com",
  934. "url": "http://example.com",
  935. "sentry:user": "event2@example.com",
  936. },
  937. "level": "error",
  938. },
  939. project_id=self.project.id,
  940. ).group
  941. # assign the issue to my team instead of me
  942. GroupAssignee.objects.create(
  943. user_id=None, team_id=self.team.id, group=my_team_group, project=my_team_group.project
  944. )
  945. self.run_test_query(
  946. "assigned:me",
  947. [self.group2],
  948. user=self.user,
  949. )
  950. assert not GroupAssignee.objects.filter(user_id=self.user.id, group=my_team_group).exists()
  951. self.run_test_query(
  952. "assigned:my_teams",
  953. [my_team_group],
  954. user=self.user,
  955. )
  956. def test_assigned_to_me_my_teams_in_syntax(self):
  957. my_team_group = self.store_event(
  958. data={
  959. "fingerprint": ["put-me-in-group-my-teams"],
  960. "event_id": "f" * 32,
  961. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  962. "message": "baz",
  963. "environment": "staging",
  964. "tags": {
  965. "server": "example.com",
  966. "url": "http://example.com",
  967. "sentry:user": "event2@example.com",
  968. },
  969. "level": "error",
  970. },
  971. project_id=self.project.id,
  972. ).group
  973. # assign the issue to my team instead of me
  974. GroupAssignee.objects.create(
  975. user_id=None, team_id=self.team.id, group=my_team_group, project=my_team_group.project
  976. )
  977. self.run_test_query(
  978. "assigned:[me]",
  979. [self.group2],
  980. user=self.user,
  981. )
  982. assert not GroupAssignee.objects.filter(user_id=self.user.id, group=my_team_group).exists()
  983. self.run_test_query(
  984. "assigned:[me]",
  985. [self.group2],
  986. user=self.user,
  987. )
  988. self.run_test_query(
  989. "assigned:[my_teams]",
  990. [my_team_group],
  991. user=self.user,
  992. )
  993. self.run_test_query(
  994. "assigned:[me, my_teams]",
  995. [self.group2, my_team_group],
  996. user=self.user,
  997. )
  998. def test_assigned_to_in_syntax(self):
  999. group_3 = self.store_event(
  1000. data={
  1001. "fingerprint": ["put-me-in-group3"],
  1002. "event_id": "c" * 32,
  1003. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  1004. },
  1005. project_id=self.project.id,
  1006. ).group
  1007. group_3.status = GroupStatus.MUTED
  1008. group_3.substatus = None
  1009. group_3.save()
  1010. other_user = self.create_user()
  1011. self.run_test_query(
  1012. f"assigned:[{self.user.username}, {other_user.username}]",
  1013. [self.group2],
  1014. [self.group1, group_3],
  1015. )
  1016. GroupAssignee.objects.create(project=self.project, group=group_3, user_id=other_user.id)
  1017. self.run_test_query(
  1018. f"assigned:[{self.user.username}, {other_user.username}]",
  1019. [self.group2, group_3],
  1020. [self.group1],
  1021. )
  1022. self.run_test_query(
  1023. f"assigned:[#{self.team.slug}, {other_user.username}]",
  1024. [group_3],
  1025. [self.group1, self.group2],
  1026. )
  1027. ga_2 = GroupAssignee.objects.get(
  1028. user_id=self.user.id, group=self.group2, project=self.group2.project
  1029. )
  1030. ga_2.update(team=self.team, user_id=None)
  1031. self.run_test_query(
  1032. f"assigned:[{self.user.username}, {other_user.username}]",
  1033. [group_3],
  1034. [self.group1, self.group2],
  1035. )
  1036. self.run_test_query(
  1037. f"assigned:[#{self.team.slug}, {other_user.username}]",
  1038. [self.group2, group_3],
  1039. [self.group1],
  1040. )
  1041. self.run_test_query(
  1042. f"assigned:[me, none, {other_user.username}]",
  1043. [self.group1, group_3],
  1044. [self.group2],
  1045. )
  1046. def test_assigned_or_suggested_in_syntax(self):
  1047. Group.objects.all().delete()
  1048. group = self.store_event(
  1049. data={
  1050. "timestamp": before_now(seconds=180).isoformat(),
  1051. "fingerprint": ["group-1"],
  1052. },
  1053. project_id=self.project.id,
  1054. ).group
  1055. group1 = self.store_event(
  1056. data={
  1057. "timestamp": before_now(seconds=185).isoformat(),
  1058. "fingerprint": ["group-2"],
  1059. },
  1060. project_id=self.project.id,
  1061. ).group
  1062. group2 = self.store_event(
  1063. data={
  1064. "timestamp": before_now(seconds=190).isoformat(),
  1065. "fingerprint": ["group-3"],
  1066. },
  1067. project_id=self.project.id,
  1068. ).group
  1069. assigned_group = self.store_event(
  1070. data={
  1071. "timestamp": before_now(seconds=195).isoformat(),
  1072. "fingerprint": ["group-4"],
  1073. },
  1074. project_id=self.project.id,
  1075. ).group
  1076. assigned_to_other_group = self.store_event(
  1077. data={
  1078. "timestamp": before_now(seconds=195).isoformat(),
  1079. "fingerprint": ["group-5"],
  1080. },
  1081. project_id=self.project.id,
  1082. ).group
  1083. self.run_test_query(
  1084. "assigned_or_suggested:[me]",
  1085. [],
  1086. [group, group1, group2, assigned_group, assigned_to_other_group],
  1087. )
  1088. GroupOwner.objects.create(
  1089. group=assigned_to_other_group,
  1090. project=self.project,
  1091. organization=self.organization,
  1092. type=0,
  1093. team_id=None,
  1094. user_id=self.user.id,
  1095. )
  1096. GroupOwner.objects.create(
  1097. group=group,
  1098. project=self.project,
  1099. organization=self.organization,
  1100. type=0,
  1101. team_id=None,
  1102. user_id=self.user.id,
  1103. )
  1104. self.run_test_query(
  1105. "assigned_or_suggested:[me]",
  1106. [group, assigned_to_other_group],
  1107. [group1, group2, assigned_group],
  1108. )
  1109. # 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)
  1110. other_user = self.create_user("other@user.com", is_superuser=False)
  1111. GroupAssignee.objects.create(
  1112. group=assigned_to_other_group,
  1113. project=self.project,
  1114. user_id=other_user.id,
  1115. )
  1116. self.run_test_query(
  1117. "assigned_or_suggested:[me]",
  1118. [group],
  1119. [group1, group2, assigned_group, assigned_to_other_group],
  1120. )
  1121. self.run_test_query(
  1122. f"assigned_or_suggested:[{other_user.email}]",
  1123. [assigned_to_other_group],
  1124. [group, group1, group2, assigned_group],
  1125. )
  1126. GroupAssignee.objects.create(
  1127. group=assigned_group, project=self.project, user_id=self.user.id
  1128. )
  1129. self.run_test_query(
  1130. f"assigned_or_suggested:[{self.user.email}]",
  1131. [assigned_group, group],
  1132. )
  1133. GroupOwner.objects.create(
  1134. group=group,
  1135. project=self.project,
  1136. organization=self.organization,
  1137. type=0,
  1138. team_id=self.team.id,
  1139. user_id=None,
  1140. )
  1141. self.run_test_query(
  1142. f"assigned_or_suggested:[#{self.team.slug}]",
  1143. [group],
  1144. )
  1145. self.run_test_query(
  1146. "assigned_or_suggested:[me, none]",
  1147. [group, group1, group2, assigned_group],
  1148. [assigned_to_other_group],
  1149. )
  1150. not_me = self.create_user(email="notme@sentry.io")
  1151. GroupOwner.objects.create(
  1152. group=group2,
  1153. project=self.project,
  1154. organization=self.organization,
  1155. type=0,
  1156. team_id=None,
  1157. user_id=not_me.id,
  1158. )
  1159. self.run_test_query(
  1160. "assigned_or_suggested:[me, none]",
  1161. [group, group1, assigned_group],
  1162. [assigned_to_other_group, group2],
  1163. )
  1164. GroupOwner.objects.filter(group=group, user_id=self.user.id).delete()
  1165. self.run_test_query(
  1166. f"assigned_or_suggested:[me, none, #{self.team.slug}]",
  1167. [group, group1, assigned_group],
  1168. [assigned_to_other_group, group2],
  1169. )
  1170. self.run_test_query(
  1171. f"assigned_or_suggested:[me, none, #{self.team.slug}, {not_me.email}]",
  1172. [group, group1, assigned_group, group2],
  1173. [assigned_to_other_group],
  1174. )
  1175. def test_assigned_or_suggested_my_teams(self):
  1176. Group.objects.all().delete()
  1177. group = self.store_event(
  1178. data={
  1179. "timestamp": before_now(seconds=180).isoformat(),
  1180. "fingerprint": ["group-1"],
  1181. },
  1182. project_id=self.project.id,
  1183. ).group
  1184. group1 = self.store_event(
  1185. data={
  1186. "timestamp": before_now(seconds=185).isoformat(),
  1187. "fingerprint": ["group-2"],
  1188. },
  1189. project_id=self.project.id,
  1190. ).group
  1191. group2 = self.store_event(
  1192. data={
  1193. "timestamp": before_now(seconds=190).isoformat(),
  1194. "fingerprint": ["group-3"],
  1195. },
  1196. project_id=self.project.id,
  1197. ).group
  1198. assigned_group = self.store_event(
  1199. data={
  1200. "timestamp": before_now(seconds=195).isoformat(),
  1201. "fingerprint": ["group-4"],
  1202. },
  1203. project_id=self.project.id,
  1204. ).group
  1205. assigned_to_other_group = self.store_event(
  1206. data={
  1207. "timestamp": before_now(seconds=195).isoformat(),
  1208. "fingerprint": ["group-5"],
  1209. },
  1210. project_id=self.project.id,
  1211. ).group
  1212. my_team_group = self.store_event(
  1213. data={
  1214. "fingerprint": ["put-me-in-group-my-teams"],
  1215. "event_id": "f" * 32,
  1216. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  1217. "message": "baz",
  1218. "environment": "staging",
  1219. "tags": {
  1220. "server": "example.com",
  1221. "url": "http://example.com",
  1222. "sentry:user": "event2@example.com",
  1223. },
  1224. "level": "error",
  1225. },
  1226. project_id=self.project.id,
  1227. ).group
  1228. self.run_test_query(
  1229. "assigned_or_suggested:me",
  1230. [],
  1231. [group, group1, group2, assigned_group, assigned_to_other_group, my_team_group],
  1232. user=self.user,
  1233. )
  1234. self.run_test_query(
  1235. "assigned_or_suggested:my_teams",
  1236. [],
  1237. [group, group1, group2, assigned_group, assigned_to_other_group, my_team_group],
  1238. user=self.user,
  1239. )
  1240. GroupOwner.objects.create(
  1241. group=assigned_to_other_group,
  1242. project=self.project,
  1243. organization=self.organization,
  1244. type=0,
  1245. team_id=None,
  1246. user_id=self.user.id,
  1247. )
  1248. GroupOwner.objects.create(
  1249. group=group,
  1250. project=self.project,
  1251. organization=self.organization,
  1252. type=0,
  1253. team_id=None,
  1254. user_id=self.user.id,
  1255. )
  1256. GroupAssignee.objects.create(
  1257. user_id=None, team_id=self.team.id, group=my_team_group, project=my_team_group.project
  1258. )
  1259. self.run_test_query(
  1260. "assigned_or_suggested:me",
  1261. [group, assigned_to_other_group],
  1262. [group1, group2, assigned_group, my_team_group],
  1263. user=self.user,
  1264. )
  1265. self.run_test_query(
  1266. "assigned_or_suggested:my_teams",
  1267. [my_team_group],
  1268. [group, group1, group2, assigned_group, assigned_to_other_group],
  1269. user=self.user,
  1270. )
  1271. # 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)
  1272. other_user = self.create_user("other@user.com", is_superuser=False)
  1273. GroupAssignee.objects.create(
  1274. group=assigned_to_other_group,
  1275. project=self.project,
  1276. user_id=other_user.id,
  1277. )
  1278. self.run_test_query(
  1279. "assigned_or_suggested:me",
  1280. [group],
  1281. [group1, group2, assigned_group, my_team_group, assigned_to_other_group],
  1282. user=self.user,
  1283. )
  1284. self.run_test_query(
  1285. "assigned_or_suggested:my_teams",
  1286. [my_team_group],
  1287. [group, group1, group2, assigned_group, assigned_to_other_group],
  1288. user=self.user,
  1289. )
  1290. self.run_test_query(
  1291. f"assigned_or_suggested:{other_user.email}",
  1292. [assigned_to_other_group],
  1293. [group, group1, group2, assigned_group, my_team_group],
  1294. user=self.user,
  1295. )
  1296. GroupAssignee.objects.create(
  1297. group=assigned_group, project=self.project, user_id=self.user.id
  1298. )
  1299. self.run_test_query(
  1300. f"assigned_or_suggested:{self.user.email}",
  1301. [assigned_group, group],
  1302. [group1, group2, my_team_group, assigned_to_other_group],
  1303. user=self.user,
  1304. )
  1305. GroupOwner.objects.create(
  1306. group=group,
  1307. project=self.project,
  1308. organization=self.organization,
  1309. type=0,
  1310. team_id=self.team.id,
  1311. user_id=None,
  1312. )
  1313. self.run_test_query(
  1314. f"assigned_or_suggested:#{self.team.slug}",
  1315. [group, my_team_group],
  1316. [group1, group2, assigned_group, assigned_to_other_group],
  1317. user=self.user,
  1318. )
  1319. def test_assigned_or_suggested_my_teams_in_syntax(self):
  1320. Group.objects.all().delete()
  1321. group = self.store_event(
  1322. data={
  1323. "timestamp": before_now(seconds=180).isoformat(),
  1324. "fingerprint": ["group-1"],
  1325. },
  1326. project_id=self.project.id,
  1327. ).group
  1328. group1 = self.store_event(
  1329. data={
  1330. "timestamp": before_now(seconds=185).isoformat(),
  1331. "fingerprint": ["group-2"],
  1332. },
  1333. project_id=self.project.id,
  1334. ).group
  1335. group2 = self.store_event(
  1336. data={
  1337. "timestamp": before_now(seconds=190).isoformat(),
  1338. "fingerprint": ["group-3"],
  1339. },
  1340. project_id=self.project.id,
  1341. ).group
  1342. assigned_group = self.store_event(
  1343. data={
  1344. "timestamp": before_now(seconds=195).isoformat(),
  1345. "fingerprint": ["group-4"],
  1346. },
  1347. project_id=self.project.id,
  1348. ).group
  1349. assigned_to_other_group = self.store_event(
  1350. data={
  1351. "timestamp": before_now(seconds=195).isoformat(),
  1352. "fingerprint": ["group-5"],
  1353. },
  1354. project_id=self.project.id,
  1355. ).group
  1356. my_team_group = self.store_event(
  1357. data={
  1358. "fingerprint": ["put-me-in-group-my-teams"],
  1359. "event_id": "f" * 32,
  1360. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  1361. "message": "baz",
  1362. "environment": "staging",
  1363. "tags": {
  1364. "server": "example.com",
  1365. "url": "http://example.com",
  1366. "sentry:user": "event2@example.com",
  1367. },
  1368. "level": "error",
  1369. },
  1370. project_id=self.project.id,
  1371. ).group
  1372. self.run_test_query(
  1373. "assigned_or_suggested:[me]",
  1374. [],
  1375. [group, group1, group2, assigned_group, assigned_to_other_group, my_team_group],
  1376. user=self.user,
  1377. )
  1378. self.run_test_query(
  1379. "assigned_or_suggested:[my_teams]",
  1380. [],
  1381. [group, group1, group2, assigned_group, assigned_to_other_group, my_team_group],
  1382. user=self.user,
  1383. )
  1384. self.run_test_query(
  1385. "assigned_or_suggested:[me, my_teams]",
  1386. [],
  1387. [group, group1, group2, assigned_group, assigned_to_other_group, my_team_group],
  1388. user=self.user,
  1389. )
  1390. GroupOwner.objects.create(
  1391. group=assigned_to_other_group,
  1392. project=self.project,
  1393. organization=self.organization,
  1394. type=0,
  1395. team_id=None,
  1396. user_id=self.user.id,
  1397. )
  1398. GroupOwner.objects.create(
  1399. group=group,
  1400. project=self.project,
  1401. organization=self.organization,
  1402. type=0,
  1403. team_id=None,
  1404. user_id=self.user.id,
  1405. )
  1406. GroupAssignee.objects.create(
  1407. user_id=None, team_id=self.team.id, group=my_team_group, project=my_team_group.project
  1408. )
  1409. self.run_test_query(
  1410. "assigned_or_suggested:[me]",
  1411. [group, assigned_to_other_group],
  1412. [group1, group2, assigned_group, my_team_group],
  1413. user=self.user,
  1414. )
  1415. self.run_test_query(
  1416. "assigned_or_suggested:[my_teams]",
  1417. [my_team_group],
  1418. [group, group1, group2, assigned_group, assigned_to_other_group],
  1419. user=self.user,
  1420. )
  1421. self.run_test_query(
  1422. "assigned_or_suggested:[me, my_teams]",
  1423. [group, assigned_to_other_group, my_team_group],
  1424. [group1, group2, assigned_group],
  1425. user=self.user,
  1426. )
  1427. # 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)
  1428. other_user = self.create_user("other@user.com", is_superuser=False)
  1429. GroupAssignee.objects.create(
  1430. group=assigned_to_other_group,
  1431. project=self.project,
  1432. user_id=other_user.id,
  1433. )
  1434. self.run_test_query(
  1435. "assigned_or_suggested:[me]",
  1436. [group],
  1437. [group1, group2, assigned_group, my_team_group, assigned_to_other_group],
  1438. user=self.user,
  1439. )
  1440. self.run_test_query(
  1441. "assigned_or_suggested:[my_teams]",
  1442. [my_team_group],
  1443. [group, group1, group2, assigned_group, assigned_to_other_group],
  1444. user=self.user,
  1445. )
  1446. self.run_test_query(
  1447. "assigned_or_suggested:[me, my_teams]",
  1448. [group, my_team_group],
  1449. [group1, group2, assigned_group, assigned_to_other_group],
  1450. user=self.user,
  1451. )
  1452. self.run_test_query(
  1453. f"assigned_or_suggested:[{other_user.email}]",
  1454. [assigned_to_other_group],
  1455. [group, group1, group2, assigned_group, my_team_group],
  1456. user=self.user,
  1457. )
  1458. GroupAssignee.objects.create(
  1459. group=assigned_group, project=self.project, user_id=self.user.id
  1460. )
  1461. self.run_test_query(
  1462. f"assigned_or_suggested:[{self.user.email}]",
  1463. [assigned_group, group],
  1464. [group1, group2, my_team_group, assigned_to_other_group],
  1465. user=self.user,
  1466. )
  1467. GroupOwner.objects.create(
  1468. group=group,
  1469. project=self.project,
  1470. organization=self.organization,
  1471. type=0,
  1472. team_id=self.team.id,
  1473. user_id=None,
  1474. )
  1475. self.run_test_query(
  1476. f"assigned_or_suggested:[#{self.team.slug}]",
  1477. [group, my_team_group],
  1478. [group1, group2, assigned_group, assigned_to_other_group],
  1479. user=self.user,
  1480. )
  1481. self.run_test_query(
  1482. "assigned_or_suggested:[me, none]",
  1483. [group, group1, group2, assigned_group],
  1484. [my_team_group, assigned_to_other_group],
  1485. user=self.user,
  1486. )
  1487. self.run_test_query(
  1488. "assigned_or_suggested:[my_teams, none]",
  1489. [group, group1, group2, my_team_group],
  1490. [assigned_to_other_group, assigned_group],
  1491. user=self.user,
  1492. )
  1493. self.run_test_query(
  1494. "assigned_or_suggested:[me, my_teams, none]",
  1495. [group, group1, group2, my_team_group, assigned_group],
  1496. [assigned_to_other_group],
  1497. user=self.user,
  1498. )
  1499. not_me = self.create_user(email="notme@sentry.io")
  1500. GroupOwner.objects.create(
  1501. group=group2,
  1502. project=self.project,
  1503. organization=self.organization,
  1504. type=0,
  1505. team_id=None,
  1506. user_id=not_me.id,
  1507. )
  1508. self.run_test_query(
  1509. "assigned_or_suggested:[me, none]",
  1510. [group, group1, assigned_group],
  1511. [group2, my_team_group, assigned_to_other_group],
  1512. user=self.user,
  1513. )
  1514. self.run_test_query(
  1515. "assigned_or_suggested:[my_teams, none]",
  1516. [group, group1, my_team_group],
  1517. [group2, assigned_group, assigned_to_other_group],
  1518. user=self.user,
  1519. )
  1520. self.run_test_query(
  1521. "assigned_or_suggested:[me, my_teams, none]",
  1522. [group, group1, my_team_group, assigned_group],
  1523. [group2, assigned_to_other_group],
  1524. user=self.user,
  1525. )
  1526. GroupOwner.objects.filter(group=group, user_id=self.user.id).delete()
  1527. self.run_test_query(
  1528. f"assigned_or_suggested:[me, none, #{self.team.slug}]",
  1529. [group, group1, assigned_group, my_team_group],
  1530. [assigned_to_other_group, group2],
  1531. user=self.user,
  1532. )
  1533. self.run_test_query(
  1534. f"assigned_or_suggested:[my_teams, none, #{self.team.slug}]",
  1535. [group, group1, my_team_group],
  1536. [assigned_to_other_group, group2, assigned_group],
  1537. user=self.user,
  1538. )
  1539. self.run_test_query(
  1540. f"assigned_or_suggested:[me, my_teams, none, #{self.team.slug}]",
  1541. [group, group1, my_team_group, assigned_group],
  1542. [assigned_to_other_group, group2],
  1543. user=self.user,
  1544. )
  1545. self.run_test_query(
  1546. f"assigned_or_suggested:[me, none, #{self.team.slug}, {not_me.email}]",
  1547. [group, group1, group2, assigned_group, my_team_group],
  1548. [assigned_to_other_group],
  1549. user=self.user,
  1550. )
  1551. self.run_test_query(
  1552. f"assigned_or_suggested:[my_teams, none, #{self.team.slug}, {not_me.email}]",
  1553. [group, group1, group2, my_team_group],
  1554. [assigned_to_other_group, assigned_group],
  1555. user=self.user,
  1556. )
  1557. self.run_test_query(
  1558. f"assigned_or_suggested:[me, my_teams, none, #{self.team.slug}, {not_me.email}]",
  1559. [group, group1, group2, my_team_group, assigned_group],
  1560. [assigned_to_other_group],
  1561. user=self.user,
  1562. )
  1563. def test_assigned_to_with_environment(self):
  1564. results = self.make_query(
  1565. environments=[self.environments["staging"]],
  1566. search_filter_query="assigned:%s" % self.user.username,
  1567. )
  1568. assert set(results) == {self.group2}
  1569. results = self.make_query(
  1570. environments=[self.environments["production"]],
  1571. search_filter_query="assigned:%s" % self.user.username,
  1572. )
  1573. assert set(results) == set()
  1574. def test_subscribed_by(self):
  1575. results = self.make_query(
  1576. [self.group1.project], search_filter_query="subscribed:%s" % self.user.username
  1577. )
  1578. assert set(results) == {self.group1}
  1579. def test_subscribed_by_in_syntax(self):
  1580. self.run_test_query(f"subscribed:[{self.user.username}]", [self.group1], [self.group2])
  1581. user_2 = self.create_user()
  1582. GroupSubscription.objects.create(
  1583. user_id=user_2.id, group=self.group2, project=self.project, is_active=True
  1584. )
  1585. self.run_test_query(
  1586. f"subscribed:[{self.user.username}, {user_2.username}]", [self.group1, self.group2], []
  1587. )
  1588. def test_subscribed_by_with_environment(self):
  1589. results = self.make_query(
  1590. [self.group1.project],
  1591. environments=[self.environments["production"]],
  1592. search_filter_query="subscribed:%s" % self.user.username,
  1593. )
  1594. assert set(results) == {self.group1}
  1595. results = self.make_query(
  1596. [self.group1.project],
  1597. environments=[self.environments["staging"]],
  1598. search_filter_query="subscribed:%s" % self.user.username,
  1599. )
  1600. assert set(results) == set()
  1601. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1602. def test_snuba_not_called_optimization(self, query_mock):
  1603. assert self.make_query(search_filter_query="status:unresolved").results == [self.group1]
  1604. assert not query_mock.called
  1605. assert (
  1606. self.make_query(
  1607. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1608. sort_by="date",
  1609. ).results
  1610. == []
  1611. )
  1612. assert query_mock.called
  1613. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1614. def test_reduce_bulk_results_none_total(self, bulk_raw_query_mock):
  1615. bulk_raw_query_mock.return_value = [
  1616. {"data": [], "totals": {"total": None}},
  1617. {"data": [], "totals": {"total": None}},
  1618. ]
  1619. assert (
  1620. self.make_query(
  1621. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1622. sort_by="date",
  1623. ).results
  1624. == []
  1625. )
  1626. assert bulk_raw_query_mock.called
  1627. @mock.patch("sentry.search.snuba.executors.bulk_raw_query")
  1628. def test_reduce_bulk_results_none_data(self, bulk_raw_query_mock):
  1629. bulk_raw_query_mock.return_value = [
  1630. {"data": None, "totals": {"total": 0}},
  1631. {"data": None, "totals": {"total": 0}},
  1632. ]
  1633. assert (
  1634. self.make_query(
  1635. search_filter_query="last_seen:>%s" % date_to_query_format(timezone.now()),
  1636. sort_by="date",
  1637. ).results
  1638. == []
  1639. )
  1640. assert bulk_raw_query_mock.called
  1641. def test_pre_and_post_filtering(self):
  1642. prev_max_pre = options.get("snuba.search.max-pre-snuba-candidates")
  1643. options.set("snuba.search.max-pre-snuba-candidates", 1)
  1644. try:
  1645. # normal queries work as expected
  1646. results = self.make_query(search_filter_query="foo")
  1647. assert set(results) == {self.group1}
  1648. results = self.make_query(search_filter_query="bar")
  1649. assert set(results) == {self.group2}
  1650. # no candidate matches in Sentry, immediately return empty paginator
  1651. results = self.make_query(search_filter_query="NO MATCHES IN SENTRY")
  1652. assert set(results) == set()
  1653. # too many candidates, skip pre-filter, requires >1 postfilter queries
  1654. results = self.make_query()
  1655. assert set(results) == {self.group1, self.group2}
  1656. finally:
  1657. options.set("snuba.search.max-pre-snuba-candidates", prev_max_pre)
  1658. def test_optimizer_enabled(self):
  1659. prev_optimizer_enabled = options.get("snuba.search.pre-snuba-candidates-optimizer")
  1660. options.set("snuba.search.pre-snuba-candidates-optimizer", True)
  1661. try:
  1662. results = self.make_query(
  1663. search_filter_query="server:example.com",
  1664. environments=[self.environments["production"]],
  1665. )
  1666. assert set(results) == {self.group1}
  1667. finally:
  1668. options.set("snuba.search.pre-snuba-candidates-optimizer", prev_optimizer_enabled)
  1669. def test_search_out_of_range(self):
  1670. the_date = datetime(2000, 1, 1, 0, 0, 0, tzinfo=UTC)
  1671. results = self.make_query(
  1672. search_filter_query=f"event.timestamp:>{the_date} event.timestamp:<{the_date}",
  1673. date_from=the_date,
  1674. date_to=the_date,
  1675. )
  1676. assert set(results) == set()
  1677. def test_regressed_in_release(self):
  1678. # expect no groups within the results since there are no releases
  1679. results = self.make_query(search_filter_query="regressed_in_release:fake")
  1680. assert set(results) == set()
  1681. # expect no groups even though there is a release; since no group regressed in this release
  1682. release_1 = self.create_release()
  1683. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1684. assert set(results) == set()
  1685. # Create a new event so that we get a group in this release
  1686. group = self.store_event(
  1687. data={
  1688. "release": release_1.version,
  1689. },
  1690. project_id=self.project.id,
  1691. ).group
  1692. # # Should still be no group since we didn't regress in this release
  1693. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1694. assert set(results) == set()
  1695. record_group_history(group, GroupHistoryStatus.REGRESSED, release=release_1)
  1696. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1697. assert set(results) == {group}
  1698. # Make sure this works correctly with multiple releases
  1699. release_2 = self.create_release()
  1700. group_2 = self.store_event(
  1701. data={
  1702. "fingerprint": ["put-me-in-group9001"],
  1703. "event_id": "a" * 32,
  1704. "release": release_2.version,
  1705. },
  1706. project_id=self.project.id,
  1707. ).group
  1708. record_group_history(group_2, GroupHistoryStatus.REGRESSED, release=release_2)
  1709. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_1.version)
  1710. assert set(results) == {group}
  1711. results = self.make_query(search_filter_query="regressed_in_release:%s" % release_2.version)
  1712. assert set(results) == {group_2}
  1713. def test_first_release(self):
  1714. # expect no groups within the results since there are no releases
  1715. results = self.make_query(search_filter_query="first_release:%s" % "fake")
  1716. assert set(results) == set()
  1717. # expect no groups even though there is a release; since no group
  1718. # is attached to a release
  1719. release_1 = self.create_release(self.project)
  1720. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1721. assert set(results) == set()
  1722. # Create a new event so that we get a group in this release
  1723. group = self.store_event(
  1724. data={
  1725. "fingerprint": ["put-me-in-group9001"],
  1726. "event_id": "a" * 32,
  1727. "message": "hello",
  1728. "environment": "production",
  1729. "tags": {"server": "example.com"},
  1730. "release": release_1.version,
  1731. "stacktrace": {"frames": [{"module": "group1"}]},
  1732. },
  1733. project_id=self.project.id,
  1734. ).group
  1735. results = self.make_query(search_filter_query="first_release:%s" % release_1.version)
  1736. assert set(results) == {group}
  1737. def test_first_release_in_syntax(self):
  1738. # expect no groups within the results since there are no releases
  1739. self.run_test_query("first_release:[fake, fake2]", [])
  1740. # expect no groups even though there is a release; since no group
  1741. # is attached to a release
  1742. release_1 = self.create_release(self.project)
  1743. release_2 = self.create_release(self.project)
  1744. self.run_test_query(f"first_release:[{release_1.version}, {release_2.version}]", [])
  1745. # Create a new event so that we get a group in release 1
  1746. group = self.store_event(
  1747. data={
  1748. "fingerprint": ["put-me-in-group9001"],
  1749. "event_id": "a" * 32,
  1750. "message": "hello",
  1751. "environment": "production",
  1752. "tags": {"server": "example.com"},
  1753. "release": release_1.version,
  1754. "stacktrace": {"frames": [{"module": "group1"}]},
  1755. },
  1756. project_id=self.project.id,
  1757. ).group
  1758. self.run_test_query(
  1759. f"first_release:[{release_1.version}, {release_2.version}]",
  1760. [group],
  1761. [self.group1, self.group2],
  1762. )
  1763. # Create a new event so that we get a group in release 2
  1764. group_2 = self.store_event(
  1765. data={
  1766. "fingerprint": ["put-me-in-group9002"],
  1767. "event_id": "a" * 32,
  1768. "message": "hello",
  1769. "environment": "production",
  1770. "tags": {"server": "example.com"},
  1771. "release": release_2.version,
  1772. "stacktrace": {"frames": [{"module": "group1"}]},
  1773. },
  1774. project_id=self.project.id,
  1775. ).group
  1776. self.run_test_query(
  1777. f"first_release:[{release_1.version}, {release_2.version}]",
  1778. [group, group_2],
  1779. [self.group1, self.group2],
  1780. )
  1781. def test_first_release_environments(self):
  1782. results = self.make_query(
  1783. environments=[self.environments["production"]],
  1784. search_filter_query="first_release:fake",
  1785. )
  1786. assert set(results) == set()
  1787. release = self.create_release(self.project)
  1788. group_env = GroupEnvironment.get_or_create(
  1789. group_id=self.group1.id, environment_id=self.environments["production"].id
  1790. )[0]
  1791. results = self.make_query(
  1792. environments=[self.environments["production"]],
  1793. search_filter_query=f"first_release:{release.version}",
  1794. )
  1795. assert set(results) == set()
  1796. group_env.first_release = release
  1797. group_env.save()
  1798. results = self.make_query(
  1799. environments=[self.environments["production"]],
  1800. search_filter_query=f"first_release:{release.version}",
  1801. )
  1802. assert set(results) == {self.group1}
  1803. def test_first_release_environments_in_syntax(self):
  1804. self.run_test_query(
  1805. "first_release:[fake, fake2]",
  1806. [],
  1807. [self.group1, self.group2],
  1808. environments=[self.environments["production"]],
  1809. )
  1810. release = self.create_release(self.project)
  1811. group_1_env = GroupEnvironment.objects.get(
  1812. group_id=self.group1.id, environment_id=self.environments["production"].id
  1813. )
  1814. group_1_env.update(first_release=release)
  1815. self.group1.first_release = release
  1816. self.group1.save()
  1817. self.run_test_query(
  1818. f"first_release:[{release.version}, fake2]",
  1819. [self.group1],
  1820. [self.group2],
  1821. environments=[self.environments["production"]],
  1822. )
  1823. group_2_env = GroupEnvironment.objects.get(
  1824. group_id=self.group2.id, environment_id=self.environments["staging"].id
  1825. )
  1826. group_2_env.update(first_release=release)
  1827. self.group2.first_release = release
  1828. self.group2.save()
  1829. self.run_test_query(
  1830. f"first_release:[{release.version}, fake2]",
  1831. [self.group1, self.group2],
  1832. [],
  1833. environments=[self.environments["production"], self.environments["staging"]],
  1834. )
  1835. # Make sure we don't get duplicate groups
  1836. GroupEnvironment.objects.create(
  1837. group_id=self.group1.id,
  1838. environment_id=self.environments["staging"].id,
  1839. first_release=release,
  1840. )
  1841. self.run_test_query(
  1842. f"first_release:[{release.version}, fake2]",
  1843. [self.group1, self.group2],
  1844. [],
  1845. environments=[self.environments["production"], self.environments["staging"]],
  1846. )
  1847. def test_query_enclosed_in_quotes(self):
  1848. results = self.make_query(search_filter_query='"foo"')
  1849. assert set(results) == {self.group1}
  1850. results = self.make_query(search_filter_query='"bar"')
  1851. assert set(results) == {self.group2}
  1852. def test_wildcard(self):
  1853. escaped_event = self.store_event(
  1854. data={
  1855. "fingerprint": ["hello-there"],
  1856. "event_id": "f" * 32,
  1857. "message": "somet[hing]",
  1858. "environment": "production",
  1859. "tags": {"server": "example.net"},
  1860. "timestamp": self.base_datetime.isoformat(),
  1861. "stacktrace": {"frames": [{"module": "group1"}]},
  1862. },
  1863. project_id=self.project.id,
  1864. )
  1865. # Note: Adding in `environment:production` so that we make sure we query
  1866. # in both snuba and postgres
  1867. results = self.make_query(search_filter_query="environment:production so*t")
  1868. assert set(results) == {escaped_event.group}
  1869. # Make sure it's case insensitive
  1870. results = self.make_query(search_filter_query="environment:production SO*t")
  1871. assert set(results) == {escaped_event.group}
  1872. results = self.make_query(search_filter_query="environment:production so*zz")
  1873. assert set(results) == set()
  1874. results = self.make_query(search_filter_query="environment:production [hing]")
  1875. assert set(results) == {escaped_event.group}
  1876. results = self.make_query(search_filter_query="environment:production s*]")
  1877. assert set(results) == {escaped_event.group}
  1878. results = self.make_query(search_filter_query="environment:production server:example.*")
  1879. assert set(results) == {self.group1, escaped_event.group}
  1880. results = self.make_query(search_filter_query="environment:production !server:*net")
  1881. assert set(results) == {self.group1}
  1882. # TODO: Disabling tests that use [] syntax for the moment. Re-enable
  1883. # these if we decide to add back in, or remove if this comment has been
  1884. # here a while.
  1885. # results = self.make_query(
  1886. # search_filter_query='environment:production [s][of][mz]',
  1887. # )
  1888. # assert set(results) == set([escaped_event.group])
  1889. # results = self.make_query(
  1890. # search_filter_query='environment:production [z][of][mz]',
  1891. # )
  1892. # assert set(results) == set()
  1893. def test_null_tags(self):
  1894. tag_event = self.store_event(
  1895. data={
  1896. "fingerprint": ["hello-there"],
  1897. "event_id": "f" * 32,
  1898. "message": "something",
  1899. "environment": "production",
  1900. "tags": {"server": "example.net"},
  1901. "timestamp": self.base_datetime.isoformat(),
  1902. "stacktrace": {"frames": [{"module": "group1"}]},
  1903. },
  1904. project_id=self.project.id,
  1905. )
  1906. no_tag_event = self.store_event(
  1907. data={
  1908. "fingerprint": ["hello-there-2"],
  1909. "event_id": "5" * 32,
  1910. "message": "something",
  1911. "environment": "production",
  1912. "timestamp": self.base_datetime.isoformat(),
  1913. "stacktrace": {"frames": [{"module": "group2"}]},
  1914. },
  1915. project_id=self.project.id,
  1916. )
  1917. results = self.make_query(search_filter_query="environment:production !server:*net")
  1918. assert set(results) == {self.group1, no_tag_event.group}
  1919. results = self.make_query(search_filter_query="environment:production server:*net")
  1920. assert set(results) == {tag_event.group}
  1921. results = self.make_query(search_filter_query="environment:production !server:example.net")
  1922. assert set(results) == {self.group1, no_tag_event.group}
  1923. results = self.make_query(search_filter_query="environment:production server:example.net")
  1924. assert set(results) == {tag_event.group}
  1925. results = self.make_query(search_filter_query="environment:production has:server")
  1926. assert set(results) == {self.group1, tag_event.group}
  1927. results = self.make_query(search_filter_query="environment:production !has:server")
  1928. assert set(results) == {no_tag_event.group}
  1929. def test_null_promoted_tags(self):
  1930. tag_event = self.store_event(
  1931. data={
  1932. "fingerprint": ["hello-there"],
  1933. "event_id": "f" * 32,
  1934. "message": "something",
  1935. "environment": "production",
  1936. "tags": {"logger": "csp"},
  1937. "timestamp": self.base_datetime.isoformat(),
  1938. "stacktrace": {"frames": [{"module": "group1"}]},
  1939. },
  1940. project_id=self.project.id,
  1941. )
  1942. no_tag_event = self.store_event(
  1943. data={
  1944. "fingerprint": ["hello-there-2"],
  1945. "event_id": "5" * 32,
  1946. "message": "something",
  1947. "environment": "production",
  1948. "timestamp": self.base_datetime.isoformat(),
  1949. "stacktrace": {"frames": [{"module": "group2"}]},
  1950. },
  1951. project_id=self.project.id,
  1952. )
  1953. results = self.make_query(search_filter_query="environment:production !logger:*sp")
  1954. assert set(results) == {self.group1, no_tag_event.group}
  1955. results = self.make_query(search_filter_query="environment:production logger:*sp")
  1956. assert set(results) == {tag_event.group}
  1957. results = self.make_query(search_filter_query="environment:production !logger:csp")
  1958. assert set(results) == {self.group1, no_tag_event.group}
  1959. results = self.make_query(search_filter_query="environment:production logger:csp")
  1960. assert set(results) == {tag_event.group}
  1961. results = self.make_query(search_filter_query="environment:production has:logger")
  1962. assert set(results) == {tag_event.group}
  1963. results = self.make_query(search_filter_query="environment:production !has:logger")
  1964. assert set(results) == {self.group1, no_tag_event.group}
  1965. def test_sort_multi_project(self):
  1966. self.set_up_multi_project()
  1967. results = self.make_query([self.project, self.project2], sort_by="date")
  1968. assert list(results) == [self.group1, self.group_p2, self.group2]
  1969. results = self.make_query([self.project, self.project2], sort_by="new")
  1970. assert list(results) == [self.group2, self.group_p2, self.group1]
  1971. results = self.make_query([self.project, self.project2], sort_by="freq")
  1972. assert list(results) == [self.group1, self.group_p2, self.group2]
  1973. results = self.make_query([self.project, self.project2], sort_by="trends")
  1974. assert list(results) == [
  1975. self.group_p2,
  1976. self.group2,
  1977. self.group1,
  1978. ]
  1979. results = self.make_query([self.project, self.project2], sort_by="user")
  1980. assert list(results) == [self.group1, self.group2, self.group_p2]
  1981. def test_in_syntax_is_invalid(self):
  1982. with pytest.raises(InvalidSearchQuery, match='"in" syntax invalid for "is" search'):
  1983. self.make_query(search_filter_query="is:[unresolved, resolved]")
  1984. def test_first_release_any_or_no_environments(self):
  1985. # test scenarios for tickets:
  1986. # SEN-571
  1987. # ISSUE-432
  1988. # given the following setup:
  1989. #
  1990. # groups table:
  1991. # group first_release
  1992. # A 1
  1993. # B 1
  1994. # C 2
  1995. #
  1996. # groupenvironments table:
  1997. # group environment first_release
  1998. # A staging 1
  1999. # A production 2
  2000. #
  2001. # when querying by first release, the appropriate set of groups should be displayed:
  2002. #
  2003. # first_release: 1
  2004. # env=[]: A, B
  2005. # env=[production, staging]: A
  2006. # env=[staging]: A
  2007. # env=[production]: nothing
  2008. #
  2009. # first_release: 2
  2010. # env=[]: C
  2011. # env=[production, staging]: A
  2012. # env=[staging]: nothing
  2013. # env=[production]: A
  2014. # create an issue/group whose events that occur in 2 distinct environments
  2015. group_a_event_1 = self.store_event(
  2016. data={
  2017. "fingerprint": ["group_a"],
  2018. "event_id": "aaa" + ("1" * 29),
  2019. "environment": "example_staging",
  2020. "release": "release_1",
  2021. },
  2022. project_id=self.project.id,
  2023. )
  2024. group_a_event_2 = self.store_event(
  2025. data={
  2026. "fingerprint": ["group_a"],
  2027. "event_id": "aaa" + ("2" * 29),
  2028. "environment": "example_production",
  2029. "release": "release_2",
  2030. },
  2031. project_id=self.project.id,
  2032. )
  2033. group_a = group_a_event_1.group
  2034. # get the environments for group_a
  2035. prod_env = group_a_event_2.get_environment()
  2036. staging_env = group_a_event_1.get_environment()
  2037. # create an issue/group whose event that occur in no environments
  2038. # but will be tied to release release_1
  2039. group_b_event_1 = self.store_event(
  2040. data={
  2041. "fingerprint": ["group_b"],
  2042. "event_id": "bbb" + ("1" * 29),
  2043. "release": "release_1",
  2044. },
  2045. project_id=self.project.id,
  2046. )
  2047. assert group_b_event_1.get_environment().name == "" # has no environment
  2048. group_b = group_b_event_1.group
  2049. # create an issue/group whose event that occur in no environments
  2050. # but will be tied to release release_2
  2051. group_c_event_1 = self.store_event(
  2052. data={
  2053. "fingerprint": ["group_c"],
  2054. "event_id": "ccc" + ("1" * 29),
  2055. "release": "release_2",
  2056. },
  2057. project_id=self.project.id,
  2058. )
  2059. assert group_c_event_1.get_environment().name == "" # has no environment
  2060. group_c = group_c_event_1.group
  2061. # query by release release_1
  2062. results = self.make_query(search_filter_query="first_release:release_1")
  2063. assert set(results) == {group_a, group_b}
  2064. results = self.make_query(
  2065. environments=[staging_env, prod_env],
  2066. search_filter_query="first_release:release_1",
  2067. )
  2068. assert set(results) == {group_a}
  2069. results = self.make_query(
  2070. environments=[staging_env], search_filter_query="first_release:release_1"
  2071. )
  2072. assert set(results) == {group_a}
  2073. results = self.make_query(
  2074. environments=[prod_env], search_filter_query="first_release:release_1"
  2075. )
  2076. assert set(results) == set()
  2077. # query by release release_2
  2078. results = self.make_query(search_filter_query="first_release:release_2")
  2079. assert set(results) == {group_c}
  2080. results = self.make_query(
  2081. environments=[staging_env, prod_env],
  2082. search_filter_query="first_release:release_2",
  2083. )
  2084. assert set(results) == {group_a}
  2085. results = self.make_query(
  2086. environments=[staging_env], search_filter_query="first_release:release_2"
  2087. )
  2088. assert set(results) == set()
  2089. results = self.make_query(
  2090. environments=[prod_env], search_filter_query="first_release:release_2"
  2091. )
  2092. assert set(results) == {group_a}
  2093. @pytest.mark.skip(reason="test runs far too slowly, causing timeouts atm.")
  2094. def test_all_fields_do_not_error(self):
  2095. # Just a sanity check to make sure that all fields can be successfully
  2096. # searched on without returning type errors and other schema related
  2097. # issues.
  2098. def test_query(query):
  2099. self.make_query(search_filter_query=query)
  2100. for key in SENTRY_SNUBA_MAP:
  2101. if key in ["project.id", "issue.id", "performance.issue_ids", "status"]:
  2102. continue
  2103. test_query("has:%s" % key)
  2104. test_query("!has:%s" % key)
  2105. if key == "error.handled":
  2106. val: Any = 1
  2107. elif key in issue_search_config.numeric_keys:
  2108. val = "123"
  2109. elif key in issue_search_config.date_keys:
  2110. val = self.base_datetime.isoformat()
  2111. elif key in issue_search_config.boolean_keys:
  2112. val = "true"
  2113. elif key in {"trace.span", "trace.parent_span"}:
  2114. val = "abcdef1234abcdef"
  2115. test_query(f"!{key}:{val}")
  2116. else:
  2117. val = "abadcafedeadbeefdeaffeedabadfeed"
  2118. test_query(f"!{key}:{val}")
  2119. test_query(f"{key}:{val}")
  2120. def test_message_negation(self):
  2121. self.store_event(
  2122. data={
  2123. "fingerprint": ["put-me-in-group1"],
  2124. "event_id": "2" * 32,
  2125. "message": "something",
  2126. "timestamp": self.base_datetime.isoformat(),
  2127. },
  2128. project_id=self.project.id,
  2129. )
  2130. results = self.make_query(search_filter_query="!message:else")
  2131. results2 = self.make_query(search_filter_query="!message:else")
  2132. assert list(results) == list(results2)
  2133. def test_error_main_thread_true(self):
  2134. myProject = self.create_project(
  2135. name="Foo", slug="foo", teams=[self.team], fire_project_created=True
  2136. )
  2137. event = self.store_event(
  2138. data={
  2139. "event_id": "1" * 32,
  2140. "message": "something",
  2141. "timestamp": self.base_datetime.isoformat(),
  2142. "exception": {
  2143. "values": [
  2144. {
  2145. "type": "SyntaxError",
  2146. "value": "hello world",
  2147. "thread_id": 1,
  2148. },
  2149. ],
  2150. },
  2151. "threads": {
  2152. "values": [
  2153. {
  2154. "id": 1,
  2155. "main": True,
  2156. },
  2157. ],
  2158. },
  2159. },
  2160. project_id=myProject.id,
  2161. )
  2162. myGroup = event.groups[0]
  2163. results = self.make_query(
  2164. projects=[myProject],
  2165. search_filter_query="error.main_thread:1",
  2166. sort_by="date",
  2167. )
  2168. assert list(results) == [myGroup]
  2169. def test_error_main_thread_false(self):
  2170. myProject = self.create_project(
  2171. name="Foo2", slug="foo2", teams=[self.team], fire_project_created=True
  2172. )
  2173. event = self.store_event(
  2174. data={
  2175. "event_id": "2" * 32,
  2176. "message": "something",
  2177. "timestamp": self.base_datetime.isoformat(),
  2178. "exception": {
  2179. "values": [
  2180. {
  2181. "type": "SyntaxError",
  2182. "value": "hello world",
  2183. "thread_id": 1,
  2184. },
  2185. ],
  2186. },
  2187. "threads": {
  2188. "values": [
  2189. {
  2190. "id": 1,
  2191. "main": False,
  2192. },
  2193. ],
  2194. },
  2195. },
  2196. project_id=myProject.id,
  2197. )
  2198. myGroup = event.groups[0]
  2199. results = self.make_query(
  2200. projects=[myProject],
  2201. search_filter_query="error.main_thread:0",
  2202. sort_by="date",
  2203. )
  2204. assert list(results) == [myGroup]
  2205. def test_error_main_thread_no_results(self):
  2206. myProject = self.create_project(
  2207. name="Foo3", slug="foo3", teams=[self.team], fire_project_created=True
  2208. )
  2209. self.store_event(
  2210. data={
  2211. "event_id": "3" * 32,
  2212. "message": "something",
  2213. "timestamp": self.base_datetime.isoformat(),
  2214. "exception": {
  2215. "values": [
  2216. {
  2217. "type": "SyntaxError",
  2218. "value": "hello world",
  2219. "thread_id": 1,
  2220. },
  2221. ],
  2222. },
  2223. "threads": {
  2224. "values": [
  2225. {
  2226. "id": 1,
  2227. },
  2228. ],
  2229. },
  2230. },
  2231. project_id=myProject.id,
  2232. )
  2233. results = self.make_query(
  2234. projects=[myProject],
  2235. search_filter_query="error.main_thread:1",
  2236. sort_by="date",
  2237. )
  2238. assert len(results) == 0
  2239. class EventsSnubaSearchTest(TestCase, EventsSnubaSearchTestCases):
  2240. pass
  2241. @apply_feature_flag_on_cls("organizations:issue-search-group-attributes-side-query")
  2242. class EventsJoinedGroupAttributesSnubaSearchTest(TransactionTestCase, EventsSnubaSearchTestCases):
  2243. def setUp(self):
  2244. def post_insert(snapshot: GroupAttributesSnapshot) -> None:
  2245. from sentry.utils import snuba
  2246. resp = snuba._snuba_pool.urlopen(
  2247. "POST",
  2248. "/tests/entities/group_attributes/insert",
  2249. body=json.dumps([snapshot]),
  2250. headers={},
  2251. )
  2252. assert resp.status == 200
  2253. with mock.patch("sentry.issues.attributes.produce_snapshot_to_kafka", post_insert):
  2254. super().setUp()
  2255. @mock.patch("sentry.utils.metrics.timer")
  2256. @mock.patch("sentry.utils.metrics.incr")
  2257. def test_is_unresolved_query_logs_metric(self, metrics_incr, metrics_timer):
  2258. results = self.make_query(search_filter_query="is:unresolved")
  2259. assert set(results) == {self.group1}
  2260. # introduce a slight delay so the async future has time to run and log the metric
  2261. time.sleep(1)
  2262. metrics_incr_called = False
  2263. for call in metrics_incr.call_args_list:
  2264. args, kwargs = call
  2265. if "snuba.search.group_attributes_joined.events_compared" in set(args):
  2266. metrics_incr_called = True
  2267. assert metrics_incr_called
  2268. metrics_timer_called = False
  2269. for call in metrics_timer.call_args_list:
  2270. args, kwargs = call
  2271. if "snuba.search.group_attributes_joined.duration" in set(args):
  2272. metrics_timer_called = True
  2273. assert metrics_timer_called
  2274. def test_issue_priority(self):
  2275. results = self.make_query(search_filter_query="issue.priority:high")
  2276. assert set(results) == {self.group1, self.group2}
  2277. event_3 = self.store_event(
  2278. data={
  2279. "fingerprint": ["put-me-in-group3"],
  2280. "event_id": "c" * 32,
  2281. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  2282. },
  2283. project_id=self.project.id,
  2284. )
  2285. group_3 = event_3.group
  2286. group_3.update(priority=PriorityLevel.LOW)
  2287. results = self.make_query(search_filter_query="issue.priority:low")
  2288. assert set(results) == {group_3}
  2289. results = self.make_query(search_filter_query="issue.priority:[high, low]")
  2290. assert set(results) == {self.group1, self.group2, group_3}
  2291. with pytest.raises(InvalidSearchQuery):
  2292. self.make_query(search_filter_query="issue.category:wrong")
  2293. class EventsTrendsTest(TestCase, SharedSnubaMixin, OccurrenceTestMixin):
  2294. @property
  2295. def backend(self):
  2296. return EventsDatasetSnubaSearchBackend()
  2297. def test_trends_sort_old_and_new_events(self):
  2298. """Test that an issue with only one old event is ranked lower than an issue with only one new event"""
  2299. new_project = self.create_project(organization=self.project.organization)
  2300. base_datetime = before_now(days=3)
  2301. recent_event = self.store_event(
  2302. data={
  2303. "fingerprint": ["put-me-in-recent-group"],
  2304. "event_id": "c" * 32,
  2305. "message": "group1",
  2306. "environment": "production",
  2307. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  2308. "timestamp": base_datetime.isoformat(),
  2309. "stacktrace": {"frames": [{"module": "group1"}]},
  2310. },
  2311. project_id=new_project.id,
  2312. )
  2313. old_event = self.store_event(
  2314. data={
  2315. "fingerprint": ["put-me-in-old-group"],
  2316. "event_id": "a" * 32,
  2317. "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.",
  2318. "environment": "production",
  2319. "tags": {"server": "example.com", "sentry:user": "old_event@example.com"},
  2320. "timestamp": (base_datetime - timedelta(days=20)).isoformat(),
  2321. "stacktrace": {"frames": [{"module": "group1"}]},
  2322. },
  2323. project_id=new_project.id,
  2324. )
  2325. # datetime(2017, 9, 6, 0, 0)
  2326. old_event.data["timestamp"] = 1504656000.0
  2327. weights: TrendsSortWeights = {
  2328. "log_level": 0,
  2329. "has_stacktrace": 0,
  2330. "relative_volume": 1,
  2331. "event_halflife_hours": 4,
  2332. "issue_halflife_hours": 24 * 7,
  2333. "v2": False,
  2334. "norm": False,
  2335. }
  2336. results = self.make_query(
  2337. sort_by="trends",
  2338. projects=[new_project],
  2339. aggregate_kwargs=weights,
  2340. )
  2341. recent_group = Group.objects.get(id=recent_event.group.id)
  2342. old_group = Group.objects.get(id=old_event.group.id)
  2343. assert list(results) == [recent_group, old_group]
  2344. def test_trends_sort_v2(self):
  2345. """Test that the v2 formula works."""
  2346. new_project = self.create_project(organization=self.project.organization)
  2347. base_datetime = before_now(days=3)
  2348. recent_event = self.store_event(
  2349. data={
  2350. "fingerprint": ["put-me-in-recent-group"],
  2351. "event_id": "c" * 32,
  2352. "message": "group1",
  2353. "environment": "production",
  2354. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  2355. "timestamp": base_datetime.isoformat(),
  2356. "stacktrace": {"frames": [{"module": "group1"}]},
  2357. },
  2358. project_id=new_project.id,
  2359. )
  2360. old_event = self.store_event(
  2361. data={
  2362. "fingerprint": ["put-me-in-old-group"],
  2363. "event_id": "a" * 32,
  2364. "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.",
  2365. "environment": "production",
  2366. "tags": {"server": "example.com", "sentry:user": "old_event@example.com"},
  2367. "timestamp": (base_datetime - timedelta(days=20)).isoformat(),
  2368. "stacktrace": {"frames": [{"module": "group1"}]},
  2369. },
  2370. project_id=new_project.id,
  2371. )
  2372. # datetime(2017, 9, 6, 0, 0)
  2373. old_event.data["timestamp"] = 1504656000.0
  2374. weights: TrendsSortWeights = {
  2375. "log_level": 0,
  2376. "has_stacktrace": 0,
  2377. "relative_volume": 1,
  2378. "event_halflife_hours": 4,
  2379. "issue_halflife_hours": 24 * 7,
  2380. "v2": True,
  2381. "norm": False,
  2382. }
  2383. results = self.make_query(
  2384. sort_by="trends",
  2385. projects=[new_project],
  2386. aggregate_kwargs=weights,
  2387. )
  2388. recent_group = Group.objects.get(id=recent_event.group.id)
  2389. old_group = Group.objects.get(id=old_event.group.id)
  2390. assert list(results) == [recent_group, old_group]
  2391. def test_trends_log_level_results(self):
  2392. """Test that the scoring results change when we pass in different log level weights"""
  2393. base_datetime = before_now(hours=1)
  2394. event1 = self.store_event(
  2395. data={
  2396. "fingerprint": ["put-me-in-group1"],
  2397. "event_id": "c" * 32,
  2398. "timestamp": (base_datetime - timedelta(hours=1)).isoformat(),
  2399. "message": "foo",
  2400. "stacktrace": {"frames": [{"module": "group1"}]},
  2401. "environment": "staging",
  2402. "level": "fatal",
  2403. },
  2404. project_id=self.project.id,
  2405. )
  2406. event2 = self.store_event(
  2407. data={
  2408. "fingerprint": ["put-me-in-group2"],
  2409. "event_id": "d" * 32,
  2410. "timestamp": base_datetime.isoformat(),
  2411. "message": "bar",
  2412. "stacktrace": {"frames": [{"module": "group2"}]},
  2413. "environment": "staging",
  2414. "level": "error",
  2415. },
  2416. project_id=self.project.id,
  2417. )
  2418. group1 = Group.objects.get(id=event1.group.id)
  2419. group2 = Group.objects.get(id=event2.group.id)
  2420. agg_kwargs = {
  2421. "trends": {
  2422. "log_level": 0,
  2423. "has_stacktrace": 0,
  2424. "relative_volume": 1,
  2425. "event_halflife_hours": 4,
  2426. "issue_halflife_hours": 24 * 7,
  2427. "v2": False,
  2428. "norm": False,
  2429. }
  2430. }
  2431. query_executor = self.backend._get_query_executor()
  2432. results_zero_log_level = query_executor.snuba_search(
  2433. start=None,
  2434. end=None,
  2435. project_ids=[self.project.id],
  2436. environment_ids=[],
  2437. sort_field="trends",
  2438. organization=self.organization,
  2439. group_ids=[group1.id, group2.id],
  2440. limit=150,
  2441. aggregate_kwargs=agg_kwargs,
  2442. )[0]
  2443. group1_score_before = results_zero_log_level[0][1]
  2444. group2_score_before = results_zero_log_level[1][1]
  2445. # initially group 2's score is higher since it has a more recent event
  2446. assert group2_score_before > group1_score_before
  2447. agg_kwargs["trends"].update({"log_level": 5})
  2448. results2 = query_executor.snuba_search(
  2449. start=None,
  2450. end=None,
  2451. project_ids=[self.project.id],
  2452. environment_ids=[],
  2453. sort_field="trends",
  2454. organization=self.organization,
  2455. group_ids=[group1.id, group2.id],
  2456. limit=150,
  2457. aggregate_kwargs=agg_kwargs,
  2458. )[0]
  2459. group1_score_after = results2[0][1]
  2460. group2_score_after = results2[1][1]
  2461. # ensure fatal has a higher score than error
  2462. assert group1_score_after > group2_score_after
  2463. def test_trends_has_stacktrace_results(self):
  2464. """Test that the scoring results change when we pass in different has_stacktrace weights"""
  2465. base_datetime = before_now(hours=1)
  2466. agg_kwargs = {
  2467. "trends": {
  2468. "log_level": 0,
  2469. "has_stacktrace": 0,
  2470. "relative_volume": 1,
  2471. "event_halflife_hours": 4,
  2472. "issue_halflife_hours": 24 * 7,
  2473. "v2": False,
  2474. "norm": False,
  2475. }
  2476. }
  2477. query_executor = self.backend._get_query_executor()
  2478. no_stacktrace_event = self.store_event(
  2479. data={
  2480. "event_id": "d" * 32,
  2481. "message": "oh no",
  2482. "timestamp": (base_datetime - timedelta(hours=1)).isoformat(),
  2483. },
  2484. project_id=self.project.id,
  2485. )
  2486. group1 = Group.objects.get(id=no_stacktrace_event.group.id)
  2487. stacktrace_event = self.store_event(
  2488. data={
  2489. "event_id": "d" * 32,
  2490. "exception": {
  2491. "values": [
  2492. {
  2493. "type": "AnError",
  2494. "value": "Bad request",
  2495. "stacktrace": {
  2496. "frames": [
  2497. {
  2498. "module": "<my module>",
  2499. },
  2500. ]
  2501. },
  2502. }
  2503. ]
  2504. },
  2505. "timestamp": (base_datetime - timedelta(hours=1)).isoformat(),
  2506. },
  2507. project_id=self.project.id,
  2508. )
  2509. group2 = Group.objects.get(id=stacktrace_event.group.id)
  2510. results = query_executor.snuba_search(
  2511. start=None,
  2512. end=None,
  2513. project_ids=[self.project.id],
  2514. environment_ids=[],
  2515. sort_field="trends",
  2516. organization=self.organization,
  2517. group_ids=[group1.id, group2.id],
  2518. limit=150,
  2519. aggregate_kwargs=agg_kwargs,
  2520. )[0]
  2521. group1_score = results[0][1]
  2522. group2_score = results[1][1]
  2523. assert group1_score == group2_score
  2524. agg_kwargs["trends"].update({"has_stacktrace": 3})
  2525. results = query_executor.snuba_search(
  2526. start=None,
  2527. end=None,
  2528. project_ids=[self.project.id],
  2529. environment_ids=[],
  2530. sort_field="trends",
  2531. organization=self.organization,
  2532. group_ids=[group1.id, group2.id],
  2533. limit=150,
  2534. aggregate_kwargs=agg_kwargs,
  2535. )[0]
  2536. group1_score = results[0][1]
  2537. group2_score = results[1][1]
  2538. # check that a group with an event with a stacktrace has a higher weight than one without
  2539. assert group1_score < group2_score
  2540. def test_trends_event_halflife_results(self):
  2541. """Test that the scoring results change when we pass in different event halflife weights"""
  2542. base_datetime = before_now(hours=1)
  2543. event1 = self.store_event(
  2544. data={
  2545. "fingerprint": ["put-me-in-group1"],
  2546. "event_id": "a" * 32,
  2547. "timestamp": (base_datetime - timedelta(hours=1)).isoformat(),
  2548. "message": "foo",
  2549. "stacktrace": {"frames": [{"module": "group1"}]},
  2550. "environment": "staging",
  2551. "level": "fatal",
  2552. },
  2553. project_id=self.project.id,
  2554. )
  2555. event2 = self.store_event(
  2556. data={
  2557. "fingerprint": ["put-me-in-group2"],
  2558. "event_id": "b" * 32,
  2559. "timestamp": base_datetime.isoformat(),
  2560. "message": "bar",
  2561. "stacktrace": {"frames": [{"module": "group2"}]},
  2562. "environment": "staging",
  2563. "level": "error",
  2564. },
  2565. project_id=self.project.id,
  2566. )
  2567. group1 = Group.objects.get(id=event1.group.id)
  2568. group2 = Group.objects.get(id=event2.group.id)
  2569. agg_kwargs = {
  2570. "trends": {
  2571. "log_level": 0,
  2572. "has_stacktrace": 0,
  2573. "relative_volume": 1,
  2574. "event_halflife_hours": 4,
  2575. "issue_halflife_hours": 24 * 7,
  2576. "v2": False,
  2577. "norm": False,
  2578. }
  2579. }
  2580. query_executor = self.backend._get_query_executor()
  2581. results = query_executor.snuba_search(
  2582. start=None,
  2583. end=None,
  2584. project_ids=[self.project.id],
  2585. environment_ids=[],
  2586. sort_field="trends",
  2587. organization=self.organization,
  2588. group_ids=[group1.id, group2.id],
  2589. limit=150,
  2590. aggregate_kwargs=agg_kwargs,
  2591. )[0]
  2592. group1_score_before = results[0][1]
  2593. group2_score_before = results[1][1]
  2594. # initially group 2's score is higher since it has a more recent event
  2595. assert group2_score_before > group1_score_before
  2596. agg_kwargs["trends"].update({"event_halflife_hours": 2})
  2597. results = query_executor.snuba_search(
  2598. start=None,
  2599. end=None,
  2600. project_ids=[self.project.id],
  2601. environment_ids=[],
  2602. sort_field="trends",
  2603. organization=self.organization,
  2604. group_ids=[group1.id, group2.id],
  2605. limit=150,
  2606. aggregate_kwargs=agg_kwargs,
  2607. )[0]
  2608. group1_score_after = results[0][1]
  2609. group2_score_after = results[1][1]
  2610. assert group1_score_after < group2_score_after
  2611. def test_trends_mixed_group_types(self):
  2612. base_datetime = before_now(hours=1)
  2613. error_event = self.store_event(
  2614. data={
  2615. "fingerprint": ["put-me-in-group1"],
  2616. "event_id": "a" * 32,
  2617. "timestamp": (base_datetime - timedelta(hours=1)).isoformat(),
  2618. "message": "foo",
  2619. "stacktrace": {"frames": [{"module": "group1"}]},
  2620. "environment": "staging",
  2621. "level": "fatal",
  2622. },
  2623. project_id=self.project.id,
  2624. )
  2625. error_group = error_event.group
  2626. profile_event_id = uuid.uuid4().hex
  2627. _, group_info = self.process_occurrence(
  2628. event_id=profile_event_id,
  2629. project_id=self.project.id,
  2630. event_data={
  2631. "title": "some problem",
  2632. "platform": "python",
  2633. "tags": {"my_tag": "1"},
  2634. "timestamp": before_now(minutes=1).isoformat(),
  2635. "received": before_now(minutes=1).isoformat(),
  2636. },
  2637. )
  2638. assert group_info is not None
  2639. profile_group_1 = group_info.group
  2640. agg_kwargs = {
  2641. "trends": {
  2642. "log_level": 0,
  2643. "has_stacktrace": 0,
  2644. "relative_volume": 1,
  2645. "event_halflife_hours": 4,
  2646. "issue_halflife_hours": 24 * 7,
  2647. "v2": False,
  2648. "norm": False,
  2649. }
  2650. }
  2651. query_executor = self.backend._get_query_executor()
  2652. with self.feature(
  2653. [
  2654. ProfileFileIOGroupType.build_visible_feature_name(),
  2655. ]
  2656. ):
  2657. results = query_executor.snuba_search(
  2658. start=None,
  2659. end=None,
  2660. project_ids=[self.project.id],
  2661. environment_ids=[],
  2662. sort_field="trends",
  2663. organization=self.organization,
  2664. group_ids=[profile_group_1.id, error_group.id],
  2665. limit=150,
  2666. aggregate_kwargs=agg_kwargs,
  2667. )[0]
  2668. error_group_score = results[0][1]
  2669. profile_group_score = results[1][1]
  2670. assert error_group_score > 0
  2671. assert profile_group_score > 0
  2672. class EventsTransactionsSnubaSearchTest(TestCase, SharedSnubaMixin):
  2673. @property
  2674. def backend(self):
  2675. return EventsDatasetSnubaSearchBackend()
  2676. def setUp(self):
  2677. super().setUp()
  2678. self.base_datetime = before_now(days=3)
  2679. transaction_event_data = {
  2680. "level": "info",
  2681. "message": "ayoo",
  2682. "type": "transaction",
  2683. "culprit": "app/components/events/eventEntries in map",
  2684. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  2685. }
  2686. with (
  2687. mock.patch(
  2688. "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
  2689. side_effect=send_issue_occurrence_to_eventstream,
  2690. ) as mock_eventstream,
  2691. mock.patch.object(
  2692. PerformanceRenderBlockingAssetSpanGroupType,
  2693. "noise_config",
  2694. new=NoiseConfig(0, timedelta(minutes=1)),
  2695. ),
  2696. ):
  2697. self.store_event(
  2698. data={
  2699. **transaction_event_data,
  2700. "event_id": "a" * 32,
  2701. "timestamp": before_now(minutes=1).isoformat(),
  2702. "start_timestamp": (before_now(minutes=1, seconds=5)).isoformat(),
  2703. "tags": {"my_tag": 1},
  2704. "fingerprint": [
  2705. f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"
  2706. ],
  2707. },
  2708. project_id=self.project.id,
  2709. )
  2710. self.perf_group_1 = mock_eventstream.call_args[0][2].group
  2711. self.store_event(
  2712. data={
  2713. **transaction_event_data,
  2714. "event_id": "a" * 32,
  2715. "timestamp": before_now(minutes=2).isoformat(),
  2716. "start_timestamp": before_now(minutes=2, seconds=5).isoformat(),
  2717. "tags": {"my_tag": 1},
  2718. "fingerprint": [
  2719. f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group2"
  2720. ],
  2721. },
  2722. project_id=self.project.id,
  2723. )
  2724. self.perf_group_2 = mock_eventstream.call_args[0][2].group
  2725. error_event_data = {
  2726. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  2727. "message": "bar",
  2728. "environment": "staging",
  2729. "tags": {
  2730. "server": "example.com",
  2731. "url": "http://example.com",
  2732. "sentry:user": "event2@example.com",
  2733. "my_tag": 1,
  2734. },
  2735. }
  2736. error_event = self.store_event(
  2737. data={
  2738. **error_event_data,
  2739. "fingerprint": ["put-me-in-error_group_1"],
  2740. "event_id": "c" * 32,
  2741. "stacktrace": {"frames": [{"module": "error_group_1"}]},
  2742. },
  2743. project_id=self.project.id,
  2744. )
  2745. self.error_group_1 = error_event.group
  2746. error_event_2 = self.store_event(
  2747. data={
  2748. **error_event_data,
  2749. "fingerprint": ["put-me-in-error_group_2"],
  2750. "event_id": "d" * 32,
  2751. "stacktrace": {"frames": [{"module": "error_group_2"}]},
  2752. },
  2753. project_id=self.project.id,
  2754. )
  2755. self.error_group_2 = error_event_2.group
  2756. def test_performance_query(self):
  2757. with self.feature(
  2758. [
  2759. self.perf_group_1.issue_type.build_visible_feature_name(),
  2760. ]
  2761. ):
  2762. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  2763. assert list(results) == [self.perf_group_1, self.perf_group_2]
  2764. results = self.make_query(
  2765. search_filter_query="issue.type:[performance_n_plus_one_db_queries, performance_render_blocking_asset_span] my_tag:1"
  2766. )
  2767. assert list(results) == [self.perf_group_1, self.perf_group_2]
  2768. def test_performance_query_no_duplicates(self):
  2769. # Regression test to catch an issue we had with performance issues showing duplicated in the
  2770. # issue stream. This was caused by us dual writing perf issues to transactions and to the
  2771. # issue platform. We'd end up reading the same issue twice and duplicate it in the response.
  2772. with self.feature(
  2773. [
  2774. self.perf_group_1.issue_type.build_visible_feature_name(),
  2775. ]
  2776. ):
  2777. results = self.make_query(search_filter_query="!issue.category:error my_tag:1")
  2778. assert list(results) == [self.perf_group_1, self.perf_group_2]
  2779. def test_performance_issue_search_feature_off(self):
  2780. with Feature({"organizations:performance-issues-search": False}):
  2781. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  2782. assert list(results) == []
  2783. with self.feature(
  2784. [
  2785. self.perf_group_1.issue_type.build_visible_feature_name(),
  2786. ]
  2787. ):
  2788. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  2789. assert list(results) == [self.perf_group_1, self.perf_group_2]
  2790. def test_error_performance_query(self):
  2791. with self.feature(
  2792. [
  2793. self.perf_group_1.issue_type.build_visible_feature_name(),
  2794. ]
  2795. ):
  2796. results = self.make_query(search_filter_query="my_tag:1")
  2797. assert list(results) == [
  2798. self.perf_group_1,
  2799. self.perf_group_2,
  2800. self.error_group_2,
  2801. self.error_group_1,
  2802. ]
  2803. results = self.make_query(
  2804. search_filter_query="issue.category:[performance, error] my_tag:1"
  2805. )
  2806. assert list(results) == [
  2807. self.perf_group_1,
  2808. self.perf_group_2,
  2809. self.error_group_2,
  2810. self.error_group_1,
  2811. ]
  2812. results = self.make_query(
  2813. search_filter_query="issue.type:[performance_render_blocking_asset_span, error] my_tag:1"
  2814. )
  2815. assert list(results) == [
  2816. self.perf_group_1,
  2817. self.perf_group_2,
  2818. self.error_group_2,
  2819. self.error_group_1,
  2820. ]
  2821. def test_cursor_performance_issues(self):
  2822. with self.feature(
  2823. [
  2824. self.perf_group_1.issue_type.build_visible_feature_name(),
  2825. ]
  2826. ):
  2827. results = self.make_query(
  2828. projects=[self.project],
  2829. search_filter_query="issue.category:performance my_tag:1",
  2830. sort_by="date",
  2831. limit=1,
  2832. count_hits=True,
  2833. )
  2834. assert list(results) == [self.perf_group_1]
  2835. assert results.hits == 2
  2836. results = self.make_query(
  2837. projects=[self.project],
  2838. search_filter_query="issue.category:performance my_tag:1",
  2839. sort_by="date",
  2840. limit=1,
  2841. cursor=results.next,
  2842. count_hits=True,
  2843. )
  2844. assert list(results) == [self.perf_group_2]
  2845. assert results.hits == 2
  2846. results = self.make_query(
  2847. projects=[self.project],
  2848. search_filter_query="issue.category:performance my_tag:1",
  2849. sort_by="date",
  2850. limit=1,
  2851. cursor=results.next,
  2852. count_hits=True,
  2853. )
  2854. assert list(results) == []
  2855. assert results.hits == 2
  2856. def test_perf_issue_search_message_term_queries_postgres(self):
  2857. from django.db.models import Q
  2858. from sentry.utils import snuba
  2859. transaction_name = "im a little tea pot"
  2860. with (
  2861. mock.patch(
  2862. "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
  2863. side_effect=send_issue_occurrence_to_eventstream,
  2864. ) as mock_eventstream,
  2865. mock.patch.object(
  2866. PerformanceRenderBlockingAssetSpanGroupType,
  2867. "noise_config",
  2868. new=NoiseConfig(0, timedelta(minutes=1)),
  2869. ),
  2870. ):
  2871. tx = self.store_event(
  2872. data={
  2873. "level": "info",
  2874. "culprit": "app/components/events/eventEntries in map",
  2875. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  2876. "fingerprint": [
  2877. f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"
  2878. ],
  2879. "event_id": "e" * 32,
  2880. "timestamp": self.base_datetime.isoformat(),
  2881. "start_timestamp": self.base_datetime.isoformat(),
  2882. "type": "transaction",
  2883. "transaction": transaction_name,
  2884. },
  2885. project_id=self.project.id,
  2886. )
  2887. assert "tea" in tx.search_message
  2888. created_group = mock_eventstream.call_args[0][2].group
  2889. find_group = Group.objects.filter(
  2890. Q(type=PerformanceRenderBlockingAssetSpanGroupType.type_id, message__icontains="tea")
  2891. ).first()
  2892. assert created_group == find_group
  2893. with self.feature(
  2894. [
  2895. created_group.issue_type.build_visible_feature_name(),
  2896. ]
  2897. ):
  2898. result = snuba.raw_query(
  2899. dataset=Dataset.IssuePlatform,
  2900. start=self.base_datetime - timedelta(hours=1),
  2901. end=self.base_datetime + timedelta(hours=1),
  2902. selected_columns=[
  2903. "event_id",
  2904. "group_id",
  2905. "transaction_name",
  2906. ],
  2907. groupby=None,
  2908. filter_keys={"project_id": [self.project.id], "event_id": [tx.event_id]},
  2909. referrer="_insert_transaction.verify_transaction",
  2910. )
  2911. assert result["data"][0]["transaction_name"] == transaction_name
  2912. assert result["data"][0]["group_id"] == created_group.id
  2913. results = self.make_query(search_filter_query="issue.category:performance tea")
  2914. assert set(results) == {created_group}
  2915. results2 = self.make_query(search_filter_query="tea")
  2916. assert set(results2) == {created_group}
  2917. def test_search_message_error_and_perf_issues(self):
  2918. with (
  2919. mock.patch(
  2920. "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
  2921. side_effect=send_issue_occurrence_to_eventstream,
  2922. ) as mock_eventstream,
  2923. mock.patch.object(
  2924. PerformanceRenderBlockingAssetSpanGroupType,
  2925. "noise_config",
  2926. new=NoiseConfig(0, timedelta(minutes=1)),
  2927. ),
  2928. ):
  2929. self.store_event(
  2930. data={
  2931. "level": "info",
  2932. "culprit": "app/components/events/eventEntries in map",
  2933. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  2934. "fingerprint": [
  2935. f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"
  2936. ],
  2937. "event_id": "e" * 32,
  2938. "timestamp": self.base_datetime.isoformat(),
  2939. "start_timestamp": self.base_datetime.isoformat(),
  2940. "type": "transaction",
  2941. "transaction": "/api/0/events",
  2942. },
  2943. project_id=self.project.id,
  2944. )
  2945. perf_issue = mock_eventstream.call_args[0][2].group
  2946. assert perf_issue
  2947. error = self.store_event(
  2948. data={
  2949. "fingerprint": ["another-random-group"],
  2950. "event_id": "d" * 32,
  2951. "message": "Uncaught exception on api /api/0/events",
  2952. "environment": "production",
  2953. "tags": {"server": "example.com", "sentry:user": "event3@example.com"},
  2954. "timestamp": self.base_datetime.isoformat(),
  2955. "stacktrace": {"frames": [{"module": "group1"}]},
  2956. },
  2957. project_id=self.project.id,
  2958. )
  2959. error_issue = error.group
  2960. assert error_issue
  2961. assert error_issue != perf_issue
  2962. with self.feature(
  2963. [
  2964. perf_issue.issue_type.build_visible_feature_name(),
  2965. ]
  2966. ):
  2967. assert set(self.make_query(search_filter_query="is:unresolved /api/0/events")) == {
  2968. perf_issue,
  2969. error_issue,
  2970. }
  2971. assert set(self.make_query(search_filter_query="/api/0/events")) == {
  2972. error_issue,
  2973. perf_issue,
  2974. }
  2975. def test_compound_message_negation(self):
  2976. self.store_event(
  2977. data={
  2978. "fingerprint": ["put-me-in-group1"],
  2979. "event_id": "2" * 32,
  2980. "message": "something",
  2981. "timestamp": self.base_datetime.isoformat(),
  2982. },
  2983. project_id=self.project.id,
  2984. )
  2985. self.store_event(
  2986. data={
  2987. "level": "info",
  2988. "culprit": "app/components/events/eventEntries in map",
  2989. "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
  2990. "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group12"],
  2991. "event_id": "e" * 32,
  2992. "timestamp": self.base_datetime.isoformat(),
  2993. "start_timestamp": self.base_datetime.isoformat(),
  2994. "type": "transaction",
  2995. "transaction": "something",
  2996. },
  2997. project_id=self.project.id,
  2998. )
  2999. error_issues_only = self.make_query(
  3000. search_filter_query="!message:else group.category:error"
  3001. )
  3002. error_and_perf_issues = self.make_query(search_filter_query="!message:else")
  3003. assert set(error_and_perf_issues) > set(error_issues_only)
  3004. class EventsGenericSnubaSearchTest(TestCase, SharedSnubaMixin, OccurrenceTestMixin):
  3005. @property
  3006. def backend(self):
  3007. return EventsDatasetSnubaSearchBackend()
  3008. def setUp(self):
  3009. super().setUp()
  3010. self.base_datetime = before_now(days=3)
  3011. event_id_1 = uuid.uuid4().hex
  3012. _, group_info = self.process_occurrence(
  3013. event_id=event_id_1,
  3014. project_id=self.project.id,
  3015. issue_title="File I/O on Main Thread",
  3016. event_data={
  3017. "title": "some problem",
  3018. "platform": "python",
  3019. "tags": {"my_tag": "1"},
  3020. "timestamp": before_now(minutes=1).isoformat(),
  3021. "received": before_now(minutes=1).isoformat(),
  3022. },
  3023. )
  3024. assert group_info is not None
  3025. self.profile_group_1 = group_info.group
  3026. event_id_2 = uuid.uuid4().hex
  3027. _, group_info = self.process_occurrence(
  3028. event_id=event_id_2,
  3029. project_id=self.project.id,
  3030. fingerprint=["put-me-in-group-2"],
  3031. issue_title="File I/O on Main Thread",
  3032. event_data={
  3033. "title": "some other problem",
  3034. "platform": "python",
  3035. "tags": {"my_tag": "1"},
  3036. "timestamp": before_now(minutes=2).isoformat(),
  3037. "received": before_now(minutes=2).isoformat(),
  3038. },
  3039. )
  3040. assert group_info is not None
  3041. self.profile_group_2 = group_info.group
  3042. event_id_3 = uuid.uuid4().hex
  3043. self.process_occurrence(
  3044. event_id=event_id_3,
  3045. project_id=self.project.id,
  3046. fingerprint=["put-me-in-group-3"],
  3047. event_data={
  3048. "title": "some other problem",
  3049. "platform": "python",
  3050. "tags": {"my_tag": "2"},
  3051. "timestamp": before_now(minutes=2).isoformat(),
  3052. "message_timestamp": before_now(minutes=2).isoformat(),
  3053. },
  3054. )
  3055. error_event_data = {
  3056. "timestamp": (self.base_datetime - timedelta(days=20)).isoformat(),
  3057. "message": "bar",
  3058. "environment": "staging",
  3059. "tags": {
  3060. "server": "example.com",
  3061. "url": "http://example.com",
  3062. "sentry:user": "event2@example.com",
  3063. "my_tag": 1,
  3064. },
  3065. }
  3066. error_event = self.store_event(
  3067. data={
  3068. **error_event_data,
  3069. "fingerprint": ["put-me-in-error_group_1"],
  3070. "event_id": "c" * 32,
  3071. "stacktrace": {"frames": [{"module": "error_group_1"}]},
  3072. },
  3073. project_id=self.project.id,
  3074. )
  3075. self.error_group_1 = error_event.group
  3076. error_event_2 = self.store_event(
  3077. data={
  3078. **error_event_data,
  3079. "fingerprint": ["put-me-in-error_group_2"],
  3080. "event_id": "d" * 32,
  3081. "stacktrace": {"frames": [{"module": "error_group_2"}]},
  3082. },
  3083. project_id=self.project.id,
  3084. )
  3085. self.error_group_2 = error_event_2.group
  3086. def test_no_feature(self):
  3087. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  3088. assert list(results) == []
  3089. def test_generic_query(self):
  3090. with self.feature([ProfileFileIOGroupType.build_visible_feature_name()]):
  3091. results = self.make_query(search_filter_query="issue.category:performance my_tag:1")
  3092. assert list(results) == [self.profile_group_1, self.profile_group_2]
  3093. results = self.make_query(
  3094. search_filter_query="issue.type:profile_file_io_main_thread my_tag:1"
  3095. )
  3096. assert list(results) == [self.profile_group_1, self.profile_group_2]
  3097. def test_generic_query_message(self):
  3098. with self.feature([ProfileFileIOGroupType.build_visible_feature_name()]):
  3099. results = self.make_query(search_filter_query="File I/O")
  3100. assert list(results) == [self.profile_group_1, self.profile_group_2]
  3101. def test_generic_query_perf(self):
  3102. event_id = uuid.uuid4().hex
  3103. group_type = PerformanceNPlusOneGroupType
  3104. with mock.patch.object(
  3105. PerformanceNPlusOneGroupType, "noise_config", new=NoiseConfig(0, timedelta(minutes=1))
  3106. ):
  3107. with self.feature(group_type.build_ingest_feature_name()):
  3108. _, group_info = self.process_occurrence(
  3109. event_id=event_id,
  3110. project_id=self.project.id,
  3111. type=group_type.type_id,
  3112. fingerprint=["some perf issue"],
  3113. event_data={
  3114. "title": "some problem",
  3115. "platform": "python",
  3116. "tags": {"my_tag": "2"},
  3117. "timestamp": before_now(minutes=1).isoformat(),
  3118. "received": before_now(minutes=1).isoformat(),
  3119. },
  3120. )
  3121. assert group_info is not None
  3122. with self.feature(
  3123. [
  3124. group_type.build_visible_feature_name(),
  3125. "organizations:performance-issues-search",
  3126. ]
  3127. ):
  3128. results = self.make_query(search_filter_query="issue.category:performance my_tag:2")
  3129. assert list(results) == [group_info.group]
  3130. def test_error_generic_query(self):
  3131. with self.feature([ProfileFileIOGroupType.build_visible_feature_name()]):
  3132. results = self.make_query(search_filter_query="my_tag:1")
  3133. assert list(results) == [
  3134. self.profile_group_1,
  3135. self.profile_group_2,
  3136. self.error_group_2,
  3137. self.error_group_1,
  3138. ]
  3139. results = self.make_query(
  3140. search_filter_query="issue.category:[performance, error] my_tag:1"
  3141. )
  3142. assert list(results) == [
  3143. self.profile_group_1,
  3144. self.profile_group_2,
  3145. self.error_group_2,
  3146. self.error_group_1,
  3147. ]
  3148. results = self.make_query(
  3149. search_filter_query="issue.type:[profile_file_io_main_thread, error] my_tag:1"
  3150. )
  3151. assert list(results) == [
  3152. self.profile_group_1,
  3153. self.profile_group_2,
  3154. self.error_group_2,
  3155. self.error_group_1,
  3156. ]
  3157. def test_cursor_profile_issues(self):
  3158. with self.feature([ProfileFileIOGroupType.build_visible_feature_name()]):
  3159. results = self.make_query(
  3160. projects=[self.project],
  3161. search_filter_query="issue.category:performance my_tag:1",
  3162. sort_by="date",
  3163. limit=1,
  3164. count_hits=True,
  3165. )
  3166. assert list(results) == [self.profile_group_1]
  3167. assert results.hits == 2
  3168. results = self.make_query(
  3169. projects=[self.project],
  3170. search_filter_query="issue.category:performance my_tag:1",
  3171. sort_by="date",
  3172. limit=1,
  3173. cursor=results.next,
  3174. count_hits=True,
  3175. )
  3176. assert list(results) == [self.profile_group_2]
  3177. assert results.hits == 2
  3178. results = self.make_query(
  3179. projects=[self.project],
  3180. search_filter_query="issue.category:performance my_tag:1",
  3181. sort_by="date",
  3182. limit=1,
  3183. cursor=results.next,
  3184. count_hits=True,
  3185. )
  3186. assert list(results) == []
  3187. assert results.hits == 2
  3188. def test_rejected_filters(self):
  3189. """
  3190. Any queries with `error.handled` or `error.unhandled` filters querying the search_issues dataset
  3191. should be rejected and return empty results.
  3192. """
  3193. with self.feature([ProfileFileIOGroupType.build_visible_feature_name()]):
  3194. results = self.make_query(
  3195. projects=[self.project],
  3196. search_filter_query="issue.category:performance error.unhandled:0",
  3197. sort_by="date",
  3198. limit=1,
  3199. count_hits=True,
  3200. )
  3201. results2 = self.make_query(
  3202. projects=[self.project],
  3203. search_filter_query="issue.category:performance error.unhandled:1",
  3204. sort_by="date",
  3205. limit=1,
  3206. count_hits=True,
  3207. )
  3208. result3 = self.make_query(
  3209. projects=[self.project],
  3210. search_filter_query="issue.category:performance error.handled:0",
  3211. sort_by="date",
  3212. limit=1,
  3213. count_hits=True,
  3214. )
  3215. results4 = self.make_query(
  3216. projects=[self.project],
  3217. search_filter_query="issue.category:performance error.handled:1",
  3218. sort_by="date",
  3219. limit=1,
  3220. count_hits=True,
  3221. )
  3222. results5 = self.make_query(
  3223. projects=[self.project],
  3224. search_filter_query="issue.category:performance error.main_thread:0",
  3225. sort_by="date",
  3226. limit=1,
  3227. count_hits=True,
  3228. )
  3229. results6 = self.make_query(
  3230. projects=[self.project],
  3231. search_filter_query="issue.category:performance error.main_thread:1",
  3232. sort_by="date",
  3233. limit=1,
  3234. count_hits=True,
  3235. )
  3236. assert (
  3237. list(results)
  3238. == list(results2)
  3239. == list(result3)
  3240. == list(results4)
  3241. == list(results5)
  3242. == list(results6)
  3243. == []
  3244. )
  3245. def test_feedback_category_hidden_default(self):
  3246. with self.feature([FeedbackGroup.build_visible_feature_name()]):
  3247. event_id_1 = uuid.uuid4().hex
  3248. self.process_occurrence(
  3249. **{
  3250. "project_id": self.project.id,
  3251. "event_id": event_id_1,
  3252. "fingerprint": ["c" * 32],
  3253. "issue_title": "User Feedback",
  3254. "type": FeedbackGroup.type_id,
  3255. "detection_time": datetime.now().timestamp(),
  3256. "level": "info",
  3257. },
  3258. event_data={
  3259. "platform": "python",
  3260. "timestamp": before_now(minutes=1).isoformat(),
  3261. "received": before_now(minutes=1).isoformat(),
  3262. },
  3263. )
  3264. results = self.make_query(
  3265. date_from=self.base_datetime,
  3266. date_to=self.base_datetime + timedelta(days=10),
  3267. )
  3268. assert set(results) == set()
  3269. def test_feedback_category_show_when_filtered_on(self):
  3270. with self.feature(
  3271. [
  3272. FeedbackGroup.build_visible_feature_name(),
  3273. FeedbackGroup.build_ingest_feature_name(),
  3274. ]
  3275. ):
  3276. event_id_1 = uuid.uuid4().hex
  3277. _, group_info = self.process_occurrence(
  3278. **{
  3279. "project_id": self.project.id,
  3280. "event_id": event_id_1,
  3281. "fingerprint": ["c" * 32],
  3282. "issue_title": "User Feedback",
  3283. "type": FeedbackGroup.type_id,
  3284. "detection_time": datetime.now().timestamp(),
  3285. "level": "info",
  3286. },
  3287. event_data={
  3288. "platform": "python",
  3289. "timestamp": before_now(minutes=1).isoformat(),
  3290. "received": before_now(minutes=1).isoformat(),
  3291. },
  3292. )
  3293. results = self.make_query(
  3294. search_filter_query="issue.category:feedback",
  3295. date_from=self.base_datetime,
  3296. date_to=self.base_datetime + timedelta(days=10),
  3297. )
  3298. assert group_info is not None
  3299. assert list(results) == [group_info.group]