diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt index 3dd549802f..4a4d4f7e20 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterReplayBreadcrumbConverter.kt @@ -30,7 +30,7 @@ class SentryFlutterReplayBreadcrumbConverter : DefaultReplayBreadcrumbConverter( "ui.click" -> newRRWebBreadcrumb(breadcrumb).apply { category = "ui.tap" - message = breadcrumb.data["path"] as String? + message = getTouchPathMessage(breadcrumb.data["path"]) } else -> { @@ -83,4 +83,38 @@ class SentryFlutterReplayBreadcrumbConverter : DefaultReplayBreadcrumbConverter( } return rrWebEvent } + + private fun getTouchPathMessage(maybePath: Any?): String? { + if (maybePath !is List<*>) { + return null + } + + if (maybePath.isEmpty()) { + return null + } + + val message = StringBuilder() + for (i in Math.min(3, maybePath.size - 1) downTo 0) { + val item = maybePath[i] + if (item !is Map<*, *>) { + continue + } + + message.append(item["element"] ?: "?") + + var identifier = item["label"] ?: item["name"] + if (identifier is String && identifier.isNotEmpty()) { + if (identifier.length > 20) { + identifier = identifier.substring(0, 17) + "..." + } + message.append("(").append(identifier).append(")") + } + + if (i > 0) { + message.append(" > ") + } + } + + return message.toString() + } } diff --git a/flutter/ios/Classes/SentryFlutterReplayBreadcrumbConverter.m b/flutter/ios/Classes/SentryFlutterReplayBreadcrumbConverter.m index 75b073de82..bde889b6bf 100644 --- a/flutter/ios/Classes/SentryFlutterReplayBreadcrumbConverter.m +++ b/flutter/ios/Classes/SentryFlutterReplayBreadcrumbConverter.m @@ -38,7 +38,7 @@ - (instancetype _Nonnull)init { if ([breadcrumb.category isEqualToString:@"ui.click"]) { return [self convertFrom:breadcrumb withCategory:@"ui.tap" - andMessage:breadcrumb.data[@"path"]]; + andMessage:[self getTouchPathMessage:breadcrumb.data[@"path"]]]; } SentryRRWebEvent *nativeBreadcrumb = @@ -112,6 +112,42 @@ - (NSDate *_Nonnull)dateFrom:(NSNumber *_Nonnull)timestamp { return [NSDate dateWithTimeIntervalSince1970:(timestamp.doubleValue / 1000)]; } +- (NSString * _Nullable)getTouchPathMessage:(id _Nullable)maybePath { + if (![maybePath isKindOfClass:[NSArray class]]) { + return nil; + } + + NSArray *path = (NSArray *)maybePath; + if (path.count == 0) { + return nil; + } + + NSMutableString *message = [NSMutableString string]; + for (NSInteger i = MIN(3, path.count - 1); i >= 0; i--) { + id item = path[i]; + if (![item isKindOfClass:[NSDictionary class]]) { + continue; + } + + NSDictionary *itemDict = (NSDictionary *)item; + [message appendString:itemDict[@"element"] ?: @"?"]; + + id identifier = itemDict[@"label"] ?: itemDict[@"name"]; + if ([identifier isKindOfClass:[NSString class]] && [(NSString *)identifier length] > 0) { + NSString *identifierStr = (NSString *)identifier; + if (identifierStr.length > 20) { + identifierStr = [[identifierStr substringToIndex:17] stringByAppendingString:@"..."]; + } + [message appendFormat:@"(%@)", identifierStr]; + } + + if (i > 0) { + [message appendString:@" > "]; + } + } + + return message.length > 0 ? message : nil; +} @end #endif