Skip to content

Commit

Permalink
Fix: Add missing iOS contexts (#761)
Browse files Browse the repository at this point in the history
  • Loading branch information
marandaneto committed Apr 27, 2022
1 parent 2b4d628 commit 3af1dc8
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased:

* Fix: Add missing iOS contexts (#761)

## 6.5.1

- Update event contexts (#838)
Expand Down
6 changes: 5 additions & 1 deletion dart/lib/src/protocol/sentry_user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ class SentryUser {

/// Deserializes a [SentryUser] from JSON [Map].
factory SentryUser.fromJson(Map<String, dynamic> json) {
var extras = json['extras'];
if (extras != null) {
extras = Map<String, dynamic>.from(extras as Map);
}
return SentryUser(
id: json['id'],
username: json['username'],
email: json['email'],
ipAddress: json['ip_address'],
extras: json['extras'],
extras: extras,
);
}

Expand Down
25 changes: 25 additions & 0 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {

var infos = ["contexts": context]

if let tags = serializedScope["tags"] as? [String: String] {
infos["tags"] = tags
}
if let extra = serializedScope["extra"] as? [String: Any] {
infos["extra"] = extra
}
if let user = serializedScope["user"] as? [String: Any] {
infos["user"] = user
}
if let dist = serializedScope["dist"] as? String {
infos["dist"] = dist
}
if let environment = serializedScope["environment"] as? String {
infos["environment"] = environment
}
if let fingerprint = serializedScope["fingerprint"] as? [String] {
infos["fingerprint"] = fingerprint
}
if let level = serializedScope["level"] as? String {
infos["level"] = level
}
if let breadcrumbs = serializedScope["breadcrumbs"] as? [[String: Any]] {
infos["breadcrumbs"] = breadcrumbs
}

if let user = serializedScope["user"] as? [String: Any] {
infos["user"] = user
} else {
Expand Down
93 changes: 84 additions & 9 deletions flutter/lib/src/default_integrations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ class _LoadContextsIntegrationEventProcessor extends EventProcessor {
final infos = Map<String, dynamic>.from(
await (_channel.invokeMethod('loadContexts')),
);
if (infos['contexts'] != null) {
final contextsMap = infos['contexts'] as Map?;
if (contextsMap != null && contextsMap.isNotEmpty) {
final contexts = Contexts.fromJson(
Map<String, dynamic>.from(infos['contexts'] as Map),
Map<String, dynamic>.from(contextsMap),
);
final eventContexts = event.contexts.clone();

Expand All @@ -195,14 +196,87 @@ class _LoadContextsIntegrationEventProcessor extends EventProcessor {
event = event.copyWith(contexts: eventContexts);
}

final userMap = infos['user'];
if (event.user == null && userMap != null) {
final user = Map<String, dynamic>.from(userMap as Map);
final tagsMap = infos['tags'] as Map?;
if (tagsMap != null && tagsMap.isNotEmpty) {
final tags = event.tags ?? {};
final newTags = Map<String, String>.from(tagsMap);

for (final tag in newTags.entries) {
if (!tags.containsKey(tag.key)) {
tags[tag.key] = tag.value;
}
}
event = event.copyWith(tags: tags);
}

final extraMap = infos['extra'] as Map?;
if (extraMap != null && extraMap.isNotEmpty) {
final extras = event.extra ?? {};
final newExtras = Map<String, dynamic>.from(extraMap);

for (final extra in newExtras.entries) {
if (!extras.containsKey(extra.key)) {
extras[extra.key] = extra.value;
}
}
event = event.copyWith(extra: extras);
}

final userMap = infos['user'] as Map?;
if (event.user == null && userMap != null && userMap.isNotEmpty) {
final user = Map<String, dynamic>.from(userMap);
event = event.copyWith(user: SentryUser.fromJson(user));
}

if (infos['integrations'] != null) {
final integrations = List<String>.from(infos['integrations'] as List);
final distString = infos['dist'] as String?;
if (event.dist == null && distString != null) {
event = event.copyWith(dist: distString);
}

final environmentString = infos['environment'] as String?;
if (event.environment == null && environmentString != null) {
event = event.copyWith(environment: environmentString);
}

final fingerprintList = infos['fingerprint'] as List?;
if (fingerprintList != null && fingerprintList.isNotEmpty) {
final eventFingerprints = event.fingerprint ?? [];
final newFingerprint = List<String>.from(fingerprintList);

for (final fingerprint in newFingerprint) {
if (!eventFingerprints.contains(fingerprint)) {
eventFingerprints.add(fingerprint);
}
}
event = event.copyWith(fingerprint: eventFingerprints);
}

final levelString = infos['level'] as String?;
if (event.level == null && levelString != null) {
event = event.copyWith(level: SentryLevel.fromName(levelString));
}

final breadcrumbsList = infos['breadcrumbs'] as List?;
if (breadcrumbsList != null && breadcrumbsList.isNotEmpty) {
final breadcrumbs = event.breadcrumbs ?? [];
final newBreadcrumbs = List<Map>.from(breadcrumbsList);

for (final breadcrumb in newBreadcrumbs) {
final newBreadcrumb = Map<String, dynamic>.from(breadcrumb);
final crumb = Breadcrumb.fromJson(newBreadcrumb);
breadcrumbs.add(crumb);
}

breadcrumbs.sort((a, b) {
return a.timestamp.compareTo(b.timestamp);
});

event = event.copyWith(breadcrumbs: breadcrumbs);
}

final integrationsList = infos['integrations'] as List?;
if (integrationsList != null && integrationsList.isNotEmpty) {
final integrations = List<String>.from(integrationsList);
final sdk = event.sdk ?? _options.sdk;

for (final integration in integrations) {
Expand All @@ -214,8 +288,9 @@ class _LoadContextsIntegrationEventProcessor extends EventProcessor {
event = event.copyWith(sdk: sdk);
}

if (infos['package'] != null) {
final package = Map<String, String>.from(infos['package'] as Map);
final packageMap = infos['package'] as Map?;
if (packageMap != null && packageMap.isNotEmpty) {
final package = Map<String, String>.from(packageMap);
final sdk = event.sdk ?? _options.sdk;

final name = package['sdk_name'];
Expand Down
171 changes: 170 additions & 1 deletion flutter/test/load_contexts_integrations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ void main() {
SentryEvent getEvent(
{SdkVersion? sdk,
Map<String, String>? tags,
Map<String, dynamic>? extra,
SentryUser? user,
String? dist,
String? environment,
List<String>? fingerprint,
SentryLevel? level,
List<Breadcrumb>? breadcrumbs,
List<String> integrations = const ['EventIntegration'],
List<SentryPackage> packages = const [
SentryPackage('event-package', '2.0')
Expand All @@ -39,6 +46,13 @@ void main() {
packages: packages,
),
tags: tags,
extra: extra,
user: user,
dist: dist,
environment: environment,
fingerprint: fingerprint,
level: level,
breadcrumbs: breadcrumbs,
);
}

Expand Down Expand Up @@ -265,6 +279,143 @@ void main() {
expect(event?.tags?.containsKey('event.environment'), false);
},
);

test('should merge in tags from native without overriding flutter keys',
() async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(tags: {'key': 'flutter', 'key-a': 'flutter'});
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.tags?['key'], 'flutter');
expect(event?.tags?['key-a'], 'flutter');
expect(event?.tags?['key-b'], 'native');
});

test('should merge in extra from native without overriding flutter keys',
() async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(extra: {'key': 'flutter', 'key-a': 'flutter'});
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.extra?['key'], 'flutter');
expect(event?.extra?['key-a'], 'flutter');
expect(event?.extra?['key-b'], 'native');
});

test('should set user from native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent();
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.user?.id, '196E065A-AAF7-409A-9A6C-A81F40274CB9');
expect(event?.user?.username, 'fixture-username');
expect(event?.user?.email, 'fixture-email');
expect(event?.user?.ipAddress, 'fixture-ip_address');
expect(event?.user?.extras?['key'], 'value');
});

test('should not override user with native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(user: SentryUser(id: 'abc'));
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.user?.id, 'abc');
});

test('should set dist from native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent();
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.dist, 'fixture-dist');
});

test('should not override dist with native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(dist: 'abc');
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.dist, 'abc');
});

test('should set environment from native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent();
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.environment, 'fixture-environment');
});

test('should not override environment with native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(environment: 'abc');
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.environment, 'abc');
});

