From 44f9bcc002808a84ee863e3c6eb435bde569ec03 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:13:48 -0400 Subject: [PATCH 01/21] `.fromJson` and `.toJson` added + move utils func --- pkgs/unified_analytics/lib/src/enums.dart | 10 +++++ pkgs/unified_analytics/lib/src/event.dart | 52 ++++++++++++++++++++--- pkgs/unified_analytics/lib/src/utils.dart | 20 --------- 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index 1bceab116..3c2873cfc 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -153,6 +153,16 @@ enum DashEvent { required this.description, this.toolOwner, }); + + /// This takes in the string label for a given [DashEvent] and returns the + /// enum for that string label. + static DashEvent getDashEventByLabel(String label) { + for (final event in DashEvent.values) { + if (event.label == label) return event; + } + + throw Exception('The event $label is not a valid DashEvent enum value'); + } } /// Officially-supported clients of this package as logical diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 5ad7d13ce..f6abee19b 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'enums.dart'; -import 'utils.dart'; final class Event { final DashEvent eventName; @@ -449,6 +448,11 @@ final class Event { if (maxRss != null) 'maxRss': maxRss, }; + /// Returns an instance of [Event] from the data in [json]. + Event.fromJson(Map json) + : eventName = DashEvent.getDashEventByLabel(json['eventName'] as String), + eventData = json['eventData'] as Map; + // TODO: eliasyishak, remove this or replace once we have a generic // timing event that can be used by potentially more than one DashTool Event.hotReloadTime({required int timeMs}) @@ -716,11 +720,49 @@ final class Event { other is Event && other.runtimeType == runtimeType && other.eventName == eventName && - compareEventData(other.eventData, eventData); - - @override - String toString() => jsonEncode({ + _compareEventData(other.eventData, eventData); + + /// Converts an instance of [Event] to JSON. + /// + /// Example for [Event.timing] converted to JSON below. + /// ```json + /// { + /// "eventName": "timing", + /// "eventData": { + /// "workflow": "my-work-flow", + /// "variableName": "my-variable", + /// "elapsedMilliseconds": 123, + /// "label": "my-label" + /// } + /// } + /// ``` + String toJson() => jsonEncode({ 'eventName': eventName.label, 'eventData': eventData, }); + + @override + String toString() => toJson(); + + /// Utility function to take in two maps [a] and [b] and compares them + /// to ensure that they have the same keys and values. + /// + /// Used for the equality operator. + bool _compareEventData(Map a, Map b) { + final keySetA = a.keys.toSet(); + final keySetB = b.keys.toSet(); + + // Ensure that the keys are the same for each object + if (keySetA.intersection(keySetB).length != keySetA.length || + keySetA.intersection(keySetB).length != keySetB.length) { + return false; + } + + // Ensure that each of the key's values are the same + for (final key in a.keys) { + if (a[key] != b[key]) return false; + } + + return true; + } } diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart index 55b59f350..6c644411e 100644 --- a/pkgs/unified_analytics/lib/src/utils.dart +++ b/pkgs/unified_analytics/lib/src/utils.dart @@ -36,26 +36,6 @@ bool checkDirectoryForWritePermissions(Directory directory) { return fileStat.modeString()[1] == 'w'; } -/// Utility function to take in two maps [a] and [b] and compares them -/// to ensure that they have the same keys and values -bool compareEventData(Map a, Map b) { - final keySetA = a.keys.toSet(); - final keySetB = b.keys.toSet(); - - // Ensure that the keys are the same for each object - if (keySetA.intersection(keySetB).length != keySetA.length || - keySetA.intersection(keySetB).length != keySetB.length) { - return false; - } - - // Ensure that each of the key's values are the same - for (final key in a.keys) { - if (a[key] != b[key]) return false; - } - - return true; -} - /// Format time as 'yyyy-MM-dd HH:mm:ss Z' where Z is the difference between the /// timezone of t and UTC formatted according to RFC 822. String formatDateTime(DateTime t) { From ce19f1267aeed473e3bc326bb9c554894496d54d Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:46:23 -0400 Subject: [PATCH 02/21] Use nullable static method to parse string json --- pkgs/unified_analytics/lib/src/event.dart | 43 +++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index f6abee19b..b2ee98db4 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -448,18 +448,12 @@ final class Event { if (maxRss != null) 'maxRss': maxRss, }; - /// Returns an instance of [Event] from the data in [json]. - Event.fromJson(Map json) - : eventName = DashEvent.getDashEventByLabel(json['eventName'] as String), - eventData = json['eventData'] as Map; - // TODO: eliasyishak, remove this or replace once we have a generic // timing event that can be used by potentially more than one DashTool Event.hotReloadTime({required int timeMs}) : eventName = DashEvent.hotReloadTime, eventData = {'timeMs': timeMs}; - // TODO: eliasyishak, add better dartdocs to explain each param /// Events to be sent for the Flutter Hot Runner. Event.hotRunnerInfo({ required String label, @@ -511,6 +505,7 @@ final class Event { if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs, }; + // TODO: eliasyishak, add better dartdocs to explain each param /// Event that is emitted periodically to report the number of times each lint /// has been enabled. /// @@ -712,6 +707,12 @@ final class Event { if (label != null) 'label': label, }; + /// Returns an instance of [Event] from the data in [json]. + Event._fromJsonMap(Map jsonMap) + : eventName = + DashEvent.getDashEventByLabel(jsonMap['eventName'] as String), + eventData = jsonMap['eventData'] as Map; + @override int get hashCode => Object.hash(eventName, jsonEncode(eventData)); @@ -765,4 +766,34 @@ final class Event { return true; } + + /// Returns a valid instance of [Event] if [json] follows the correct schema. + /// + /// Schema is below with the following notes: + /// - The `eventName` key must have a string value that exists in [DashEvent]. + /// - The `eventData` key must have a nested object as its value. + /// ```json + /// { + /// "eventName": "string-label", + /// "eventData": { + /// "myVar1": "value1", + /// "myVar2": 123 + /// } + /// } + /// ``` + static Event? fromJson(String json) { + try { + final jsonMap = jsonDecode(json) as Map; + return Event._fromJsonMap(jsonMap); + } on FormatException { + return null; + } on Exception { + // One possible Exception thrown is if the "eventName" value + // is not a valid DashEvent label + return null; + // ignore: avoid_catching_errors + } on TypeError { + return null; + } + } } From 9a55df9fc393f954edbb0034e9e54e8e2057170a Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:52:48 -0400 Subject: [PATCH 03/21] Update event.dart --- pkgs/unified_analytics/lib/src/event.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index b2ee98db4..ab704144e 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -454,6 +454,7 @@ final class Event { : eventName = DashEvent.hotReloadTime, eventData = {'timeMs': timeMs}; + // TODO: eliasyishak, add better dartdocs to explain each param /// Events to be sent for the Flutter Hot Runner. Event.hotRunnerInfo({ required String label, @@ -505,7 +506,6 @@ final class Event { if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs, }; - // TODO: eliasyishak, add better dartdocs to explain each param /// Event that is emitted periodically to report the number of times each lint /// has been enabled. /// From 1846d97f92ce22c60b91b46088e79bbe040ed79f Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:54:42 -0400 Subject: [PATCH 04/21] Fix test --- pkgs/unified_analytics/test/event_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 4bd3464ba..0868eecd9 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -556,7 +556,9 @@ void main() { test('Confirm all constructors were checked', () { var constructorCount = 0; for (var declaration in reflectClass(Event).declarations.keys) { - if (declaration.toString().contains('Event.')) constructorCount++; + // Count public constructors but omit private constructors + if (declaration.toString().contains('Event.') && + !declaration.toString().contains('Event._')) constructorCount++; } // Change this integer below if your PR either adds or removes From 03dcdbeee0a9fd7e937147e83ba82e982f696cb5 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:56:04 -0400 Subject: [PATCH 05/21] Tests for encoding/decoding json --- pkgs/unified_analytics/test/event_test.dart | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 0868eecd9..64a5c4f20 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -570,4 +570,47 @@ void main() { '`pkgs/unified_analytics/test/event_test.dart` ' 'to reflect the changes made'); }); + + test('Serializing event to json successful', () { + final event = Event.analyticsException( + workflow: 'workflow', + error: 'error', + description: 'description', + ); + + final expectedResult = '{"eventName":"analytics_exception",' + '"eventData":{"workflow":"workflow",' + '"error":"error",' + '"description":"description"}}'; + + expect(event.toJson(), expectedResult); + }); + + test('Deserializing string to event successful', () { + final eventJson = '{"eventName":"analytics_exception",' + '"eventData":{"workflow":"workflow",' + '"error":"error",' + '"description":"description"}}'; + + final eventConstructed = Event.fromJson(eventJson); + expect(eventConstructed, isNotNull); + eventConstructed!; + + expect(eventConstructed.eventName, DashEvent.analyticsException); + expect(eventConstructed.eventData, { + 'workflow': 'workflow', + 'error': 'error', + 'description': 'description', + }); + }); + + test('Deserializing string to event unsuccessful', () { + final eventJson = '{"eventName":"NOT_VALID_NAME",' + '"eventData":{"workflow":"workflow",' + '"error":"error",' + '"description":"description"}}'; + + final eventConstructed = Event.fromJson(eventJson); + expect(eventConstructed, isNull); + }); } From 7a7511ecc1521827f97b7a5a25b022a20106780b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:04:12 -0400 Subject: [PATCH 06/21] Store intersection in local var --- pkgs/unified_analytics/lib/src/event.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index ab704144e..39ff5072c 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -752,10 +752,11 @@ final class Event { bool _compareEventData(Map a, Map b) { final keySetA = a.keys.toSet(); final keySetB = b.keys.toSet(); + final intersection = keySetA.intersection(keySetB); // Ensure that the keys are the same for each object - if (keySetA.intersection(keySetB).length != keySetA.length || - keySetA.intersection(keySetB).length != keySetB.length) { + if (intersection.length != keySetA.length || + intersection.length != keySetB.length) { return false; } From 9d2bd10454ef4149c90df8334f6098360363a312 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:24:49 -0400 Subject: [PATCH 07/21] Remove exception thrown for nullable return --- pkgs/unified_analytics/lib/src/enums.dart | 4 ++-- pkgs/unified_analytics/lib/src/event.dart | 27 ++++++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index 3c2873cfc..33d0bbe75 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -156,12 +156,12 @@ enum DashEvent { /// This takes in the string label for a given [DashEvent] and returns the /// enum for that string label. - static DashEvent getDashEventByLabel(String label) { + static DashEvent? getDashEventByLabel(String label) { for (final event in DashEvent.values) { if (event.label == label) return event; } - throw Exception('The event $label is not a valid DashEvent enum value'); + return null; } } diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 39ff5072c..3f7c1db57 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -707,11 +707,9 @@ final class Event { if (label != null) 'label': label, }; - /// Returns an instance of [Event] from the data in [json]. - Event._fromJsonMap(Map jsonMap) - : eventName = - DashEvent.getDashEventByLabel(jsonMap['eventName'] as String), - eventData = jsonMap['eventData'] as Map; + /// Private constructor to be used when deserializing JSON into an instance + /// of [Event]. + Event._({required this.eventName, required this.eventData}); @override int get hashCode => Object.hash(eventName, jsonEncode(eventData)); @@ -785,13 +783,22 @@ final class Event { static Event? fromJson(String json) { try { final jsonMap = jsonDecode(json) as Map; - return Event._fromJsonMap(jsonMap); + + if (!jsonMap.containsKey('eventName') || + !jsonMap.containsKey('eventData')) { + return null; + } + + final dashEvent = + DashEvent.getDashEventByLabel(jsonMap['eventName'] as String); + if (dashEvent == null) { + return null; + } + final eventData = jsonMap['eventData'] as Map; + + return Event._(eventName: dashEvent, eventData: eventData); } on FormatException { return null; - } on Exception { - // One possible Exception thrown is if the "eventName" value - // is not a valid DashEvent label - return null; // ignore: avoid_catching_errors } on TypeError { return null; From 886b253f4a2b81424e5a8f56bed9309a67ee7dbb Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:42:54 -0400 Subject: [PATCH 08/21] Use conditionals to check for any type errors --- pkgs/unified_analytics/lib/src/event.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 3f7c1db57..40b685fd2 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -784,24 +784,28 @@ final class Event { try { final jsonMap = jsonDecode(json) as Map; + // Ensure the required keys are present if (!jsonMap.containsKey('eventName') || !jsonMap.containsKey('eventData')) { return null; } - final dashEvent = - DashEvent.getDashEventByLabel(jsonMap['eventName'] as String); + // Ensure the values for each key is the correct type + final eventName = jsonMap['eventName']; + final eventData = jsonMap['eventData']; + if (eventName is! String || eventData is! Map) { + return null; + } + + // Retrieve the correct DashEvent enum from the provided label + final dashEvent = DashEvent.getDashEventByLabel(eventName); if (dashEvent == null) { return null; } - final eventData = jsonMap['eventData'] as Map; return Event._(eventName: dashEvent, eventData: eventData); } on FormatException { return null; - // ignore: avoid_catching_errors - } on TypeError { - return null; } } } From 9df20b3403c27aa63e08cb841f8e0a74616d0c3c Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:49:10 -0400 Subject: [PATCH 09/21] Test case added to check for invalid eventData --- pkgs/unified_analytics/test/event_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart index 64a5c4f20..b0c155967 100644 --- a/pkgs/unified_analytics/test/event_test.dart +++ b/pkgs/unified_analytics/test/event_test.dart @@ -604,7 +604,7 @@ void main() { }); }); - test('Deserializing string to event unsuccessful', () { + test('Deserializing string to event unsuccessful for invalid eventName', () { final eventJson = '{"eventName":"NOT_VALID_NAME",' '"eventData":{"workflow":"workflow",' '"error":"error",' @@ -613,4 +613,12 @@ void main() { final eventConstructed = Event.fromJson(eventJson); expect(eventConstructed, isNull); }); + + test('Deserializing string to event unsuccessful for invalid eventData', () { + final eventJson = '{"eventName":"analytics_exception",' + '"eventData": "not_valid_event_data"}'; + + final eventConstructed = Event.fromJson(eventJson); + expect(eventConstructed, isNull); + }); } From f16b37a3bad5e1caef232f7a67365903a702159b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:49:49 -0400 Subject: [PATCH 10/21] Update CHANGELOG.md --- pkgs/unified_analytics/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index 1c827f0a7..7800b7703 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -4,6 +4,7 @@ - Get rid of `late` variables throughout implementation class, `AnalyticsImpl` - Any error events (`Event.analyticsException`) encountered within package will be sent when invoking `Analytics.close`; replacing `ErrorHandler` functionality - Exposing new method for `FakeAnalytics.sendPendingErrorEvents` to send error events on command +- Added `Event.fromJson` static method to generate instance of `Event` from JSON ## 5.8.8 From 9602053f60b88122ef72251d048c231d114aff79 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:13:10 -0400 Subject: [PATCH 11/21] Refactor `Event.fromJson` to use pattern matching --- pkgs/unified_analytics/lib/src/event.dart | 30 +++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 40b685fd2..0e1d9bae3 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -784,26 +784,20 @@ final class Event { try { final jsonMap = jsonDecode(json) as Map; - // Ensure the required keys are present - if (!jsonMap.containsKey('eventName') || - !jsonMap.containsKey('eventData')) { - return null; + // Ensure that eventName is a string and a valid label and + // eventData is a nested object + if (jsonMap + case { + 'eventName': final String eventName, + 'eventData': final Map eventData, + } when DashEvent.getDashEventByLabel(eventName) != null) { + return Event._( + eventName: DashEvent.getDashEventByLabel(eventName)!, + eventData: eventData, + ); } - // Ensure the values for each key is the correct type - final eventName = jsonMap['eventName']; - final eventData = jsonMap['eventData']; - if (eventName is! String || eventData is! Map) { - return null; - } - - // Retrieve the correct DashEvent enum from the provided label - final dashEvent = DashEvent.getDashEventByLabel(eventName); - if (dashEvent == null) { - return null; - } - - return Event._(eventName: dashEvent, eventData: eventData); + return null; } on FormatException { return null; } From 2d58a3db445ecc36bfe148ac22cd49e34e182ef4 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:32:29 -0400 Subject: [PATCH 12/21] Use package:collection for comparing eventData --- pkgs/unified_analytics/lib/src/event.dart | 29 ++++------------------- pkgs/unified_analytics/pubspec.yaml | 1 + 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 0e1d9bae3..e7fb006d6 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -4,11 +4,15 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; + import 'enums.dart'; final class Event { final DashEvent eventName; final Map eventData; + final DeepCollectionEquality _deepCollectionEquality = + const DeepCollectionEquality(); /// Event that is emitted whenever a user has opted in /// or out of the analytics collection. @@ -719,7 +723,7 @@ final class Event { other is Event && other.runtimeType == runtimeType && other.eventName == eventName && - _compareEventData(other.eventData, eventData); + _deepCollectionEquality.equals(other.eventData, eventData); /// Converts an instance of [Event] to JSON. /// @@ -743,29 +747,6 @@ final class Event { @override String toString() => toJson(); - /// Utility function to take in two maps [a] and [b] and compares them - /// to ensure that they have the same keys and values. - /// - /// Used for the equality operator. - bool _compareEventData(Map a, Map b) { - final keySetA = a.keys.toSet(); - final keySetB = b.keys.toSet(); - final intersection = keySetA.intersection(keySetB); - - // Ensure that the keys are the same for each object - if (intersection.length != keySetA.length || - intersection.length != keySetB.length) { - return false; - } - - // Ensure that each of the key's values are the same - for (final key in a.keys) { - if (a[key] != b[key]) return false; - } - - return true; - } - /// Returns a valid instance of [Event] if [json] follows the correct schema. /// /// Schema is below with the following notes: diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 02ac8d2ed..205d37b2a 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -12,6 +12,7 @@ environment: dependencies: clock: ^1.1.1 + collection: ^1.18.0 file: '>=6.1.4 <8.0.0' http: '>=0.13.5 <2.0.0' intl: '>=0.18.0 <0.20.0' From 760d9b7a7740887dff5b509f1423f4828721f33a Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:41:35 -0400 Subject: [PATCH 13/21] Use range for collection version --- pkgs/unified_analytics/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 205d37b2a..94c8f31fd 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -12,7 +12,7 @@ environment: dependencies: clock: ^1.1.1 - collection: ^1.18.0 + collection: '>=1.18.0 <2.0.0' file: '>=6.1.4 <8.0.0' http: '>=0.13.5 <2.0.0' intl: '>=0.18.0 <0.20.0' From 3977ae863c975f7eb4d6ca4a3975a0af8504b0a8 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:53:20 -0400 Subject: [PATCH 14/21] `fromLabel` static method renaming --- pkgs/unified_analytics/lib/src/enums.dart | 22 +++++-------------- pkgs/unified_analytics/lib/src/event.dart | 4 ++-- .../lib/src/survey_handler.dart | 11 ++++++---- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index 33d0bbe75..cb2829031 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:collection/collection.dart'; + /// The valid dash tool labels stored in the [DashTool] enum. List get validDashTools => DashTool.values.map((e) => e.label).toList()..sort(); @@ -156,13 +158,8 @@ enum DashEvent { /// This takes in the string label for a given [DashEvent] and returns the /// enum for that string label. - static DashEvent? getDashEventByLabel(String label) { - for (final event in DashEvent.values) { - if (event.label == label) return event; - } - - return null; - } + static DashEvent? fromLabel(String label) => + DashEvent.values.firstWhereOrNull((e) => e.label == label); } /// Officially-supported clients of this package as logical @@ -209,15 +206,8 @@ enum DashTool { /// This takes in the string label for a given [DashTool] and returns the /// enum for that string label. - static DashTool getDashToolByLabel(String label) { - for (final tool in DashTool.values) { - if (tool.label == label) return tool; - } - - throw Exception('The tool $label from the survey metadata file is not ' - 'a valid DashTool enum value\n' - 'Valid labels for dash tools: ${validDashTools.join(', ')}'); - } + static DashTool? fromLabel(String label) => + DashTool.values.firstWhereOrNull((e) => e.label == label); } /// Enumerate options for platforms supported. diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index e7fb006d6..9d201ec1a 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -771,9 +771,9 @@ final class Event { case { 'eventName': final String eventName, 'eventData': final Map eventData, - } when DashEvent.getDashEventByLabel(eventName) != null) { + } when DashEvent.fromLabel(eventName) != null) { return Event._( - eventName: DashEvent.getDashEventByLabel(eventName)!, + eventName: DashEvent.fromLabel(eventName)!, eventData: eventData, ); } diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index cc533475e..9271977b8 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:clock/clock.dart'; +import 'package:collection/collection.dart'; import 'package:file/file.dart'; import 'package:http/http.dart' as http; @@ -134,10 +135,12 @@ class Survey { samplingRate = json['samplingRate'] is String ? double.parse(json['samplingRate'] as String) : json['samplingRate'] as double, - excludeDashToolList = - (json['excludeDashTools'] as List).map((e) { - return DashTool.getDashToolByLabel(e as String); - }).toList(), + excludeDashToolList = (json['excludeDashTools'] as List) + .map((e) { + return DashTool.fromLabel(e as String); + }) + .whereType() + .toList(), conditionList = (json['conditions'] as List).map((e) { return Condition.fromJson(e as Map); }).toList(), From d71b224b9cec9075744b79c99e0c7c907184f0a1 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:59:41 -0400 Subject: [PATCH 15/21] Fix test by refactoring unrelated DashTool static method --- pkgs/unified_analytics/lib/src/enums.dart | 11 +++++++++-- pkgs/unified_analytics/lib/src/survey_handler.dart | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index cb2829031..d77b478a4 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -206,8 +206,15 @@ enum DashTool { /// This takes in the string label for a given [DashTool] and returns the /// enum for that string label. - static DashTool? fromLabel(String label) => - DashTool.values.firstWhereOrNull((e) => e.label == label); + static DashTool? fromLabel(String label) { + for (final tool in DashTool.values) { + if (tool.label == label) return tool; + } + + throw Exception('The tool $label from the survey metadata file is not ' + 'a valid DashTool enum value\n' + 'Valid labels for dash tools: ${validDashTools.join(', ')}'); + } } /// Enumerate options for platforms supported. diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 9271977b8..b74a33f2a 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:clock/clock.dart'; -import 'package:collection/collection.dart'; import 'package:file/file.dart'; import 'package:http/http.dart' as http; From bd2fae17c280b00cc8c24527bc83f74ce288e1f0 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:02:06 -0400 Subject: [PATCH 16/21] Remove `when` clause and check inside if statement --- pkgs/unified_analytics/lib/src/event.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 9d201ec1a..73831b187 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -771,9 +771,12 @@ final class Event { case { 'eventName': final String eventName, 'eventData': final Map eventData, - } when DashEvent.fromLabel(eventName) != null) { + }) { + final dashEvent = DashEvent.fromLabel(eventName); + if (dashEvent == null) return null; + return Event._( - eventName: DashEvent.fromLabel(eventName)!, + eventName: dashEvent, eventData: eventData, ); } From 7fd9071152d41d94536cf93f80a97fb65316fc65 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 29 Mar 2024 13:13:08 -0400 Subject: [PATCH 17/21] `_deepCollectionEquality` to global scope + nit fix --- pkgs/unified_analytics/lib/src/event.dart | 5 +++-- pkgs/unified_analytics/lib/src/survey_handler.dart | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 73831b187..e0abd7710 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -8,11 +8,12 @@ import 'package:collection/collection.dart'; import 'enums.dart'; +final DeepCollectionEquality _deepCollectionEquality = + const DeepCollectionEquality(); + final class Event { final DashEvent eventName; final Map eventData; - final DeepCollectionEquality _deepCollectionEquality = - const DeepCollectionEquality(); /// Event that is emitted whenever a user has opted in /// or out of the analytics collection. diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index b74a33f2a..a455be37a 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -135,9 +135,7 @@ class Survey { ? double.parse(json['samplingRate'] as String) : json['samplingRate'] as double, excludeDashToolList = (json['excludeDashTools'] as List) - .map((e) { - return DashTool.fromLabel(e as String); - }) + .map((e) => DashTool.fromLabel(e as String)) .whereType() .toList(), conditionList = (json['conditions'] as List).map((e) { From 8cacb0b86f033c653da32d3dacb8337897f07bde Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:05:32 -0400 Subject: [PATCH 18/21] Remove collection dep + schema in dartdoc + nit fixes --- pkgs/unified_analytics/lib/src/enums.dart | 6 +- pkgs/unified_analytics/lib/src/event.dart | 55 ++++++++----------- .../lib/src/survey_handler.dart | 1 - pkgs/unified_analytics/pubspec.yaml | 1 - 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index d77b478a4..390eaf220 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:collection/collection.dart'; - /// The valid dash tool labels stored in the [DashTool] enum. List get validDashTools => DashTool.values.map((e) => e.label).toList()..sort(); @@ -159,7 +157,7 @@ enum DashEvent { /// This takes in the string label for a given [DashEvent] and returns the /// enum for that string label. static DashEvent? fromLabel(String label) => - DashEvent.values.firstWhereOrNull((e) => e.label == label); + DashEvent.values.where((e) => e.label == label).firstOrNull; } /// Officially-supported clients of this package as logical @@ -206,7 +204,7 @@ enum DashTool { /// This takes in the string label for a given [DashTool] and returns the /// enum for that string label. - static DashTool? fromLabel(String label) { + static DashTool fromLabel(String label) { for (final tool in DashTool.values) { if (tool.label == label) return tool; } diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index e0abd7710..bd0bfd086 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -4,13 +4,8 @@ import 'dart:convert'; -import 'package:collection/collection.dart'; - import 'enums.dart'; -final DeepCollectionEquality _deepCollectionEquality = - const DeepCollectionEquality(); - final class Event { final DashEvent eventName; final Map eventData; @@ -459,7 +454,6 @@ final class Event { : eventName = DashEvent.hotReloadTime, eventData = {'timeMs': timeMs}; - // TODO: eliasyishak, add better dartdocs to explain each param /// Events to be sent for the Flutter Hot Runner. Event.hotRunnerInfo({ required String label, @@ -511,6 +505,7 @@ final class Event { if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs, }; + // TODO: eliasyishak, add better dartdocs to explain each param /// Event that is emitted periodically to report the number of times each lint /// has been enabled. /// @@ -724,22 +719,9 @@ final class Event { other is Event && other.runtimeType == runtimeType && other.eventName == eventName && - _deepCollectionEquality.equals(other.eventData, eventData); + _compareEventData(other.eventData, eventData); /// Converts an instance of [Event] to JSON. - /// - /// Example for [Event.timing] converted to JSON below. - /// ```json - /// { - /// "eventName": "timing", - /// "eventData": { - /// "workflow": "my-work-flow", - /// "variableName": "my-variable", - /// "elapsedMilliseconds": 123, - /// "label": "my-label" - /// } - /// } - /// ``` String toJson() => jsonEncode({ 'eventName': eventName.label, 'eventData': eventData, @@ -748,20 +730,27 @@ final class Event { @override String toString() => toJson(); + /// Utility function to take in two maps [a] and [b] and compares them + /// to ensure that they have the same keys and values + bool _compareEventData(Map a, Map b) { + final keySetA = a.keys.toSet(); + final keySetB = b.keys.toSet(); + + // Ensure that the keys are the same for each object + if (keySetA.intersection(keySetB).length != keySetA.length || + keySetA.intersection(keySetB).length != keySetB.length) { + return false; + } + + // Ensure that each of the key's values are the same + for (final key in a.keys) { + if (a[key] != b[key]) return false; + } + + return true; + } + /// Returns a valid instance of [Event] if [json] follows the correct schema. - /// - /// Schema is below with the following notes: - /// - The `eventName` key must have a string value that exists in [DashEvent]. - /// - The `eventData` key must have a nested object as its value. - /// ```json - /// { - /// "eventName": "string-label", - /// "eventData": { - /// "myVar1": "value1", - /// "myVar2": 123 - /// } - /// } - /// ``` static Event? fromJson(String json) { try { final jsonMap = jsonDecode(json) as Map; diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index a455be37a..c3c01b2eb 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -136,7 +136,6 @@ class Survey { : json['samplingRate'] as double, excludeDashToolList = (json['excludeDashTools'] as List) .map((e) => DashTool.fromLabel(e as String)) - .whereType() .toList(), conditionList = (json['conditions'] as List).map((e) { return Condition.fromJson(e as Map); diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index 94c8f31fd..02ac8d2ed 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -12,7 +12,6 @@ environment: dependencies: clock: ^1.1.1 - collection: '>=1.18.0 <2.0.0' file: '>=6.1.4 <8.0.0' http: '>=0.13.5 <2.0.0' intl: '>=0.18.0 <0.20.0' From 1a853351e34a9fcdf0a109657ec9f4dc9762fde5 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:07:19 -0400 Subject: [PATCH 19/21] Add'l context to `Event.fromJson` static method --- pkgs/unified_analytics/lib/src/event.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index bd0bfd086..78b94751b 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -751,6 +751,10 @@ final class Event { } /// Returns a valid instance of [Event] if [json] follows the correct schema. + /// + /// Common use case for this static method involves clients of this package + /// that have a client-server setup where the server sends events that the + /// client creates. static Event? fromJson(String json) { try { final jsonMap = jsonDecode(json) as Map; From b412b59ae3fdb352d782878aa37d26947532e870 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:08:22 -0400 Subject: [PATCH 20/21] Store intersection in local variable --- pkgs/unified_analytics/lib/src/event.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 78b94751b..8c8ec9e32 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -735,10 +735,11 @@ final class Event { bool _compareEventData(Map a, Map b) { final keySetA = a.keys.toSet(); final keySetB = b.keys.toSet(); + final intersection = keySetA.intersection(keySetB); // Ensure that the keys are the same for each object - if (keySetA.intersection(keySetB).length != keySetA.length || - keySetA.intersection(keySetB).length != keySetB.length) { + if (intersection.length != keySetA.length || + intersection.length != keySetB.length) { return false; } From e1d45d5d868d8f215eb94c95d13777fac0a1385b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:17:37 -0400 Subject: [PATCH 21/21] Refactor `DashTool.fromLabel` --- pkgs/unified_analytics/lib/src/enums.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart index 390eaf220..a62f88965 100644 --- a/pkgs/unified_analytics/lib/src/enums.dart +++ b/pkgs/unified_analytics/lib/src/enums.dart @@ -205,9 +205,8 @@ enum DashTool { /// This takes in the string label for a given [DashTool] and returns the /// enum for that string label. static DashTool fromLabel(String label) { - for (final tool in DashTool.values) { - if (tool.label == label) return tool; - } + final tool = DashTool.values.where((t) => t.label == label).firstOrNull; + if (tool != null) return tool; throw Exception('The tool $label from the survey metadata file is not ' 'a valid DashTool enum value\n'