Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serializing/deserializing methods for Event instances #251

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
44f9bcc
`.fromJson` and `.toJson` added + move utils func
eliasyishak Mar 13, 2024
ce19f12
Use nullable static method to parse string json
eliasyishak Mar 13, 2024
9a55df9
Update event.dart
eliasyishak Mar 14, 2024
1846d97
Fix test
eliasyishak Mar 14, 2024
65c353d
Merge remote-tracking branch 'upstream/main' into 250-helper-method-t…
eliasyishak Mar 25, 2024
03dcdbe
Tests for encoding/decoding json
eliasyishak Mar 25, 2024
7a7511e
Store intersection in local var
eliasyishak Mar 26, 2024
9d2bd10
Remove exception thrown for nullable return
eliasyishak Mar 26, 2024
886b253
Use conditionals to check for any type errors
eliasyishak Mar 26, 2024
9df20b3
Test case added to check for invalid eventData
eliasyishak Mar 26, 2024
f16b37a
Update CHANGELOG.md
eliasyishak Mar 26, 2024
9602053
Refactor `Event.fromJson` to use pattern matching
eliasyishak Mar 27, 2024
2d58a3d
Use package:collection for comparing eventData
eliasyishak Mar 27, 2024
760d9b7
Use range for collection version
eliasyishak Mar 27, 2024
3977ae8
`fromLabel` static method renaming
eliasyishak Mar 27, 2024
d71b224
Fix test by refactoring unrelated DashTool static method
eliasyishak Mar 27, 2024
bd2fae1
Remove `when` clause and check inside if statement
eliasyishak Mar 28, 2024
7fd9071
`_deepCollectionEquality` to global scope + nit fix
eliasyishak Mar 29, 2024
8cacb0b
Remove collection dep + schema in dartdoc + nit fixes
eliasyishak Mar 29, 2024
1a85335
Add'l context to `Event.fromJson` static method
eliasyishak Mar 29, 2024
b412b59
Store intersection in local variable
eliasyishak Mar 29, 2024
e1d45d5
Refactor `DashTool.fromLabel`
eliasyishak Mar 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/unified_analytics/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 8 additions & 1 deletion pkgs/unified_analytics/lib/src/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> get validDashTools =>
DashTool.values.map((e) => e.label).toList()..sort();
Expand Down Expand Up @@ -153,6 +155,11 @@ 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? fromLabel(String label) =>
DashEvent.values.firstWhereOrNull((e) => e.label == label);
}

/// Officially-supported clients of this package as logical
Expand Down Expand Up @@ -199,7 +206,7 @@ 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) {
static DashTool? fromLabel(String label) {
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
for (final tool in DashTool.values) {
if (tool.label == label) return tool;
}
Copy link
Contributor

@kenzieschmoll kenzieschmoll Mar 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use the simplified syntax from above for consistency
DashTool.values.where((t) => t.label == label).firstOrNull;

Expand Down
73 changes: 68 additions & 5 deletions pkgs/unified_analytics/lib/src/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

import 'dart:convert';

import 'package:collection/collection.dart';
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved

import 'enums.dart';
import 'utils.dart';

final class Event {
final DashEvent eventName;
final Map<String, Object?> eventData;
final DeepCollectionEquality _deepCollectionEquality =
const DeepCollectionEquality();
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved

/// Event that is emitted whenever a user has opted in
/// or out of the analytics collection.
Expand Down Expand Up @@ -708,6 +711,10 @@ final class Event {
if (label != null) 'label': label,
};

/// 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));

Expand All @@ -716,11 +723,67 @@ final class Event {
other is Event &&
other.runtimeType == runtimeType &&
other.eventName == eventName &&
compareEventData(other.eventData, eventData);

@override
String toString() => jsonEncode({
_deepCollectionEquality.equals(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();

/// 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
/// }
/// }
/// ```
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
static Event? fromJson(String json) {
try {
final jsonMap = jsonDecode(json) as Map<String, Object?>;

// 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<String, Object?> eventData,
}) {
final dashEvent = DashEvent.fromLabel(eventName);
if (dashEvent == null) return null;

return Event._(
eventName: dashEvent,
eventData: eventData,
);
}

return null;
} on FormatException {
return null;
}
}
}
10 changes: 6 additions & 4 deletions pkgs/unified_analytics/lib/src/survey_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ class Survey {
samplingRate = json['samplingRate'] is String
? double.parse(json['samplingRate'] as String)
: json['samplingRate'] as double,
excludeDashToolList =
(json['excludeDashTools'] as List<dynamic>).map((e) {
return DashTool.getDashToolByLabel(e as String);
}).toList(),
excludeDashToolList = (json['excludeDashTools'] as List<dynamic>)
.map((e) {
return DashTool.fromLabel(e as String);
})
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
.whereType<DashTool>()
eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
.toList(),
conditionList = (json['conditions'] as List<dynamic>).map((e) {
return Condition.fromJson(e as Map<String, dynamic>);
}).toList(),
Expand Down
20 changes: 0 additions & 20 deletions pkgs/unified_analytics/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object?> a, Map<String, Object?> 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;
}

eliasyishak marked this conversation as resolved.
Show resolved Hide resolved
/// 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) {
Expand Down
1 change: 1 addition & 0 deletions pkgs/unified_analytics/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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'
Expand Down
55 changes: 54 additions & 1 deletion pkgs/unified_analytics/test/event_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -568,4 +570,55 @@ 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 for invalid eventName', () {
final eventJson = '{"eventName":"NOT_VALID_NAME",'
'"eventData":{"workflow":"workflow",'
'"error":"error",'
'"description":"description"}}';

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);
});
}