Skip to content

Commit 254b772

Browse files
fix(replay): Add missing properties to android nav breadcrumbs (#3942)
1 parent f96cf53 commit 254b772

File tree

3 files changed

+185
-13
lines changed

3 files changed

+185
-13
lines changed

RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryReplayBreadcrumbConverterTest.kt

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.sentry.rnsentryandroidtester
22

33
import io.sentry.Breadcrumb
4+
import io.sentry.SentryLevel
45
import io.sentry.react.RNSentryReplayBreadcrumbConverter
56
import io.sentry.rrweb.RRWebBreadcrumbEvent
7+
import io.sentry.rrweb.RRWebEventType
68
import org.junit.Assert.assertEquals
79
import org.junit.Test
810
import org.junit.runner.RunWith
@@ -12,26 +14,63 @@ import org.junit.runners.JUnit4
1214
class RNSentryReplayBreadcrumbConverterTest {
1315

1416
@Test
15-
fun testConvertForegroundBreadcrumb() {
17+
fun convertNavigationBreadcrumb() {
18+
val converter = RNSentryReplayBreadcrumbConverter()
19+
val testBreadcrumb = Breadcrumb()
20+
testBreadcrumb.level = SentryLevel.INFO
21+
testBreadcrumb.type = "navigation"
22+
testBreadcrumb.category = "navigation"
23+
testBreadcrumb.setData("from", "HomeScreen")
24+
testBreadcrumb.setData("to", "ProfileScreen")
25+
val actual = converter.convert(testBreadcrumb) as RRWebBreadcrumbEvent
26+
27+
assertRRWebBreadcrumbDefaults(actual)
28+
assertEquals(SentryLevel.INFO, actual.level)
29+
assertEquals("navigation", actual.category)
30+
assertEquals("HomeScreen", actual.data?.get("from"))
31+
assertEquals("ProfileScreen", actual.data?.get("to"))
32+
}
33+
34+
@Test
35+
fun convertNavigationBreadcrumbWithOnlyTo() {
36+
val converter = RNSentryReplayBreadcrumbConverter()
37+
val testBreadcrumb = Breadcrumb()
38+
testBreadcrumb.level = SentryLevel.INFO
39+
testBreadcrumb.type = "navigation"
40+
testBreadcrumb.category = "navigation"
41+
testBreadcrumb.setData("to", "ProfileScreen")
42+
val actual = converter.convert(testBreadcrumb) as RRWebBreadcrumbEvent
43+
44+
assertRRWebBreadcrumbDefaults(actual)
45+
assertEquals(SentryLevel.INFO, actual.level)
46+
assertEquals("navigation", actual.category)
47+
assertEquals(null, actual.data?.get("from"))
48+
assertEquals("ProfileScreen", actual.data?.get("to"))
49+
}
50+
51+
@Test
52+
fun convertForegroundBreadcrumb() {
1653
val converter = RNSentryReplayBreadcrumbConverter()
1754
val testBreadcrumb = Breadcrumb()
1855
testBreadcrumb.type = "navigation"
1956
testBreadcrumb.category = "app.lifecycle"
2057
testBreadcrumb.setData("state", "foreground");
2158
val actual = converter.convert(testBreadcrumb) as RRWebBreadcrumbEvent
2259

60+
assertRRWebBreadcrumbDefaults(actual)
2361
assertEquals("app.foreground", actual.category)
2462
}
2563

2664
@Test
27-
fun testConvertBackgroundBreadcrumb() {
65+
fun convertBackgroundBreadcrumb() {
2866
val converter = RNSentryReplayBreadcrumbConverter()
2967
val testBreadcrumb = Breadcrumb()
3068
testBreadcrumb.type = "navigation"
3169
testBreadcrumb.category = "app.lifecycle"
3270
testBreadcrumb.setData("state", "background");
3371
val actual = converter.convert(testBreadcrumb) as RRWebBreadcrumbEvent
3472

73+
assertRRWebBreadcrumbDefaults(actual)
3574
assertEquals("app.background", actual.category)
3675
}
3776

@@ -53,6 +92,32 @@ class RNSentryReplayBreadcrumbConverterTest {
5392
assertEquals(null, actual)
5493
}
5594

95+
@Test
96+
fun convertTouchBreadcrumb() {
97+
val converter = RNSentryReplayBreadcrumbConverter()
98+
val testBreadcrumb = Breadcrumb()
99+
testBreadcrumb.level = SentryLevel.INFO
100+
testBreadcrumb.type = "user"
101+
testBreadcrumb.category = "touch"
102+
testBreadcrumb.message = "this won't be used for replay"
103+
testBreadcrumb.setData(
104+
"path",
105+
arrayListOf(mapOf(
106+
"element" to "element4",
107+
"file" to "file4")))
108+
val actual = converter.convert(testBreadcrumb) as RRWebBreadcrumbEvent
109+
110+
assertRRWebBreadcrumbDefaults(actual)
111+
assertEquals(SentryLevel.INFO, actual.level)
112+
assertEquals("ui.tap", actual.category)
113+
assertEquals(1, actual.data?.keys?.size)
114+
assertEquals(
115+
arrayListOf(mapOf(
116+
"element" to "element4",
117+
"file" to "file4")),
118+
actual.data?.get("path"))
119+
}
120+
56121
@Test
57122
fun doesNotConvertNullPath() {
58123
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(null)
@@ -96,4 +161,10 @@ class RNSentryReplayBreadcrumbConverterTest {
96161
mapOf("name" to "name7")))
97162
assertEquals("label5(element5, file5) > label4(file4) > label3(element3) > label2", actual)
98163
}
164+
165+
private fun assertRRWebBreadcrumbDefaults(actual: RRWebBreadcrumbEvent) {
166+
assertEquals("default", actual.breadcrumbType)
167+
assertEquals(actual.breadcrumbTimestamp * 1000, actual.timestamp.toDouble(), 0.05)
168+
assert(actual.breadcrumbTimestamp > 0)
169+
}
99170
}

RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayBreadcrumbConverterTests.swift

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,57 @@
11
import XCTest
2+
import Sentry
23

34
final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
45

6+
func testConvertNavigationBreadcrumb() {
7+
let converter = RNSentryReplayBreadcrumbConverter()
8+
let testBreadcrumb = Breadcrumb()
9+
testBreadcrumb.timestamp = Date()
10+
testBreadcrumb.level = .info
11+
testBreadcrumb.type = "navigation"
12+
testBreadcrumb.category = "navigation"
13+
testBreadcrumb.data = [
14+
"from": "HomeScreen",
15+
"to": "ProfileScreen",
16+
]
17+
let actual = converter.convert(from: testBreadcrumb)
18+
19+
XCTAssertNotNil(actual)
20+
let event = actual!.serialize()
21+
let data = event["data"] as! [String: Any?]
22+
let payload = data["payload"] as! [String: Any?]
23+
let payloadData = payload["data"] as! [String: Any?]
24+
assertRRWebBreadcrumbDefaults(actual: event)
25+
XCTAssertEqual("info", payload["level"] as! String)
26+
XCTAssertEqual("navigation", payload["category"] as! String)
27+
XCTAssertEqual("HomeScreen", payloadData["from"] as! String)
28+
XCTAssertEqual("ProfileScreen", payloadData["to"] as! String)
29+
}
30+
31+
func testConvertNavigationBreadcrumbWithOnlyTo() {
32+
let converter = RNSentryReplayBreadcrumbConverter()
33+
let testBreadcrumb = Breadcrumb()
34+
testBreadcrumb.timestamp = Date()
35+
testBreadcrumb.level = .info
36+
testBreadcrumb.type = "navigation"
37+
testBreadcrumb.category = "navigation"
38+
testBreadcrumb.data = [
39+
"to": "ProfileScreen",
40+
]
41+
let actual = converter.convert(from: testBreadcrumb)
42+
43+
XCTAssertNotNil(actual)
44+
let event = actual!.serialize()
45+
let data = event["data"] as! [String: Any?]
46+
let payload = data["payload"] as! [String: Any?]
47+
let payloadData = payload["data"] as! [String: Any?]
48+
assertRRWebBreadcrumbDefaults(actual: event)
49+
XCTAssertEqual("info", payload["level"] as! String)
50+
XCTAssertEqual("navigation", payload["category"] as! String)
51+
XCTAssertNil(payloadData["from"] ?? nil)
52+
XCTAssertEqual("ProfileScreen", payloadData["to"] as! String)
53+
}
54+
555
func testConvertForegroundBreadcrumb() {
656
let converter = RNSentryReplayBreadcrumbConverter()
757
let testBreadcrumb = Breadcrumb()
@@ -11,8 +61,10 @@ final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
1161
let actual = converter.convert(from: testBreadcrumb)
1262

1363
XCTAssertNotNil(actual)
14-
let data = actual!.serialize()["data"] as! [String: Any?];
64+
let event = actual!.serialize()
65+
let data = event["data"] as! [String: Any?]
1566
let payload = data["payload"] as! [String: Any?];
67+
assertRRWebBreadcrumbDefaults(actual: event)
1668
XCTAssertEqual(payload["category"] as! String, "app.foreground")
1769
}
1870

@@ -25,8 +77,10 @@ final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
2577
let actual = converter.convert(from: testBreadcrumb)
2678

2779
XCTAssertNotNil(actual)
28-
let data = actual!.serialize()["data"] as! [String: Any?];
80+
let event = actual!.serialize()
81+
let data = event["data"] as! [String: Any?]
2982
let payload = data["payload"] as! [String: Any?];
83+
assertRRWebBreadcrumbDefaults(actual: event)
3084
XCTAssertEqual(payload["category"] as! String, "app.background")
3185
}
3286

@@ -46,6 +100,36 @@ final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
46100
XCTAssertNil(actual)
47101
}
48102

