Files
sentinel/tests/sentry/replays/test_organization_replay_index.py
2024-02-15 17:01:50 +00:00

2029 lines
81 KiB
Python

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
@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"],
component_name="SignUpForm",
role="button",
testid="1",
alt="Alt",
aria_label="AriaLabel",
title="MyTitle",
is_dead=1,
is_rage=1,
text="Hello",
release=None,
)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "error", replay1_id, "a3a62ef6ac86415b83c2416fc2f76db1"
)
)
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.component_name": "SignUpForm",
"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="[email protected]",
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,
)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "fatal", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "fatal", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "error", replay1_id, "a3a62ef6ac86415b83c2416fc2f76db1"
)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "warning", replay1_id, uuid.uuid4().hex
)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "info", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "debug", replay1_id, uuid.uuid4().hex)
)
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:[email protected]",
"releases:[a,[email protected]]",
"release:[email protected]",
"release:[a,[email protected]]",
"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:[email protected]",
"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",
"count_warnings:1",
"count_warnings:>0",
"count_warnings:<2",
"count_infos:2",
"count_infos:>1",
"count_infos:<3",
]
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,
)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project2.id, "fatal", replay1_id, uuid.uuid4().hex
)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project2.id, "error", replay1_id, uuid.uuid4().hex
)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project2.id, "warning", replay1_id, uuid.uuid4().hex
)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project2.id, "info", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project2.id, "debug", replay1_id, uuid.uuid4().hex
)
)
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",
"count_warnings",
"count_infos",
]
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,
"warning_ids": None,
"info_ids": None,
"count_warnings": None,
"count_infos": 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", "class:hover"],
component_name="SignUpForm",
role="button",
testid="1",
alt="Alt",
aria_label="AriaLabel",
title="MyTitle",
text="Hello",
is_dead=1,
is_rage=1,
)
)
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.component_name:SignUpForm",
"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-sentry-component=SignUpForm]",
"click.selector:div[data-testid='1']",
"click.selector:div[data-test-id='1']",
"click.selector:div[role=button]",
"click.selector:div#myid.class1.class2",
"dead.selector:div#myid",
"rage.selector:div#myid",
# Assert selectors with special characters in them can be queried.
"click.selector:div.class%5C:hover",
# 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.component_name:NotSignUpForm",
"click.role:form",
"click.tag:header",
"click.testid:2",
"click.textContent:World",
"click.title:NotMyTitle",
"!click.selector:div#myid",
"click.selector:div#notmyid",
"dead.selector:button#myid",
"rage.selector:button#myid",
# 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"],
component_name="SignUpForm",
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.component_name": "SignUpForm",
"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.component_name": None,
"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, data-sentry-component, 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":"Query limits exceeded. Try narrowing your request."}'
)
@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"]
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=[]))
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "error", replay1_id, "a3a62ef6ac86415b83c2416fc2f76db1"
)
)
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
def test_query_scalar_optimization_multiple_varying(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", "microsoft"])
)
self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, urls=[]))
with self.feature(REPLAYS_FEATURES):
response = self.client.get(self.url + "?field=id&query=urls:apple urls:microsoft")
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 1
def test_get_replays_missing_segment_0(self):
"""Test fetching replays when the 0th segment is missing."""
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, segment_id=2))
self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, segment_id=1))
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"]) == 0
def test_new_errors_column(self):
project = self.create_project(teams=[self.team])
uid1 = uuid.uuid4().hex
uid2 = uuid.uuid4().hex
uid3 = 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, uid2])
)
self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id, error_ids=[]))
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "error", replay1_id, uid1)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "fatal", replay1_id, uid2)
)
with self.feature(REPLAYS_FEATURES):
queries = [
f"error_id:{uid1}",
f"error_id:{uid2}",
f"error_id:[{uid1}]",
f"!error_id:[{uid3}]",
f"!error_id:{uid3}",
]
for query in queries:
response = self.client.get(self.url + f"?field=id&field=error_ids&query={query}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 1, query
assert len(response_data["data"][0]["error_ids"]) == 2, query
response = self.client.get(
self.url + f"?field=id&field=error_ids&query=error_id:{uid3}"
)
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 0, query
def test_warnings_column(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))
self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "warning", replay1_id, uid1)
)
with self.feature(REPLAYS_FEATURES):
queries = [
f"warning_id:{uid1}",
f"warning_id:[{uid1}]",
f"!warning_id:[{uid2}]",
f"!warning_id:{uid2}",
]
for query in queries:
response = self.client.get(self.url + f"?field=id&field=warning_ids&query={query}")
assert response.status_code == 200, query
response_data = response.json()
assert len(response_data["data"]) == 1, query
assert len(response_data["data"][0]["warning_ids"]) == 1, query
response = self.client.get(
self.url + f"?field=id&field=warning_ids&query=warning_id:{uid2}"
)
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 0, query
def test_infos_column(self):
project = self.create_project(teams=[self.team])
uid1 = uuid.uuid4().hex
uid2 = uuid.uuid4().hex
uid3 = 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))
self.store_replays(mock_replay(seq2_timestamp, project.id, replay1_id))
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "info", replay1_id, uid1)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "debug", replay1_id, uid2)
)
with self.feature(REPLAYS_FEATURES):
queries = [
f"info_id:{uid1}",
f"info_id:{uid2}",
f"info_id:[{uid1}]",
f"!info_id:[{uid3}]",
f"!info_id:{uid3}",
]
for query in queries:
response = self.client.get(self.url + f"?field=id&field=info_ids&query={query}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 1, query
assert len(response_data["data"][0]["info_ids"]) == 2, query
response = self.client.get(self.url + f"?field=id&field=info_ids&query=info_id:{uid3}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 0, query
def test_exp_query_branches_error_ids_conditions(self):
"""
Test that the new columns work the same w/ only the previous errors populated
"""
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, error_ids=[]))
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&field=error_ids&query={query}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data["data"]) == 1, query
assert len(response_data["data"][0]["error_ids"]) == 1, query
def test_event_id_count_columns(self):
project = self.create_project(teams=[self.team])
replay1_id = uuid.uuid4().hex
other_replay = 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, other_replay))
self.store_replays(mock_replay(seq2_timestamp, project.id, other_replay))
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(
self.mock_event_links(seq1_timestamp, project.id, "fatal", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "error", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "warning", replay1_id, uuid.uuid4().hex
)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "info", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(seq1_timestamp, project.id, "debug", replay1_id, uuid.uuid4().hex)
)
self.store_replays(
self.mock_event_links(
seq1_timestamp, project.id, "debug", other_replay, uuid.uuid4().hex
)
)
with self.feature(REPLAYS_FEATURES):
response = self.client.get(
self.url + f"?field=id&field=count_warnings&field=count_infos&query=id:{replay1_id}"
)
assert response.status_code == 200
response_data = response.json()
assert response_data["data"][0]["count_warnings"] == 1
def test_non_empty_string_scalar(self):
project = self.create_project(teams=[self.team])
replay1_id = uuid.uuid4().hex
seq1_timestamp = datetime.datetime.now() - datetime.timedelta(seconds=22)
self.store_replays(
mock_replay(seq1_timestamp, project.id, replay1_id, segment_id=0, dist="")
)
self.store_replays(
mock_replay(seq1_timestamp, project.id, replay1_id, segment_id=0, dist="1")
)
# "dist" is used as a placeholder to test the "NonEmptyStringScalar" class. Empty
# strings should be ignored when performing negation queries.
with self.feature(REPLAYS_FEATURES):
# dist should be findable if any of its filled values match the query.
queries = [
"dist:1",
"dist:[1]",
"dist:*1*",
]
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
# If we explicitly negate dist's filled value we should also ignore empty
# values.
queries = [
"!dist:1",
"!dist:[1]",
"!dist:*1*",
]
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"]) == 0, query