Skip to content

Commit 3a476dc

Browse files
ryan953andrewshie-sentry
authored andcommitted
fix(replay): Expand replay user.geo tag if an object is found (#91885)
This is a followup on #91869 and #91854 to fix [JAVASCRIPT-30NR](https://sentry.io/organizations/sentry/issues/?project=11276&query=JAVASCRIPT-30NR) Now we're taking special care to unpack the `user.geo.*` fields and show them as individual tags: <img width="605" alt="SCR-20250519-kmta" src="https://github.com/user-attachments/assets/ebb74fbf-0a21-4f67-8f21-c4e6a2fcf16d" /> --- I tried using [`flattie`](https://www.npmjs.com/package/flattie) which is already part of our package.json but it was recursing into our arrays which we expect as the final values from the db. Dealing with that would require a manual recursive pass, which defeats the purpose.
1 parent 13fa144 commit 3a476dc

File tree

2 files changed

+27
-6
lines changed

2 files changed

+27
-6
lines changed

static/app/utils/replays/replayDataUtils.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import invariant from 'invariant';
2+
import isPlainObject from 'lodash/isPlainObject';
23
import {duration} from 'moment-timezone';
34

45
import {deviceNameMapper} from 'sentry/components/deviceName';
@@ -10,13 +11,23 @@ const defaultValues = {
1011
has_viewed: false,
1112
};
1213

13-
export function mapResponseToReplayRecord(apiResponse: any): ReplayRecord {
14-
// Marshal special fields into tags
15-
const user = Object.fromEntries(
16-
Object.entries(apiResponse.user)
14+
function mapUser(user: any): ReplayRecord['tags'] {
15+
return Object.fromEntries(
16+
Object.entries(user)
1717
.filter(([key, value]) => key !== 'display_name' && value)
18-
.map(([key, value]) => [`user.${key}`, [value]])
18+
.flatMap(([key, value]) => {
19+
if (isPlainObject(value)) {
20+
return Object.entries(value as Record<PropertyKey, unknown>).map(
21+
([subKey, subValue]) => [`user.${key}.${subKey}`, [String(subValue)]]
22+
);
23+
}
24+
return [[`user.${key}`, [String(value)]]];
25+
})
1926
);
27+
}
28+
29+
export function mapResponseToReplayRecord(apiResponse: any): ReplayRecord {
30+
// Marshal special fields into tags
2031
const unorderedTags: ReplayRecord['tags'] = {
2132
...apiResponse.tags,
2233
...(apiResponse.browser?.name ? {'browser.name': [apiResponse.browser.name]} : {}),
@@ -40,7 +51,7 @@ export function mapResponseToReplayRecord(apiResponse: any): ReplayRecord {
4051
...(apiResponse.os?.version ? {'os.version': [apiResponse.os.version]} : {}),
4152
...(apiResponse.sdk?.name ? {'sdk.name': [apiResponse.sdk.name]} : {}),
4253
...(apiResponse.sdk?.version ? {'sdk.version': [apiResponse.sdk.version]} : {}),
43-
...user,
54+
...mapUser(apiResponse.user ?? {}),
4455
};
4556

4657
// Stringify everything, so we don't try to render objects or something strange

static/app/views/replays/types.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import type {Duration} from 'moment-timezone';
22

3+
type Geo = Record<string, string>;
4+
// Geo could be this, not sure:
5+
// {
6+
// city?: string;
7+
// country_code?: string;
8+
// region?: string;
9+
// subdivision?: string;
10+
// };
11+
312
// Keep this in sync with the backend blueprint
413
// "ReplayRecord" is distinct from the common: "replay = new ReplayReader()"
514
export type ReplayRecord = {
@@ -78,6 +87,7 @@ export type ReplayRecord = {
7887
id: null | string;
7988
ip: null | string;
8089
username: null | string;
90+
geo?: Geo;
8191
};
8292
/**
8393
* The number of dead clicks associated with the replay.

0 commit comments

Comments
 (0)