12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679 |
- import datetime
- import uuid
- from unittest import mock
- import pytest
- from django.urls import reverse
- from sentry.replays.testutils import (
- assert_expected_response,
- mock_expected_response,
- mock_replay,
- mock_replay_click,
- )
- from sentry.testutils.cases import APITestCase, ReplaysSnubaTestCase
- from sentry.testutils.helpers.features import apply_feature_flag_on_cls
- from sentry.testutils.silo import region_silo_test
- from sentry.utils.cursors import Cursor
- from sentry.utils.snuba import QueryMemoryLimitExceeded
- REPLAYS_FEATURES = {"organizations:session-replay": True}
- @region_silo_test(stable=True)
- @apply_feature_flag_on_cls("organizations:global-views")
- class OrganizationReplayIndexTest(APITestCase, ReplaysSnubaTestCase):
- endpoint = "sentry-api-0-organization-replay-index"
- def setUp(self):
- super().setUp()
- self.login_as(user=self.user)
- self.url = reverse(self.endpoint, args=(self.organization.slug,))
- def test_feature_flag_disabled(self):
- """Test replays can be disabled."""
- response = self.client.get(self.url)
- assert response.status_code == 404
- def test_no_projects(self):
- """Test replays must be used with a project(s)."""
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url)
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert response_data["data"] == []
- def test_get_replays(self):
- """Test replays conform to the interchange format."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project.id,
- replay1_id,
- # NOTE: This is commented out due to a bug in CI. This will not affect
- # production use and have been verfied as working as of 08/10/2022.
- #
- # error_ids=[uuid.uuid4().hex, replay1_id], # duplicate error-id
- urls=[
- "http://localhost:3000/",
- "http://localhost:3000/login",
- ], # duplicate urls are okay,
- tags={"test": "hello", "other": "hello"},
- release="test",
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay1_id,
- # error_ids=[uuid.uuid4().hex, replay1_id], # duplicate error-id
- urls=["http://localhost:3000/"], # duplicate urls are okay
- tags={"test": "world", "other": "hello"},
- error_ids=[],
- release="",
- )
- )
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="myid",
- class_=["class1", "class2"],
- role="button",
- testid="1",
- alt="Alt",
- aria_label="AriaLabel",
- title="MyTitle",
- is_dead=1,
- is_rage=1,
- text="Hello",
- release=None,
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url)
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- # Assert the response body matches what was expected.
- expected_response = mock_expected_response(
- project.id,
- replay1_id,
- seq1_timestamp,
- seq2_timestamp,
- urls=[
- "http://localhost:3000/",
- "http://localhost:3000/login",
- "http://localhost:3000/",
- ],
- count_segments=2,
- # count_errors=3,
- count_errors=1,
- tags={"test": ["hello", "world"], "other": ["hello"]},
- activity=4,
- count_dead_clicks=1,
- count_rage_clicks=1,
- releases=["test"],
- clicks=[
- {
- "click.alt": "Alt",
- "click.classes": ["class1", "class2"],
- "click.id": "myid",
- "click.role": "button",
- "click.tag": "div",
- "click.testid": "1",
- "click.text": "Hello",
- "click.title": "MyTitle",
- "click.label": "AriaLabel",
- }
- ],
- )
- assert_expected_response(response_data["data"][0], expected_response)
- def test_get_replays_browse_screen_fields(self):
- """Test replay response with fields requested in production."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project.id,
- replay1_id,
- urls=[
- "http://localhost:3000/",
- "http://localhost:3000/login",
- ],
- tags={"test": "hello", "other": "hello"},
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay1_id,
- urls=["http://localhost:3000/"],
- tags={"test": "world", "other": "hello"},
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(
- self.url
- + "?field=activity&field=count_errors&field=duration&field=finished_at&field=id"
- "&field=project_id&field=started_at&field=urls&field=user"
- )
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- assert len(response_data["data"][0]) == 9
- assert "activity" in response_data["data"][0]
- assert "count_errors" in response_data["data"][0]
- assert "duration" in response_data["data"][0]
- assert "finished_at" in response_data["data"][0]
- assert "id" in response_data["data"][0]
- assert "project_id" in response_data["data"][0]
- assert "started_at" in response_data["data"][0]
- assert "urls" in response_data["data"][0]
- assert "user" in response_data["data"][0]
- assert len(response_data["data"][0]["user"]) == 5
- assert "id" in response_data["data"][0]["user"]
- assert "username" in response_data["data"][0]["user"]
- assert "email" in response_data["data"][0]["user"]
- assert "ip" in response_data["data"][0]["user"]
- assert "display_name" in response_data["data"][0]["user"]
- def test_get_replays_tags_field(self):
- """Test replay response with fields requested in production."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project.id,
- replay1_id,
- urls=[
- "http://localhost:3000/",
- "http://localhost:3000/login",
- ],
- tags={"test": "hello", "other": "hello"},
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay1_id,
- urls=["http://localhost:3000/"],
- tags={"test": "world", "other": "hello"},
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?field=tags")
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- assert len(response_data["data"][0]) == 1
- assert "tags" in response_data["data"][0]
- assert sorted(response_data["data"][0]["tags"]["test"]) == ["hello", "world"]
- assert response_data["data"][0]["tags"]["other"] == ["hello"]
- def test_get_replays_minimum_field_set(self):
- """Test replay response with fields requested in production."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay1_id,
- urls=[
- "http://localhost:3000/",
- "http://localhost:3000/login",
- ],
- tags={"test": "hello", "other": "hello"},
- user_id=123,
- replay_start_timestamp=int(seq1_timestamp.timestamp()),
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay2_id,
- urls=["http://localhost:3000/"],
- tags={"test": "world", "other": "hello"},
- replay_start_timestamp=int(seq1_timestamp.timestamp()),
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(
- self.url + "?field=id&sort=count_errors&query=test:hello OR user_id:123"
- )
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- assert len(response_data["data"][0]) == 1
- assert "id" in response_data["data"][0]
- def test_get_replays_filter_environment(self):
- """Test returned replays can not partially fall outside of range."""
- project = self.create_project(teams=[self.team])
- self.create_environment(name="development", project=self.project)
- self.create_environment(name="production", project=self.project)
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=20)
- timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=10)
- self.store_replays(
- mock_replay(timestamp0, project.id, replay1_id, environment="development")
- )
- self.store_replays(
- mock_replay(timestamp1, project.id, replay1_id, environment="development")
- )
- self.store_replays(
- mock_replay(timestamp0, project.id, replay2_id, environment="production")
- )
- self.store_replays(
- mock_replay(timestamp1, project.id, replay2_id, environment="production")
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?environment=development")
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert response_data["data"][0]["id"] == replay1_id
- response = self.client.get(self.url + "?environment=production")
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert response_data["data"][0]["id"] == replay2_id
- def test_get_replays_started_at_sorted(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- replay1_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=15)
- replay1_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=5)
- replay2_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=10)
- replay2_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=2)
- self.store_replays(mock_replay(replay1_timestamp0, project.id, replay1_id))
- self.store_replays(mock_replay(replay1_timestamp1, project.id, replay1_id))
- self.store_replays(mock_replay(replay2_timestamp0, project.id, replay2_id))
- self.store_replays(mock_replay(replay2_timestamp1, project.id, replay2_id))
- with self.feature(REPLAYS_FEATURES):
- # Latest first.
- response = self.client.get(self.url + "?sort=-started_at")
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay2_id
- assert response_data["data"][1]["id"] == replay1_id
- # Earlist first.
- response = self.client.get(self.url + "?sort=started_at")
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay1_id
- assert response_data["data"][1]["id"] == replay2_id
- def test_get_replays_finished_at_sorted(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- replay1_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=15)
- replay1_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=5)
- replay2_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=10)
- replay2_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=2)
- self.store_replays(mock_replay(replay1_timestamp0, project.id, replay1_id))
- self.store_replays(mock_replay(replay1_timestamp1, project.id, replay1_id))
- self.store_replays(mock_replay(replay2_timestamp0, project.id, replay2_id))
- self.store_replays(mock_replay(replay2_timestamp1, project.id, replay2_id))
- with self.feature(REPLAYS_FEATURES):
- # Latest first.
- response = self.client.get(self.url + "?sort=-finished_at")
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay2_id
- assert response_data["data"][1]["id"] == replay1_id
- # Earlist first.
- response = self.client.get(self.url + "?sort=finished_at")
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay1_id
- assert response_data["data"][1]["id"] == replay2_id
- def test_get_replays_duration_sorted(self):
- """Test replays can be sorted by duration."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- replay1_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=15)
- replay1_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=10)
- replay2_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=9)
- replay2_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=2)
- self.store_replays(mock_replay(replay1_timestamp0, project.id, replay1_id))
- self.store_replays(mock_replay(replay1_timestamp1, project.id, replay1_id))
- self.store_replays(mock_replay(replay2_timestamp0, project.id, replay2_id))
- self.store_replays(mock_replay(replay2_timestamp1, project.id, replay2_id))
- with self.feature(REPLAYS_FEATURES):
- # Smallest duration first.
- response = self.client.get(self.url + "?sort=duration")
- assert response.status_code == 200, response
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay1_id
- assert response_data["data"][1]["id"] == replay2_id
- # Largest duration first.
- response = self.client.get(self.url + "?sort=-duration")
- response_data = response.json()
- assert response_data["data"][0]["id"] == replay2_id
- assert response_data["data"][1]["id"] == replay1_id
- def test_get_replays_pagination(self):
- """Test replays can be paginated."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- replay2_id = uuid.uuid4().hex
- replay1_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=15)
- replay1_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=5)
- replay2_timestamp0 = datetime.datetime.now() - datetime.timedelta(seconds=10)
- replay2_timestamp1 = datetime.datetime.now() - datetime.timedelta(seconds=2)
- self.store_replays(mock_replay(replay1_timestamp0, project.id, replay1_id, segment_id=0))
- self.store_replays(mock_replay(replay1_timestamp1, project.id, replay1_id, segment_id=1))
- self.store_replays(mock_replay(replay2_timestamp0, project.id, replay2_id, segment_id=0))
- self.store_replays(mock_replay(replay2_timestamp1, project.id, replay2_id, segment_id=1))
- with self.feature(REPLAYS_FEATURES):
- # First page.
- response = self.get_success_response(
- self.organization.slug,
- cursor=Cursor(0, 0),
- per_page=1,
- )
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- assert response_data["data"][0]["id"] == replay2_id
- # Next page.
- response = self.get_success_response(
- self.organization.slug,
- cursor=Cursor(0, 1),
- per_page=1,
- )
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- assert response_data["data"][0]["id"] == replay1_id
- # Beyond pages.
- response = self.get_success_response(
- self.organization.slug,
- cursor=Cursor(0, 2),
- per_page=1,
- )
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 0
- def test_get_replays_user_filters(self):
- """Test replays conform to the interchange format."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project.id,
- replay1_id,
- platform="javascript",
- dist="abc123",
- user_id="123",
- user_email="username@example.com",
- user_name="username123",
- user_ip_address="127.0.0.1",
- sdk_name="sentry.javascript.react",
- sdk_version="6.18.10",
- os_name="macOS",
- os_version="15",
- browser_name="Firefox",
- browser_version="99",
- device_name="Macbook",
- device_brand="Apple",
- device_family="Macintosh",
- device_model="10",
- tags={"a": "m", "b": "q", "c": "test"},
- urls=["example.com"],
- segment_id=0,
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay1_id,
- user_id=None,
- user_name=None,
- user_email=None,
- ipv4=None,
- os_name=None,
- os_version=None,
- browser_name=None,
- browser_version=None,
- device_name=None,
- device_brand=None,
- device_family=None,
- device_model=None,
- tags={"a": "n", "b": "o"},
- error_ids=[],
- segment_id=1,
- )
- )
- with self.feature(REPLAYS_FEATURES):
- # Run all the queries individually to determine compliance.
- queries = [
- "replay_type:session",
- "error_ids:a3a62ef6ac86415b83c2416fc2f76db1",
- "error_id:a3a62ef6ac86415b83c2416fc2f76db1",
- "trace_ids:4491657243ba4dbebd2f6bd62b733080",
- "trace_id:4491657243ba4dbebd2f6bd62b733080",
- "trace:4491657243ba4dbebd2f6bd62b733080",
- "count_urls:1",
- "count_dead_clicks:0",
- "count_rage_clicks:0",
- "platform:javascript",
- "releases:version@1.3",
- "releases:[a,version@1.3]",
- "release:version@1.3",
- "release:[a,version@1.3]",
- "duration:17",
- "!duration:16",
- "duration:>16",
- "duration:<18",
- "duration:>=17",
- "duration:<=17",
- "duration:[16,17]",
- "!duration:[16,18]",
- "user.id:123",
- "user:username123",
- "user.username:username123",
- "user.email:username@example.com",
- "user.email:*@example.com",
- "user.ip:127.0.0.1",
- "sdk.name:sentry.javascript.react",
- "os.name:macOS",
- "os.version:15",
- "browser.name:Firefox",
- "browser.version:99",
- "dist:abc123",
- "releases:*3",
- "!releases:*4",
- "release:*3",
- "!release:*4",
- "count_segments:>=2",
- "device.name:Macbook",
- "device.brand:Apple",
- "device.family:Macintosh",
- "device.model:10",
- # Contains operator.
- f"id:[{replay1_id},{uuid.uuid4().hex},{uuid.uuid4().hex}]",
- f"!id:[{uuid.uuid4().hex}]",
- # Or expression.
- f"id:{replay1_id} OR id:{uuid.uuid4().hex} OR id:{uuid.uuid4().hex}",
- # Paren wrapped expression.
- f"((id:{replay1_id} OR id:b) AND (duration:>15 OR id:d))",
- # Implicit paren wrapped expression.
- f"(id:{replay1_id} OR id:b) AND (duration:>15 OR id:d)",
- # Implicit And.
- f"(id:{replay1_id} OR id:b) OR (duration:>15 platform:javascript)",
- # Tag filters.
- "tags[a]:m",
- "a:m",
- "c:*st",
- "!c:*zz",
- "urls:example.com",
- "url:example.com",
- "activity:3",
- "activity:>2",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200, query
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- # Test all queries as a single AND expression.
- all_queries = " ".join(queries)
- response = self.client.get(self.url + f"?query={all_queries}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, "all queries"
- # Assert returns empty result sets.
- null_queries = [
- "!replay_type:session",
- "!error_ids:a3a62ef6ac86415b83c2416fc2f76db1",
- "error_ids:123",
- "!error_id:a3a62ef6ac86415b83c2416fc2f76db1",
- "error_id:123",
- "!trace_ids:4491657243ba4dbebd2f6bd62b733080",
- "!trace_id:4491657243ba4dbebd2f6bd62b733080",
- "!trace:4491657243ba4dbebd2f6bd62b733080",
- "count_urls:0",
- "count_dead_clicks:>0",
- "count_rage_clicks:>0",
- f"id:{replay1_id} AND id:b",
- f"id:{replay1_id} AND duration:>1000",
- "id:b OR duration:>1000",
- "a:o",
- "a:[o,p]",
- "releases:a",
- "releases:*4",
- "!releases:*3",
- "releases:[a,b]",
- "release:a",
- "release:*4",
- "!release:*3",
- "release:[a,b]",
- "c:*zz",
- "!c:*st",
- "!activity:3",
- "activity:<2",
- ]
- for query in null_queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200, query
- response_data = response.json()
- assert len(response_data["data"]) == 0, query
- def test_get_replays_user_sorts(self):
- """Test replays conform to the interchange format."""
- project = self.create_project(teams=[self.team])
- project2 = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=15)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project2.id,
- replay1_id,
- error_ids=[uuid.uuid4().hex, uuid.uuid4().hex],
- platform="b",
- dist="b",
- user_id="b",
- user_email="b",
- user_name="b",
- user_ip_address="127.0.0.2",
- sdk_name="b",
- sdk_version="b",
- os_name="b",
- os_version="b",
- browser_name="b",
- browser_version="b",
- device_name="b",
- device_brand="b",
- device_family="b",
- device_model="b",
- segment_id=0,
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project2.id,
- replay1_id,
- platform="b",
- dist="b",
- user_id="b",
- user_email="b",
- user_name="b",
- user_ip_address="127.0.0.2",
- sdk_name="b",
- sdk_version="b",
- os_name="b",
- os_version="b",
- browser_name="b",
- browser_version="b",
- device_name="b",
- device_brand="b",
- device_family="b",
- device_model="b",
- segment_id=1,
- )
- )
- replay2_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=15)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=10)
- self.store_replays(
- mock_replay(
- seq1_timestamp,
- project.id,
- replay2_id,
- error_ids=[uuid.uuid4().hex],
- platform="a",
- dist="a",
- user_id="a",
- user_email="a",
- user_name="a",
- user_ip_address="127.0.0.1",
- sdk_name="a",
- sdk_version="a",
- os_name="a",
- os_version="a",
- browser_name="a",
- browser_version="a",
- device_name="a",
- device_brand="a",
- device_family="a",
- device_model="a",
- segment_id=0,
- )
- )
- self.store_replays(
- mock_replay(
- seq2_timestamp,
- project.id,
- replay2_id,
- platform="a",
- dist="a",
- user_id="a",
- user_email="a",
- user_name="a",
- user_ip_address="127.0.0.1",
- sdk_name="a",
- sdk_version="a",
- os_name="a",
- os_version="a",
- browser_name="a",
- browser_version="a",
- device_name="a",
- device_brand="a",
- device_family="a",
- device_model="a",
- segment_id=1,
- )
- )
- with self.feature(REPLAYS_FEATURES):
- # Run all the queries individually to determine compliance.
- queries = [
- "activity",
- "browser.name",
- "browser.version",
- "device.brand",
- "device.family",
- "device.model",
- "device.name",
- "dist",
- "duration",
- "os.name",
- "os.version",
- "platform",
- "project_id",
- "sdk.name",
- "user.email",
- "user.id",
- "user.username",
- ]
- for key in queries:
- # Ascending
- response = self.client.get(self.url + f"?sort={key}")
- assert response.status_code == 200, key
- r = response.json()
- assert len(r["data"]) == 2, key
- assert r["data"][0]["id"] == replay2_id, key
- assert r["data"][1]["id"] == replay1_id, key
- # Descending
- response = self.client.get(self.url + f"?sort=-{key}")
- assert response.status_code == 200, key
- r = response.json()
- assert len(r["data"]) == 2, key
- assert r["data"][0]["id"] == replay1_id, key
- assert r["data"][1]["id"] == replay2_id, key
- # No such thing as a bad field with the tag filtering behavior.
- #
- # def test_get_replays_filter_bad_field(self):
- # """Test replays conform to the interchange format."""
- # self.create_project(teams=[self.team])
- # with self.feature(REPLAYS_FEATURES):
- # response = self.client.get(self.url + "?query=xyz:a")
- # assert response.status_code == 400
- # assert b"xyz" in response.content
- def test_get_replays_filter_bad_value(self):
- """Test replays conform to the interchange format."""
- self.create_project(teams=[self.team])
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?query=duration:a")
- assert response.status_code == 400
- assert b"duration" in response.content
- def test_get_replays_no_multi_project_select(self):
- self.create_project(teams=[self.team])
- self.create_project(teams=[self.team])
- user = self.create_user(is_superuser=False)
- self.create_member(
- user=user, organization=self.organization, role="member", teams=[self.team]
- )
- self.login_as(user)
- with self.feature(REPLAYS_FEATURES), self.feature({"organizations:global-views": False}):
- response = self.client.get(self.url)
- assert response.status_code == 400
- assert response.data["detail"] == "You cannot view events from multiple projects."
- def test_get_replays_no_multi_project_select_query_referrer(self):
- self.create_project(teams=[self.team])
- self.create_project(teams=[self.team])
- user = self.create_user(is_superuser=False)
- self.create_member(
- user=user, organization=self.organization, role="member", teams=[self.team]
- )
- self.login_as(user)
- with self.feature(REPLAYS_FEATURES), self.feature({"organizations:global-views": False}):
- response = self.client.get(self.url + "?queryReferrer=issueReplays")
- assert response.status_code == 200
- def test_get_replays_unknown_field(self):
- """Test replays unknown fields raise a 400 error."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?field=unknown")
- assert response.status_code == 400
- def test_get_replays_activity_field(self):
- """Test replays activity field does not raise 400."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?field=activity")
- assert response.status_code == 200
- def test_archived_records_are_null_fields(self):
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=30)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=15)
- self.store_replays(mock_replay(seq1_timestamp, self.project.id, replay1_id))
- self.store_replays(
- mock_replay(seq2_timestamp, self.project.id, replay1_id, is_archived=True)
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url)
- assert response.status_code == 200
- assert response.json()["data"] == [
- {
- "id": replay1_id,
- "project_id": str(self.project.id),
- "trace_ids": [],
- "error_ids": [],
- "environment": None,
- "tags": [],
- "user": {"id": "Archived Replay", "display_name": "Archived Replay"},
- "sdk": {"name": None, "version": None},
- "os": {"name": None, "version": None},
- "browser": {"name": None, "version": None},
- "device": {"name": None, "brand": None, "model": None, "family": None},
- "urls": None,
- "started_at": None,
- "count_errors": None,
- "count_dead_clicks": None,
- "count_rage_clicks": None,
- "activity": None,
- "finished_at": None,
- "duration": None,
- "is_archived": True,
- "releases": None,
- "platform": None,
- "dist": None,
- "count_segments": None,
- "count_urls": None,
- "clicks": None,
- }
- ]
- # commented out until https://github.com/getsentry/snuba/pull/4137 is merged.
- # def test_archived_records_out_of_bounds(self):
- # replay1_id = uuid.uuid4().hex
- # seq1_timestamp = datetime.datetime.now() - datetime.timedelta(days=10)
- # seq2_timestamp = datetime.datetime.now() - datetime.timedelta(days=3)
- # self.store_replays(mock_replay(seq1_timestamp, self.project.id, replay1_id))
- # self.store_replays(
- # mock_replay(
- # seq2_timestamp, self.project.id, replay1_id, is_archived=True, segment_id=None
- # )
- # )
- # with self.feature(REPLAYS_FEATURES):
- # response = self.client.get(self.url)
- # assert response.status_code == 200
- # assert response.json()["data"] == [
- # {
- # "id": replay1_id,
- # "project_id": str(self.project.id),
- # "trace_ids": [],
- # "error_ids": [],
- # "environment": None,
- # "tags": [],
- # "user": {"id": "Archived Replay", "display_name": "Archived Replay"},
- # "sdk": {"name": None, "version": None},
- # "os": {"name": None, "version": None},
- # "browser": {"name": None, "version": None},
- # "device": {"name": None, "brand": None, "model": None, "family": None},
- # "urls": None,
- # "started_at": None,
- # "count_errors": None,
- # "activity": None,
- # "finished_at": None,
- # "duration": None,
- # "is_archived": True,
- # }
- # ]
- def test_get_replays_filter_clicks(self):
- """Test replays conform to the interchange format."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="myid",
- class_=["class1", "class2"],
- role="button",
- testid="1",
- alt="Alt",
- aria_label="AriaLabel",
- title="MyTitle",
- text="Hello",
- )
- )
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=2,
- tag="button",
- id="myid",
- class_=["class1", "class3"],
- )
- )
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "click.alt:Alt",
- "click.class:class1",
- "click.class:class2",
- "click.class:class3",
- "click.id:myid",
- "click.label:AriaLabel",
- "click.role:button",
- "click.tag:div",
- "click.tag:button",
- "click.testid:1",
- "click.textContent:Hello",
- "click.title:MyTitle",
- "click.selector:div#myid",
- "click.selector:div[alt=Alt]",
- "click.selector:div[title=MyTitle]",
- "click.selector:div[data-testid='1']",
- "click.selector:div[data-test-id='1']",
- "click.selector:div[role=button]",
- "click.selector:div#myid.class1.class2",
- # Single quotes around attribute value.
- "click.selector:div[role='button']",
- "click.selector:div#myid.class1.class2[role=button][aria-label='AriaLabel']",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200, query
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- queries = [
- "click.alt:NotAlt",
- "click.class:class4",
- "click.id:other",
- "click.label:NotAriaLabel",
- "click.role:form",
- "click.tag:header",
- "click.testid:2",
- "click.textContent:World",
- "click.title:NotMyTitle",
- "!click.selector:div#myid",
- "click.selector:div#notmyid",
- # Assert all classes must match.
- "click.selector:div#myid.class1.class2.class3",
- # Invalid selectors return no rows.
- "click.selector:$#%^#%",
- # Integer type role values are not allowed and must be wrapped in single quotes.
- "click.selector:div[title=1]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?query={query}")
- assert response.status_code == 200, query
- response_data = response.json()
- assert len(response_data["data"]) == 0, query
- def test_get_replays_click_fields(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="myid",
- class_=["class1", "class2"],
- role="button",
- testid="1",
- alt="Alt",
- aria_label="AriaLabel",
- title="MyTitle",
- text="Hello",
- )
- )
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=2,
- tag="button",
- id="myid",
- class_=["class1", "class3"],
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(self.url + "?field=clicks")
- assert response.status_code == 200, response.content
- response_data = response.json()
- assert response_data["data"] == [
- {
- "clicks": [
- {
- "click.alt": "Alt",
- "click.classes": ["class1", "class3"],
- "click.id": "myid",
- "click.role": "button",
- "click.tag": "button",
- "click.testid": "1",
- "click.text": "Hello",
- "click.title": "MyTitle",
- "click.label": "AriaLabel",
- },
- {
- "click.alt": None,
- "click.classes": ["class1", "class2"],
- "click.id": "myid",
- "click.role": None,
- "click.tag": "div",
- "click.testid": None,
- "click.text": None,
- "click.title": None,
- "click.label": None,
- },
- ]
- }
- ]
- def test_get_replays_filter_clicks_nested_selector(self):
- """Test replays do not support nested selectors."""
- project = self.create_project(teams=[self.team])
- self.store_replays(mock_replay(datetime.datetime.now(), project.id, uuid.uuid4().hex))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- 'click.selector:"div button"',
- 'click.selector:"div + button"',
- 'click.selector:"div ~ button"',
- 'click.selector:"div > button"',
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 400
- assert response.content == b'{"detail":"Nested selectors are not supported."}'
- def test_get_replays_filter_clicks_pseudo_element(self):
- """Assert replays only supports a subset of selector syntax."""
- project = self.create_project(teams=[self.team])
- self.store_replays(mock_replay(datetime.datetime.now(), project.id, uuid.uuid4().hex))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "click.selector:a::visited",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 400, query
- assert response.content == b'{"detail":"Pseudo-elements are not supported."}', query
- def test_get_replays_filter_clicks_unsupported_selector(self):
- """Assert replays only supports a subset of selector syntax."""
- project = self.create_project(teams=[self.team])
- self.store_replays(mock_replay(datetime.datetime.now(), project.id, uuid.uuid4().hex))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "click.selector:div:is(2)",
- "click.selector:p:active",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 400, query
- assert (
- response.content
- == b'{"detail":"Only attribute, class, id, and tag name selectors are supported."}'
- ), query
- def test_get_replays_filter_clicks_unsupported_attribute_selector(self):
- """Assert replays only supports a subset of selector syntax."""
- project = self.create_project(teams=[self.team])
- self.store_replays(mock_replay(datetime.datetime.now(), project.id, uuid.uuid4().hex))
- with self.feature(REPLAYS_FEATURES):
- queries = ["click.selector:div[xyz=test]"]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 400, query
- assert response.content == (
- b'{"detail":"Invalid attribute specified. Only alt, aria-label, role, '
- b'data-testid, data-test-id, and title are supported."}'
- ), query
- def test_get_replays_filter_clicks_unsupported_operators(self):
- """Assert replays only supports a subset of selector syntax."""
- project = self.create_project(teams=[self.team])
- self.store_replays(mock_replay(datetime.datetime.now(), project.id, uuid.uuid4().hex))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- 'click.selector:"[aria-label~=button]"',
- 'click.selector:"[aria-label|=button]"',
- 'click.selector:"[aria-label^=button]"',
- 'click.selector:"[aria-label$=button]"',
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 400, query
- assert (
- response.content == b'{"detail":"Only the \'=\' operator is supported."}'
- ), query
- def test_get_replays_field_order(self):
- """Test replay response with fields requested in production."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- # Invalid field-names error regardless of ordering.
- response = self.client.get(self.url + "?field=invalid&field=browser")
- assert response.status_code == 400
- response = self.client.get(self.url + "?field=browser&field=invalid")
- assert response.status_code == 400
- # Correct field-names never error.
- response = self.client.get(self.url + "?field=count_urls&field=browser")
- assert response.status_code == 200
- response = self.client.get(self.url + "?field=browser&field=count_urls")
- assert response.status_code == 200
- def test_get_replays_memory_error(self):
- """Test replay response with fields requested in production."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- # Invalid field-names error regardless of ordering.
- with mock.patch(
- "sentry.replays.endpoints.organization_replay_index.query_replays_collection",
- side_effect=QueryMemoryLimitExceeded("mocked error"),
- ):
- response = self.client.get(self.url)
- assert response.status_code == 400
- assert (
- response.content
- == b'{"detail":"Replay search query limits exceeded. Please narrow the time-range."}'
- )
- @pytest.mark.skip(reason="flaky: Date logic breaks - possibly due to stats-period.")
- def test_get_replays_dead_rage_click_cutoff(self):
- """Test rage and dead clicks are accumulated after the cutoff."""
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- pre_cutoff = datetime.datetime(year=2023, month=7, day=23)
- post_cutoff = datetime.datetime(year=2023, month=7, day=24)
- self.store_replays(
- mock_replay(
- pre_cutoff,
- project.id,
- replay1_id,
- )
- )
- self.store_replays(
- mock_replay(
- post_cutoff,
- project.id,
- replay1_id,
- )
- )
- self.store_replays(
- mock_replay_click(
- pre_cutoff,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="myid",
- class_=["class1", "class2"],
- role="button",
- testid="1",
- alt="Alt",
- aria_label="AriaLabel",
- title="MyTitle",
- is_dead=1,
- is_rage=1,
- text="Hello",
- )
- )
- self.store_replays(
- mock_replay_click(
- post_cutoff,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="myid",
- class_=["class1", "class2"],
- role="button",
- testid="1",
- alt="Alt",
- aria_label="AriaLabel",
- title="MyTitle",
- is_dead=1,
- is_rage=1,
- text="Hello",
- )
- )
- with self.feature(REPLAYS_FEATURES):
- response = self.client.get(
- self.url
- + f"?start={pre_cutoff.isoformat().split('.')[0]}&end={post_cutoff.isoformat().split('.')[0]}"
- )
- assert response.status_code == 200
- response_data = response.json()
- assert "data" in response_data
- assert len(response_data["data"]) == 1
- item = response_data["data"][0]
- assert item["count_dead_clicks"] == 1, item["count_dead_clicks"]
- assert item["count_rage_clicks"] == 1, item["count_rage_clicks"]
- @apply_feature_flag_on_cls("organizations:session-replay-optimized-search")
- class OrganizationReplayIndexOptimizedSearchTest(OrganizationReplayIndexTest):
- # Currently only available on the newest query engine so the test is defined within this
- # subclass.
- def test_get_replays_filter_clicks_non_click_rows(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=1,
- tag="div",
- id="id1",
- class_=["id1"],
- text="id1",
- role="id1",
- alt="id1",
- testid="id1",
- aria_label="id1",
- title="id1",
- )
- )
- self.store_replays(
- mock_replay_click(
- seq2_timestamp,
- project.id,
- replay1_id,
- node_id=2,
- tag="",
- id="id2",
- class_=["id2"],
- text="id2",
- role="id2",
- alt="id2",
- testid="id2",
- aria_label="id2",
- title="id2",
- )
- )
- with self.feature(REPLAYS_FEATURES):
- success_queries = [
- "click.id:id1",
- "click.class:[id1]",
- "click.textContent:id1",
- "click.role:id1",
- "click.alt:id1",
- "click.testid:id1",
- "click.label:id1",
- "click.title:id1",
- ]
- for query in success_queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- # These tests demonstrate what happens when you match a click value on non-click row.
- failure_queries = [
- "click.id:id2",
- "click.class:[id2]",
- "click.textContent:id2",
- "click.role:id2",
- "click.alt:id2",
- "click.testid:id2",
- "click.label:id2",
- "click.title:id2",
- ]
- for query in failure_queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 0, query
- # The following section tests the valid branches of the condition classes.
- def test_query_branches_string_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "device.brand:Apple",
- "!device.brand:Microsoft",
- "device.brand:[Apple,Microsoft]",
- "!device.brand:[Oracle,Microsoft]",
- "device.brand:App*",
- "!device.brand:Micro*",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_click_scalar_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- self.store_replays(
- mock_replay_click(
- seq2_timestamp, project.id, replay1_id, node_id=1, tag="div", id="id1"
- )
- )
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "click.id:id1",
- "!click.id:id2",
- "click.id:[id1,id2]",
- "!click.id:[id3,id2]",
- "click.id:*1",
- "!click.id:*2",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_click_array_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- self.store_replays(
- mock_replay_click(
- seq2_timestamp, project.id, replay1_id, node_id=1, tag="div", class_=["class1"]
- )
- )
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "click.class:class1",
- "!click.class:class2",
- "click.class:[class1,class2]",
- "!click.class:[class3,class2]",
- "click.class:*1",
- "!click.class:*2",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_array_of_string_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id, urls=["Apple"]))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, urls=[]))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "urls:Apple",
- "!urls:Microsoft",
- "urls:[Apple,Microsoft]",
- "!urls:[Oracle,Microsoft]",
- "urls:App*",
- "!urls:Micro*",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_integer_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, error_ids=[]))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "count_errors:1",
- "!count_errors:2",
- "count_errors:>0",
- "count_errors:<2",
- "count_errors:>=1",
- "count_errors:<=1",
- "count_errors:[1,2]",
- "!count_errors:[2,3]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_error_ids_conditions(self):
- project = self.create_project(teams=[self.team])
- uid1 = uuid.uuid4().hex
- uid2 = uuid.uuid4().hex
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id, error_ids=[uid1]))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- f"error_ids:{uid1}",
- f"!error_ids:{uid2}",
- f"error_ids:[{uid1},{uid2}]",
- f"!error_ids:[{uid2}]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_uuid_conditions(self):
- project = self.create_project(teams=[self.team])
- uid1 = uuid.uuid4().hex
- uid2 = uuid.uuid4().hex
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id, trace_ids=[uid1]))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- f"trace_ids:{uid1}",
- f"!trace_ids:{uid2}",
- f"trace_ids:[{uid1},{uid2}]",
- f"!trace_ids:[{uid2}]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_string_uuid_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- uid2 = uuid.uuid4().hex
- queries = [
- f"id:{replay1_id}",
- f"!id:{uid2}",
- f"id:[{replay1_id},{uid2}]",
- f"!id:[{uid2}]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_ip_address_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "user.ip_address:127.0.0.1",
- "!user.ip_address:192.168.0.1",
- "user.ip_address:[127.0.0.1,192.168.0.1]",
- "!user.ip_address:[192.168.0.1]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
- def test_query_branches_computed_activity_conditions(self):
- project = self.create_project(teams=[self.team])
- replay1_id = uuid.uuid4().hex
- seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
- seq2_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=5)
- self.store_replays(mock_replay(seq1_timestamp, project.id, replay1_id))
- self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, error_ids=[]))
- with self.feature(REPLAYS_FEATURES):
- queries = [
- "activity:2",
- "!activity:1",
- "activity:>1",
- "activity:<3",
- "activity:>=2",
- "activity:<=2",
- "activity:[1,2]",
- "!activity:[1,3]",
- ]
- for query in queries:
- response = self.client.get(self.url + f"?field=id&query={query}")
- assert response.status_code == 200
- response_data = response.json()
- assert len(response_data["data"]) == 1, query
|