From 88d31225d13573b96067d97a05ad28513c0a542a Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 8 May 2025 15:04:51 -0700 Subject: [PATCH 1/3] Update query config --- src/sentry/replays/query.py | 8 ++++++++ src/sentry/replays/usecases/query/configs/aggregate.py | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/sentry/replays/query.py b/src/sentry/replays/query.py index c56db269e41c29..281dd54422b1d3 100644 --- a/src/sentry/replays/query.py +++ b/src/sentry/replays/query.py @@ -563,6 +563,10 @@ def _empty_uuids_lambda(): "user.email": ["user_email"], "user.username": ["user_username"], "user.ip": ["user_ip"], + "user.geo.city": ["user_geo_city"], + "user.geo.country_code": ["user_geo_country_code"], + "user.geo.region": ["user_geo_region"], + "user.geo.subdivision": ["user_geo_subdivision"], "os.name": ["os_name"], "os.version": ["os_version"], "browser.name": ["browser_name"], @@ -715,6 +719,10 @@ def _empty_uuids_lambda(): parameters=[anyIfNonZeroIP(column_name="ip_address_v4", aliased=False)], alias="user_ip", ), + "user_geo_city": anyIf(column_name="user_geo_city"), + "user_geo_country_code": anyIf(column_name="user_geo_country_code"), + "user_geo_region": anyIf(column_name="user_geo_region"), + "user_geo_subdivision": anyIf(column_name="user_geo_subdivision"), "os_name": anyIf(column_name="os_name"), "os_version": anyIf(column_name="os_version"), "browser_name": anyIf(column_name="browser_name"), diff --git a/src/sentry/replays/usecases/query/configs/aggregate.py b/src/sentry/replays/usecases/query/configs/aggregate.py index 315e2a201d8a5f..2cbd7a39565bad 100644 --- a/src/sentry/replays/usecases/query/configs/aggregate.py +++ b/src/sentry/replays/usecases/query/configs/aggregate.py @@ -131,6 +131,10 @@ def array_string_field(column_name: str) -> StringColumnField: "user.id": string_field("user_id"), "user.ip_address": NullableStringColumnField("ip_address_v4", parse_ipv4, SumOfIPv4Scalar), "user.username": string_field("user_name"), + "user.geo.city": string_field("user_geo_city"), + "user.geo.country_code": string_field("user_geo_country_code"), + "user.geo.region": string_field("user_geo_region"), + "user.geo.subdivision": string_field("user_geo_subdivision"), "viewed_by_id": IntegerColumnField("viewed_by_id", parse_int, SumOfIntegerIdScalar), "warning_ids": UUIDColumnField("warning_id", parse_uuid, SumOfUUIDScalar), } From 14f67d0690c83424f1d86af64732b968181ce02a Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 8 May 2025 15:50:14 -0700 Subject: [PATCH 2/3] Add coverage --- src/sentry/replays/testutils.py | 12 ++++++++++++ .../sentry/replays/test_organization_replay_index.py | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/sentry/replays/testutils.py b/src/sentry/replays/testutils.py index ea243dfbcf5a12..9d616154620018 100644 --- a/src/sentry/replays/testutils.py +++ b/src/sentry/replays/testutils.py @@ -120,6 +120,12 @@ def mock_expected_response( "email": kwargs.pop("user_email", "username@example.com"), "username": kwargs.pop("user_name", "username"), "ip": kwargs.pop("user_ip", "127.0.0.1"), + "geo": { + "city": kwargs.pop("user_geo_city", "San Francisco"), + "country_code": kwargs.pop("user_geo_country_code", "USA"), + "region": kwargs.pop("user_geo_region", "United States"), + "subdivision": kwargs.pop("user_geo_subdivision", "California"), + }, }, "tags": kwargs.pop("tags", {}), "activity": kwargs.pop("activity", 0), @@ -180,6 +186,12 @@ def mock_replay( "username": kwargs.pop("user_name", "username"), "email": kwargs.pop("user_email", "username@example.com"), "ip_address": kwargs.pop("ipv4", "127.0.0.1"), + "geo": { + "city": kwargs.pop("user_geo_city", "San Francisco"), + "country_code": kwargs.pop("user_geo_country_code", "USA"), + "region": kwargs.pop("user_geo_region", "United States"), + "subdivision": kwargs.pop("user_geo_subdivision", "California"), + }, }, "sdk": { "name": kwargs.pop("sdk_name", "sentry.javascript.react"), diff --git a/tests/sentry/replays/test_organization_replay_index.py b/tests/sentry/replays/test_organization_replay_index.py index 7c1732dc68f9bc..7b155164f6af76 100644 --- a/tests/sentry/replays/test_organization_replay_index.py +++ b/tests/sentry/replays/test_organization_replay_index.py @@ -652,6 +652,11 @@ def test_get_replays_user_filters(self): "user.ip:127.0.0.1", "user.ip:[127.0.0.1, 10.0.4.4]", "!user.ip:[127.1.1.1, 10.0.4.4]", + 'user.geo.city:"San Francisco"', + "user.geo.country_code:USA", + 'user.geo.region:"United States"', + "user.geo.subdivision:California", + 'user.geo.city:"San Francisco" AND user.geo.country_code:USA AND user.geo.region:"United States" AND user.geo.subdivision:California', "sdk.name:sentry.javascript.react", "os.name:macOS", "os.version:15", @@ -761,6 +766,12 @@ def test_get_replays_user_filters(self): "seen_by_me:false", "user.email:[user2@example.com]", "!user.email:[username@example.com, user2@example.com]", + '!user.geo.city:"San Francisco"', + "!user.geo.country_code:USA", + '!user.geo.region:"United States"', + "!user.geo.subdivision:California", + 'user.geo.city:"San Francisco" AND !user.geo.country_code:USA', + '!user.geo.subdivision:California OR !user.geo.region:"United States"', ] for query in null_queries: response = self.client.get(self.url + f"?field=id&query={query}") From 7e8e1c2bc26c15035ae8238112c909db30e40e7d Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Fri, 16 May 2025 13:00:39 -0500 Subject: [PATCH 3/3] Add user geo marshaling --- src/sentry/replays/post_process.py | 28 ++++++++++++++++++- .../replays/test_organization_replay_index.py | 21 ++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/sentry/replays/post_process.py b/src/sentry/replays/post_process.py index b23303d64faed5..f2ab7f936479b4 100644 --- a/src/sentry/replays/post_process.py +++ b/src/sentry/replays/post_process.py @@ -32,12 +32,20 @@ class BrowserResponseType(TypedDict, total=False): version: str | None +class UserGeoResponseType(TypedDict, total=False): + city: str | None + country_code: str | None + region: str | None + subdivision: str | None + + class UserResponseType(TypedDict, total=False): id: str | None username: str | None email: str | None ip: str | None display_name: str | None + geo: UserGeoResponseType class OTAUpdatesResponseType(TypedDict, total=False): @@ -132,6 +140,12 @@ def generate_normalized_output(response: list[dict[str, Any]]) -> Generator[Repl "username": item.pop("user_username", None), "email": item.pop("user_email", None), "ip": item.pop("user_ip", None), + "geo": { + "city": item.pop("user_geo_city", None), + "country_code": item.pop("user_geo_country_code", None), + "region": item.pop("user_geo_region", None), + "subdivision": item.pop("user_geo_subdivision", None), + }, } ret_item["user"]["display_name"] = ( ret_item["user"]["username"] @@ -225,7 +239,19 @@ def _archived_row(replay_id: str, project_id: int) -> dict[str, Any]: "error_ids": [], "environment": None, "tags": [], - "user": {"id": "Archived Replay", "display_name": "Archived Replay"}, + "user": { + "id": "Archived Replay", + "display_name": "Archived Replay", + "username": None, + "email": None, + "ip": None, + "geo": { + "city": None, + "country_code": None, + "region": None, + "subdivision": None, + }, + }, "sdk": {"name": None, "version": None}, "os": {"name": None, "version": None}, "browser": {"name": None, "version": None}, diff --git a/tests/sentry/replays/test_organization_replay_index.py b/tests/sentry/replays/test_organization_replay_index.py index 3ec516ab359f19..6cc932d6a5ac29 100644 --- a/tests/sentry/replays/test_organization_replay_index.py +++ b/tests/sentry/replays/test_organization_replay_index.py @@ -235,12 +235,17 @@ def test_get_replays_browse_screen_fields(self): assert "urls" in response_data["data"][0] assert "user" in response_data["data"][0] - assert len(response_data["data"][0]["user"]) == 5 + assert len(response_data["data"][0]["user"]) == 6 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"] + assert "geo" in response_data["data"][0]["user"] + assert "city" in response_data["data"][0]["user"]["geo"] + assert "country_code" in response_data["data"][0]["user"]["geo"] + assert "region" in response_data["data"][0]["user"]["geo"] + assert "subdivision" in response_data["data"][0]["user"]["geo"] def test_get_replays_tags_field(self): """Test replay response with fields requested in production.""" @@ -1079,7 +1084,19 @@ def test_archived_records_are_null_fields(self): "error_ids": [], "environment": None, "tags": [], - "user": {"id": "Archived Replay", "display_name": "Archived Replay"}, + "user": { + "id": "Archived Replay", + "display_name": "Archived Replay", + "username": None, + "email": None, + "ip": None, + "geo": { + "city": None, + "country_code": None, + "region": None, + "subdivision": None, + }, + }, "sdk": {"name": None, "version": None}, "os": {"name": None, "version": None}, "browser": {"name": None, "version": None},