test('should merge in fingerprint from native without duplicating entries',
() async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(fingerprint: ['fingerprint-a', 'fingerprint-b']);
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.fingerprint, ['fingerprint-a', 'fingerprint-b']);
});

test('should set level from native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent();
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.level, SentryLevel.error);
});

test('should not override level with native', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final e = getEvent(level: SentryLevel.fatal);
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.level, SentryLevel.fatal);
});

test('should merge in breadcrumbs sorted by timestamp', () async {
final integration = fixture.getSut();
integration(fixture.hub, fixture.options);

final breadcrumb = Breadcrumb(
message: 'flutter-crumb',
timestamp: DateTime.fromMillisecondsSinceEpoch(1),
);
final e = getEvent(breadcrumbs: [breadcrumb]);
final event = await fixture.options.eventProcessors.first.apply(e);

expect(event?.breadcrumbs?.length, 2);
expect(event?.breadcrumbs?[0].message, 'native-crumb');
expect(event?.breadcrumbs?[1].message, 'flutter-crumb');
});
}

class Fixture {
Expand All @@ -288,7 +439,25 @@ class Fixture {
'runtime': {'name': 'RT1'},
'theme': 'material',
},
'user': {'id': '196E065A-AAF7-409A-9A6C-A81F40274CB9'}
'user': {
'id': '196E065A-AAF7-409A-9A6C-A81F40274CB9',
'username': 'fixture-username',
'email': 'fixture-email',
'ip_address': 'fixture-ip_address',
'extras': {'key': 'value'},
},
'tags': {'key-a': 'native', 'key-b': 'native'},
'extra': {'key-a': 'native', 'key-b': 'native'},
'dist': 'fixture-dist',
'environment': 'fixture-environment',
'fingerprint': ['fingerprint-a'],
'level': 'error',
'breadcrumbs': [
<String, dynamic>{
'timestamp': '1970-01-01T00:00:00.000Z',
'message': 'native-crumb',
}
]
}}) {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
called = true;
Expand Down

0 comments on commit 3af1dc8

Please sign in to comment.