103+
func testConvertTouchBreadcrumb() {
104+
let converter = RNSentryReplayBreadcrumbConverter()
105+
let testBreadcrumb = Breadcrumb()
106+
testBreadcrumb.timestamp = Date()
107+
testBreadcrumb.level = .info
108+
testBreadcrumb.type = "user"
109+
testBreadcrumb.category = "touch"
110+
testBreadcrumb.message = "this won't be used for replay"
111+
testBreadcrumb.data = [
112+
"path": [
113+
["element": "element4", "file": "file4"]
114+
]
115+
]
116+
let actual = converter.convert(from: testBreadcrumb)
117+
118+
XCTAssertNotNil(actual)
119+
let event = actual!.serialize()
120+
let data = event["data"] as! [String: Any?]
121+
let payload = data["payload"] as! [String: Any?]
122+
let payloadData = payload["data"] as! [String: Any?]
123+
assertRRWebBreadcrumbDefaults(actual: event)
124+
XCTAssertEqual("info", payload["level"] as! String)
125+
XCTAssertEqual("ui.tap", payload["category"] as! String)
126+
XCTAssertEqual(1, payloadData.keys.count)
127+
XCTAssertEqual([[
128+
"element": "element4",
129+
"file": "file4"
130+
]], payloadData["path"] as! [[String: String]])
131+
}
132+
49133
func testTouchMessageReturnsNilOnEmptyArray() throws {
50134
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: [])
51135
XCTAssertEqual(actual, nil);
@@ -88,4 +172,12 @@ final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
88172
XCTAssertEqual(actual, "label5(element5, file5) > label4(file4) > label3(element3) > label2");
89173
}
90174

175+
private func assertRRWebBreadcrumbDefaults(actual: [String: Any?]) {
176+
let data = actual["data"] as! [String: Any?]
177+
let payload = data["payload"] as! [String: Any?]
178+
XCTAssertEqual("default", payload["type"] as! String)
179+
XCTAssertEqual((payload["timestamp"] as! Double) * 1000.0, Double(actual["timestamp"] as! Int), accuracy: 1.0)
180+
XCTAssertTrue(payload["timestamp"] as! Double > 0.0)
181+
}
182+
91183
}

android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ public RNSentryReplayBreadcrumbConverter() {
3838
return convertTouchBreadcrumb(breadcrumb);
3939
}
4040
if (breadcrumb.getCategory().equals("navigation")) {
41-
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
42-
rrWebBreadcrumb.setCategory(breadcrumb.getCategory());
43-
rrWebBreadcrumb.setData(breadcrumb.getData());
44-
return rrWebBreadcrumb;
41+
return convertNavigationBreadcrumb(breadcrumb);
4542
}
4643
if (breadcrumb.getCategory().equals("xhr")) {
4744
return convertNetworkBreadcrumb(breadcrumb);
@@ -60,6 +57,14 @@ public RNSentryReplayBreadcrumbConverter() {
6057
return nativeBreadcrumb;
6158
}
6259

60+
@TestOnly
61+
public @NotNull RRWebEvent convertNavigationBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
62+
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
63+
rrWebBreadcrumb.setCategory(breadcrumb.getCategory());
64+
setRRWebEventDefaultsFrom(rrWebBreadcrumb, breadcrumb);
65+
return rrWebBreadcrumb;
66+
}
67+
6368
@TestOnly
6469
public @NotNull RRWebEvent convertTouchBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
6570
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
@@ -69,11 +74,7 @@ public RNSentryReplayBreadcrumbConverter() {
6974
rrWebBreadcrumb.setMessage(RNSentryReplayBreadcrumbConverter
7075
.getTouchPathMessage(breadcrumb.getData("path")));
7176

72-
rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
73-
rrWebBreadcrumb.setData(breadcrumb.getData());
74-
rrWebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime());
75-
rrWebBreadcrumb.setBreadcrumbTimestamp(breadcrumb.getTimestamp().getTime() / 1000.0);
76-
rrWebBreadcrumb.setBreadcrumbType("default");
77+
setRRWebEventDefaultsFrom(rrWebBreadcrumb, breadcrumb);
7778
return rrWebBreadcrumb;
7879
}
7980

@@ -175,4 +176,12 @@ public RNSentryReplayBreadcrumbConverter() {
175176
rrWebSpanEvent.setData(data);
176177
return rrWebSpanEvent;
177178
}
179+
180+
private void setRRWebEventDefaultsFrom(final @NotNull RRWebBreadcrumbEvent rrWebBreadcrumb, final @NotNull Breadcrumb breadcrumb) {
181+
rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
182+
rrWebBreadcrumb.setData(breadcrumb.getData());
183+
rrWebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime());
184+
rrWebBreadcrumb.setBreadcrumbTimestamp(breadcrumb.getTimestamp().getTime() / 1000.0);
185+
rrWebBreadcrumb.setBreadcrumbType("default");
186+
}
178187
}

0 commit comments

Comments
 (0)