From 3e76c3f9978b04357014228761b766420e9147f2 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:42:38 -0500 Subject: [PATCH 01/16] Adding duration for start and end times + record count --- .../unified_analytics/lib/src/log_handler.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index 872fe3139e..e348ee09fd 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -16,9 +16,15 @@ class LogFileStats { /// The oldest timestamp in the log file final DateTime startDateTime; + /// Number of minutes from [startDateTime] to [DateTime.now()] + final int minsFromStartDateTime; + /// The latest timestamp in the log file final DateTime endDateTime; + /// Number of minutes from [endDateTime] to [DateTime.now()] + final int minsFromEndDateTime; + /// The number of unique session ids found in the log file final int sessionCount; @@ -28,22 +34,31 @@ class LogFileStats { /// The number of unique tools found in the log file final int toolCount; + /// Total number of records in the log file + final int recordCount; + /// Contains the data from the [LogHandler.logFileStats] method const LogFileStats({ required this.startDateTime, + required this.minsFromStartDateTime, required this.endDateTime, + required this.minsFromEndDateTime, required this.sessionCount, required this.flutterChannelCount, required this.toolCount, + required this.recordCount, }); @override String toString() => jsonEncode({ 'startDateTime': startDateTime.toString(), + 'minsFromStartDateTime': minsFromStartDateTime, 'endDateTime': endDateTime.toString(), + 'minsFromEndDateTime': minsFromEndDateTime, 'sessionCount': sessionCount, 'flutterChannelCount': flutterChannelCount, 'toolCount': toolCount, + 'recordCount': recordCount, }); } @@ -105,10 +120,13 @@ class LogHandler { return LogFileStats( startDateTime: startDateTime, + minsFromStartDateTime: DateTime.now().difference(startDateTime).inMinutes, endDateTime: endDateTime, + minsFromEndDateTime: DateTime.now().difference(endDateTime).inMinutes, sessionCount: counter['sessions']!.length, flutterChannelCount: counter['flutter_channel']!.length, toolCount: counter['tool']!.length, + recordCount: records.length, ); } From 14422b88950c614771ed8750db85516b5cda5aaf Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:44:44 -0500 Subject: [PATCH 02/16] Update USAGE_GUIDE.md --- pkgs/unified_analytics/USAGE_GUIDE.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkgs/unified_analytics/USAGE_GUIDE.md b/pkgs/unified_analytics/USAGE_GUIDE.md index 6321457da7..5dc0aebc08 100644 --- a/pkgs/unified_analytics/USAGE_GUIDE.md +++ b/pkgs/unified_analytics/USAGE_GUIDE.md @@ -143,18 +143,24 @@ print(analytics.logFileStats()); // Prints out the below // { -// "startDateTime": "2023-02-08 15:07:10.293728", -// "endDateTime": "2023-02-08 15:07:10.299678", -// "sessionCount": 1, +// "startDateTime": "2023-02-22 15:23:24.410921", +// "minsFromStartDateTime": 18858, +// "endDateTime": "2023-03-02 17:24:59.206405", +// "minsFromEndDateTime": 7217, +// "sessionCount": 5, // "flutterChannelCount": 1, -// "toolCount": 1 +// "toolCount": 1, +// "recordCount": 18 // } ``` Explanation of the each key above - startDateTime: the earliest event that was sent +- minsFromStartDateTime: the number of minutes elapsed since the earliest message - endDateTime: the latest, most recent event that was sent +- minsFromEndDateTime: the number of minutes elapsed since the latest message - sessionCount: count of sessions; sessions have a minimum time of 30 minutes - flutterChannelCount: count of flutter channels (can be 0 if developer is a Dart dev only) - toolCount: count of the Dart and Flutter tools sending analytics +- recordCount: count of the total number of events in the log file \ No newline at end of file From 52b6bdaa049a36655802e0e19719051934c073f6 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 8 Mar 2023 18:26:13 -0500 Subject: [PATCH 03/16] Update to include counts for each event in LogFileStats --- pkgs/unified_analytics/USAGE_GUIDE.md | 20 ++++++---- .../lib/src/log_handler.dart | 37 ++++++++++++++++++- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/pkgs/unified_analytics/USAGE_GUIDE.md b/pkgs/unified_analytics/USAGE_GUIDE.md index 5dc0aebc08..2265171a32 100644 --- a/pkgs/unified_analytics/USAGE_GUIDE.md +++ b/pkgs/unified_analytics/USAGE_GUIDE.md @@ -144,13 +144,18 @@ print(analytics.logFileStats()); // Prints out the below // { // "startDateTime": "2023-02-22 15:23:24.410921", -// "minsFromStartDateTime": 18858, -// "endDateTime": "2023-03-02 17:24:59.206405", -// "minsFromEndDateTime": 7217, -// "sessionCount": 5, -// "flutterChannelCount": 1, +// "minsFromStartDateTime": 20319, +// "endDateTime": "2023-03-08 15:46:36.318211", +// "minsFromEndDateTime": 136, +// "sessionCount": 7, +// "flutterChannelCount": 2, // "toolCount": 1, -// "recordCount": 18 +// "recordCount": 23, +// "eventCount": { +// "hot_reload_time": 16, +// "analytics_collection_enabled": 7, +// ... scales up with number of events +// } // } ``` @@ -163,4 +168,5 @@ Explanation of the each key above - sessionCount: count of sessions; sessions have a minimum time of 30 minutes - flutterChannelCount: count of flutter channels (can be 0 if developer is a Dart dev only) - toolCount: count of the Dart and Flutter tools sending analytics -- recordCount: count of the total number of events in the log file \ No newline at end of file +- recordCount: count of the total number of events in the log file +- eventCount: counts each unique event and how many times they occurred in the log file \ No newline at end of file diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index e348ee09fd..2314e4aff2 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -34,6 +34,10 @@ class LogFileStats { /// The number of unique tools found in the log file final int toolCount; + /// The map containing all of the events in the file along with + /// how many times they have occured + final Map eventCount; + /// Total number of records in the log file final int recordCount; @@ -47,6 +51,7 @@ class LogFileStats { required this.flutterChannelCount, required this.toolCount, required this.recordCount, + required this.eventCount, }); @override @@ -59,6 +64,7 @@ class LogFileStats { 'flutterChannelCount': flutterChannelCount, 'toolCount': toolCount, 'recordCount': recordCount, + 'eventCount': eventCount, }); } @@ -104,18 +110,28 @@ class LogHandler { final DateTime startDateTime = records.first.localTime; final DateTime endDateTime = records.last.localTime; - // Collection of unique sessions + // Map with counters for user properties final Map> counter = >{ 'sessions': {}, 'flutter_channel': {}, 'tool': {}, }; + + // Map of counters for each event + final Map eventCount = {}; for (LogItem record in records) { counter['sessions']!.add(record.sessionId); counter['tool']!.add(record.tool); if (record.flutterChannel != null) { counter['flutter_channel']!.add(record.flutterChannel!); } + + // Count each event, if it doesn't exist in the [eventCount] + // it will be added first + if (!eventCount.containsKey(record.eventName)) { + eventCount[record.eventName] = 0; + } + eventCount[record.eventName] = eventCount[record.eventName]! + 1; } return LogFileStats( @@ -126,6 +142,7 @@ class LogHandler { sessionCount: counter['sessions']!.length, flutterChannelCount: counter['flutter_channel']!.length, toolCount: counter['tool']!.length, + eventCount: eventCount, recordCount: records.length, ); } @@ -153,6 +170,7 @@ class LogHandler { /// Data class for each record persisted on the client's machine class LogItem { + final String eventName; final int sessionId; final String? flutterChannel; final String host; @@ -162,6 +180,7 @@ class LogItem { final DateTime localTime; LogItem({ + required this.eventName, required this.sessionId, this.flutterChannel, required this.host, @@ -218,12 +237,21 @@ class LogItem { /// } /// ``` static LogItem? fromRecord(Map record) { - if (!record.containsKey('user_properties')) return null; + if (!record.containsKey('user_properties') || + !record.containsKey('events')) { + return null; + } // Using a try/except here to parse out the fields if possible, // if not, it will quietly return null and won't get processed // downstream try { + // Parse out values from the top level key = 'events' and return + // a map for the one event in the value + final Map eventProp = + ((record['events']! as List).first as Map); + final String eventName = eventProp['name'] as String; + // Parse the data out of the `user_properties` value final Map userProps = record['user_properties'] as Map; @@ -248,6 +276,10 @@ class LogItem { // indicates the record is malformed; note that `flutter_version` // and `flutter_channel` are nullable fields in the log file final List values = [ + // Values associated with the top level key = 'events' + eventName, + + // Values associated with the top level key = 'events' sessionId, host, dartVersion, @@ -262,6 +294,7 @@ class LogItem { final DateTime localTime = DateTime.parse(localTimeString!); return LogItem( + eventName: eventName, sessionId: sessionId!, flutterChannel: flutterChannel, host: host!, From 9e5a5b369e5e2dab76d739f36695d53c662bdfc3 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 8 Mar 2023 18:27:44 -0500 Subject: [PATCH 04/16] 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 22bc52809e..cd90e6c7a5 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.1.1-dev - Bumping intl package to 0.18.0 to fix version solving issue with flutter_tools +- LogFileStats includes more information about how many events are persisted and total count of how many times each event was sent ## 0.1.0 From 4c4050f3d993ed3d6976db0b9083025b097cbff3 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 11:53:02 -0500 Subject: [PATCH 05/16] Use package:clock for LogFileStats calculations --- pkgs/unified_analytics/lib/src/log_handler.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index 2314e4aff2..edfee49e89 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -4,6 +4,7 @@ import 'dart:convert'; +import 'package:clock/clock.dart'; import 'package:file/file.dart'; import 'package:path/path.dart' as p; @@ -16,13 +17,13 @@ class LogFileStats { /// The oldest timestamp in the log file final DateTime startDateTime; - /// Number of minutes from [startDateTime] to [DateTime.now()] + /// Number of minutes from [startDateTime] to [clock.now()] final int minsFromStartDateTime; /// The latest timestamp in the log file final DateTime endDateTime; - /// Number of minutes from [endDateTime] to [DateTime.now()] + /// Number of minutes from [endDateTime] to [clock.now()] final int minsFromEndDateTime; /// The number of unique session ids found in the log file @@ -136,9 +137,9 @@ class LogHandler { return LogFileStats( startDateTime: startDateTime, - minsFromStartDateTime: DateTime.now().difference(startDateTime).inMinutes, + minsFromStartDateTime: clock.now().difference(startDateTime).inMinutes, endDateTime: endDateTime, - minsFromEndDateTime: DateTime.now().difference(endDateTime).inMinutes, + minsFromEndDateTime: clock.now().difference(endDateTime).inMinutes, sessionCount: counter['sessions']!.length, flutterChannelCount: counter['flutter_channel']!.length, toolCount: counter['tool']!.length, From 1b24317364625704630277241523c7fec2e09fa8 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:06:56 -0500 Subject: [PATCH 06/16] Update to test to include newly added data points --- .../test/unified_analytics_test.dart | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index ffa770642b..84266b0ab2 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -699,34 +699,71 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion }); test('Check the query on the log file works as expected', () { - expect(analytics.logFileStats(), isNull, - reason: 'The result for the log file stats should be null when ' - 'there are no logs'); - analytics.sendEvent( - eventName: DashEvent.hotReloadTime, eventData: {}); + // Define a new clock so that we can check the output of the + // log file stats method explicitly + final DateTime start = DateTime(1995, 3, 3, 12, 0); + final Clock firstClock = Clock.fixed(start); - final LogFileStats firstQuery = analytics.logFileStats()!; - expect(firstQuery.sessionCount, 1, - reason: - 'There should only be one session after the initial send event'); - expect(firstQuery.flutterChannelCount, 1, - reason: 'There should only be one flutter channel logged'); - expect(firstQuery.toolCount, 1, - reason: 'There should only be one tool logged'); + // Run with the simulated clock for the initial events + withClock(firstClock, () { + expect(analytics.logFileStats(), isNull, + reason: 'The result for the log file stats should be null when ' + 'there are no logs'); + analytics.sendEvent( + eventName: DashEvent.hotReloadTime, eventData: {}); + + final LogFileStats firstQuery = analytics.logFileStats()!; + expect(firstQuery.sessionCount, 1, + reason: + 'There should only be one session after the initial send event'); + expect(firstQuery.flutterChannelCount, 1, + reason: 'There should only be one flutter channel logged'); + expect(firstQuery.toolCount, 1, + reason: 'There should only be one tool logged'); + }); // Define a new clock that is outside of the session duration - final DateTime firstClock = - clock.now().add(Duration(minutes: kSessionDurationMinutes + 1)); + final DateTime secondClock = + start.add(Duration(minutes: kSessionDurationMinutes + 1)); // Use the new clock to send an event that will change the session identifier - withClock(Clock.fixed(firstClock), () { + withClock(Clock.fixed(secondClock), () { analytics.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); - }); - final LogFileStats secondQuery = analytics.logFileStats()!; - expect(secondQuery.sessionCount, 2, - reason: 'There should be 2 sessions after the second event'); + final LogFileStats secondQuery = analytics.logFileStats()!; + expect(secondQuery.sessionCount, 2, + reason: 'There should be 2 sessions after the second event'); + + // Construct the expected response for the second query + // + // This will need to be updated as the output for [LogFileStats] + // changes in the future + // + // Expecting the below returned + // { + // "startDateTime": "1995-03-03 12:00:00.000", + // "minsFromStartDateTime": 31, + // "endDateTime": "1995-03-03 12:31:00.000", + // "minsFromEndDateTime": 0, + // "sessionCount": 2, + // "flutterChannelCount": 1, + // "toolCount": 1, + // "recordCount": 2, + // "eventCount": { + // "hot_reload_time": 2 + // } + // } + expect(secondQuery.startDateTime, DateTime(1995, 3, 3, 12, 0)); + expect(secondQuery.minsFromStartDateTime, 31); + expect(secondQuery.endDateTime, DateTime(1995, 3, 3, 12, 31)); + expect(secondQuery.minsFromEndDateTime, 0); + expect(secondQuery.sessionCount, 2); + expect(secondQuery.flutterChannelCount, 1); + expect(secondQuery.toolCount, 1); + expect(secondQuery.recordCount, 2); + expect(secondQuery.eventCount, {'hot_reload_time': 2}); + }); }); test('Check that the log file shows two different tools being used', () { From 2dfc9aeefb68fdbdc82873759b6b7e677e643ade Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:13:09 -0500 Subject: [PATCH 07/16] Added test to check CHANGELOG for matching versions --- .../unified_analytics/test/unified_analytics_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index 84266b0ab2..e4f1851776 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -838,7 +838,8 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion 'The query should be null because the `local_time` value is malformed'); }); - test('Check that the constant kPackageVersion matches pubspec version', () { + test('Version is the same in the change log, pubspec, and constants.dart', + () { // Parse the contents of the pubspec.yaml final String pubspecYamlString = io.File('pubspec.yaml').readAsStringSync(); @@ -851,6 +852,13 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion 'constants.dart need to match\n' 'Pubspec: $version && constants.dart: $kPackageVersion\n\n' 'Make sure both are the same'); + + // Parse the contents of the change log file + final String changeLogFirstLineString = + io.File('CHANGELOG.md').readAsLinesSync().first; + expect(changeLogFirstLineString.substring(3), kPackageVersion, + reason: 'The CHANGELOG.md file needs the first line to ' + 'be the same version as the pubspec and constants.dart'); }); test('Null values for flutter parameters is reflected properly in log file', From 364d9f8ad89a3597dbd8ce700eefef04008c8881 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:14:30 -0500 Subject: [PATCH 08/16] Prep for publishing --- pkgs/unified_analytics/CHANGELOG.md | 2 +- pkgs/unified_analytics/lib/src/constants.dart | 2 +- pkgs/unified_analytics/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index cd90e6c7a5..347a36a5d1 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.1.1-dev +## 0.1.1 - Bumping intl package to 0.18.0 to fix version solving issue with flutter_tools - LogFileStats includes more information about how many events are persisted and total count of how many times each event was sent diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart index 6d71836685..45fbc74035 100644 --- a/pkgs/unified_analytics/lib/src/constants.dart +++ b/pkgs/unified_analytics/lib/src/constants.dart @@ -70,7 +70,7 @@ const int kLogFileLength = 2500; const String kLogFileName = 'dash-analytics.log'; /// The current version of the package, should be in line with pubspec version. -const String kPackageVersion = '0.1.1-dev'; +const String kPackageVersion = '0.1.1'; /// The minimum length for a session const int kSessionDurationMinutes = 30; diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml index e5d0a16c81..7f1490268c 100644 --- a/pkgs/unified_analytics/pubspec.yaml +++ b/pkgs/unified_analytics/pubspec.yaml @@ -4,7 +4,7 @@ description: >- to Google Analytics. # When updating this, keep the version consistent with the changelog and the # value in lib/src/constants.dart. -version: 0.1.1-dev +version: 0.1.1 repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics environment: From 38f6501debdee2d6ced861f2ac99be3fefce7cb9 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:19:56 -0500 Subject: [PATCH 09/16] Remove redundant expect statement --- pkgs/unified_analytics/test/unified_analytics_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index e4f1851776..060f73f123 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -732,8 +732,6 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion eventName: DashEvent.hotReloadTime, eventData: {}); final LogFileStats secondQuery = analytics.logFileStats()!; - expect(secondQuery.sessionCount, 2, - reason: 'There should be 2 sessions after the second event'); // Construct the expected response for the second query // From 92373bada967b64311747b16415559acd217a911 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:43:19 -0500 Subject: [PATCH 10/16] Ensure that no events are being sent for the first run + test --- pkgs/unified_analytics/lib/src/analytics.dart | 12 +++++-- .../test/unified_analytics_test.dart | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index 67a782c37c..e0234c390d 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -246,7 +246,15 @@ class AnalyticsImpl implements Analytics { required DashEvent eventName, Map eventData = const {}, }) { - if (!telemetryEnabled) return null; + + // Checking the [telemetryEnabled] boolean reflects what the + // config file reflects + // + // Checking the [_showMessage] boolean indicates if this the first + // time the tool is using analytics or if there has been an update + // the messaging found in constants.dart - in both cases, analytics + // will not be sent until the second time the tool is used + if (!telemetryEnabled || _showMessage) return null; // Construct the body of the request final Map body = generateRequestBody( @@ -311,7 +319,7 @@ class TestAnalytics extends AnalyticsImpl { required DashEvent eventName, Map eventData = const {}, }) { - if (!telemetryEnabled) return null; + if (!telemetryEnabled || _showMessage) return null; // Calling the [generateRequestBody] method will ensure that the // session file is getting updated without actually making any diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index 060f73f123..d9c828c44c 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -138,6 +138,38 @@ void main() { 'The config file should have the same message from the constants file'); }); + test('First time analytics run will not send events, second time will', () { + // Send an event with the first analytics class; this should result + // in no logs in the log file which keeps track of all the events + // that have been sent + analytics.sendEvent( + eventName: DashEvent.hotReloadTime, eventData: {}); + analytics.sendEvent( + eventName: DashEvent.hotReloadTime, eventData: {}); + + // Create a new instance of the analytics class with the SAME tool + // to simulate that the same tool is running for the second time + final Analytics secondAnalytics = Analytics.test( + tool: initialToolName, + homeDirectory: home, + measurementId: measurementId, + apiSecret: apiSecret, + flutterChannel: flutterChannel, + toolsMessageVersion: toolsMessageVersion, + toolsMessage: toolsMessage, + flutterVersion: flutterVersion, + dartVersion: dartVersion, + fs: fs, + platform: platform, + ); + + secondAnalytics.sendEvent( + eventName: DashEvent.hotReloadTime, eventData: {}); + + expect(logFile.readAsLinesSync().length, 1, + reason: 'The second analytics instance should have logged an event'); + }); + test('Toggling telemetry boolean through Analytics class api', () async { expect(analytics.telemetryEnabled, true, reason: 'Telemetry should be enabled by default ' From b49ad610a02daf4e1bc400018fb2fb2352fde04a Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 13:15:42 -0500 Subject: [PATCH 11/16] Fixing tests to account for no events sent on tool first run --- .../test/unified_analytics_test.dart | 114 ++++++++++-------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index d9c828c44c..ec019883df 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -22,6 +22,7 @@ void main() { late FileSystem fs; late Directory home; late Directory dartToolDirectory; + late Analytics initializationAnalytics; late Analytics analytics; late File clientIdFile; late File sessionFile; @@ -49,8 +50,27 @@ void main() { home = fs.directory(homeDirName); dartToolDirectory = home.childDirectory(kDartToolDirectoryName); + // This is the first analytics instance that will be used to demonstrate + // that events will not be sent with the first run of analytics + initializationAnalytics = Analytics.test( + tool: initialToolName, + homeDirectory: home, + measurementId: measurementId, + apiSecret: apiSecret, + flutterChannel: flutterChannel, + toolsMessageVersion: toolsMessageVersion, + toolsMessage: toolsMessage, + flutterVersion: flutterVersion, + dartVersion: dartVersion, + fs: fs, + platform: platform, + ); + // The main analytics instance, other instances can be spawned within tests // to test how to instances running together work + // + // This instance should have the same parameters as the one above for + // [initializationAnalytics] analytics = Analytics.test( tool: initialToolName, homeDirectory: home, @@ -102,7 +122,7 @@ void main() { expect(dartToolDirectory.listSync().length, equals(4), reason: 'There should only be 4 files in the $kDartToolDirectoryName directory'); - expect(analytics.shouldShowMessage, true, + expect(initializationAnalytics.shouldShowMessage, true, reason: 'For the first run, analytics should default to being enabled'); expect(configFile.readAsLinesSync().length, kConfigString.split('\n').length + 1, @@ -142,28 +162,14 @@ void main() { // Send an event with the first analytics class; this should result // in no logs in the log file which keeps track of all the events // that have been sent - analytics.sendEvent( + initializationAnalytics.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); - analytics.sendEvent( + initializationAnalytics.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); - // Create a new instance of the analytics class with the SAME tool - // to simulate that the same tool is running for the second time - final Analytics secondAnalytics = Analytics.test( - tool: initialToolName, - homeDirectory: home, - measurementId: measurementId, - apiSecret: apiSecret, - flutterChannel: flutterChannel, - toolsMessageVersion: toolsMessageVersion, - toolsMessage: toolsMessage, - flutterVersion: flutterVersion, - dartVersion: dartVersion, - fs: fs, - platform: platform, - ); - - secondAnalytics.sendEvent( + // Use the second instance of analytics defined in setUp() to send the actual + // events to simulate the second time the tool ran + analytics.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); expect(logFile.readAsLinesSync().length, 1, @@ -797,24 +803,30 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion }); test('Check that the log file shows two different tools being used', () { - final Analytics secondAnalytics = Analytics.test( - tool: secondTool, - homeDirectory: home, - measurementId: 'measurementId', - apiSecret: 'apiSecret', - flutterChannel: flutterChannel, - toolsMessageVersion: toolsMessageVersion, - toolsMessage: toolsMessage, - flutterVersion: 'Flutter 3.6.0-7.0.pre.47', - dartVersion: 'Dart 2.19.0', - fs: fs, - platform: platform, - ); + // Use a for loop two initialize the second analytics instance + // twice to account for no events being sent on the first instance + // run for a given tool + Analytics? secondAnalytics; + for (int i = 0; i < 2; i++) { + secondAnalytics = Analytics.test( + tool: secondTool, + homeDirectory: home, + measurementId: 'measurementId', + apiSecret: 'apiSecret', + flutterChannel: flutterChannel, + toolsMessageVersion: toolsMessageVersion, + toolsMessage: toolsMessage, + flutterVersion: 'Flutter 3.6.0-7.0.pre.47', + dartVersion: 'Dart 2.19.0', + fs: fs, + platform: platform, + ); + } // Send events with both instances of the classes analytics.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); - secondAnalytics.sendEvent( + secondAnalytics!.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); // Query the log file stats to verify that there are two tools @@ -893,22 +905,28 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion test('Null values for flutter parameters is reflected properly in log file', () { - final Analytics secondAnalytics = Analytics.test( - tool: secondTool, - homeDirectory: home, - measurementId: 'measurementId', - apiSecret: 'apiSecret', - // flutterChannel: flutterChannel, THIS NEEDS TO REMAIN REMOVED - // toolsMessageVersion: toolsMessageVersion, THIS NEEDS TO REMAIN REMOVED - toolsMessage: toolsMessage, - flutterVersion: 'Flutter 3.6.0-7.0.pre.47', - dartVersion: 'Dart 2.19.0', - fs: fs, - platform: platform, - ); + // Use a for loop two initialize the second analytics instance + // twice to account for no events being sent on the first instance + // run for a given tool + Analytics? secondAnalytics; + for (int i = 0; i < 2; i++) { + secondAnalytics = Analytics.test( + tool: secondTool, + homeDirectory: home, + measurementId: 'measurementId', + apiSecret: 'apiSecret', + // flutterChannel: flutterChannel, THIS NEEDS TO REMAIN REMOVED + toolsMessageVersion: toolsMessageVersion, + toolsMessage: toolsMessage, + flutterVersion: 'Flutter 3.6.0-7.0.pre.47', + dartVersion: 'Dart 2.19.0', + fs: fs, + platform: platform, + ); + } // Send an event and check that the query stats reflects what is expected - secondAnalytics.sendEvent( + secondAnalytics!.sendEvent( eventName: DashEvent.hotReloadTime, eventData: {}); // Query the log file stats to verify that there are two tools From 1c5341bb98257b0c0b78ad4e8212e5f7b9e71e52 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 13:53:50 -0500 Subject: [PATCH 12/16] Clean up from dart format --- pkgs/unified_analytics/lib/src/analytics.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index e0234c390d..fa1f5102fe 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -246,10 +246,9 @@ class AnalyticsImpl implements Analytics { required DashEvent eventName, Map eventData = const {}, }) { - // Checking the [telemetryEnabled] boolean reflects what the // config file reflects - // + // // Checking the [_showMessage] boolean indicates if this the first // time the tool is using analytics or if there has been an update // the messaging found in constants.dart - in both cases, analytics From 52785cccfd423300e309da46e072276071adaa5b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 13:56:55 -0500 Subject: [PATCH 13/16] dart format on test directory --- pkgs/unified_analytics/test/unified_analytics_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index ec019883df..22474f5ecc 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -905,9 +905,9 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion test('Null values for flutter parameters is reflected properly in log file', () { - // Use a for loop two initialize the second analytics instance - // twice to account for no events being sent on the first instance - // run for a given tool + // Use a for loop two initialize the second analytics instance + // twice to account for no events being sent on the first instance + // run for a given tool Analytics? secondAnalytics; for (int i = 0; i < 2; i++) { secondAnalytics = Analytics.test( From 00ddb5df281b63f17a1560ebacf80fd4b1d4f31d Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:21:15 -0500 Subject: [PATCH 14/16] Use one clock for `now` --- pkgs/unified_analytics/lib/src/log_handler.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index edfee49e89..7654d60f03 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -135,11 +135,13 @@ class LogHandler { eventCount[record.eventName] = eventCount[record.eventName]! + 1; } + final DateTime now = clock.now(); + return LogFileStats( startDateTime: startDateTime, - minsFromStartDateTime: clock.now().difference(startDateTime).inMinutes, + minsFromStartDateTime: now.difference(startDateTime).inMinutes, endDateTime: endDateTime, - minsFromEndDateTime: clock.now().difference(endDateTime).inMinutes, + minsFromEndDateTime: now.difference(endDateTime).inMinutes, sessionCount: counter['sessions']!.length, flutterChannelCount: counter['flutter_channel']!.length, toolCount: counter['tool']!.length, From 696dc4001acc6d07c4435a18c1e8e44d4ef9bc7f Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 10 Mar 2023 15:59:49 -0500 Subject: [PATCH 15/16] Including formatDateTime to include timezone offset --- pkgs/unified_analytics/lib/src/log_handler.dart | 4 ++-- pkgs/unified_analytics/lib/src/user_property.dart | 3 ++- pkgs/unified_analytics/lib/src/utils.dart | 15 +++++++++++++++ .../test/unified_analytics_test.dart | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index 7654d60f03..2caf2b5326 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -234,7 +234,7 @@ class LogItem { /// "value": "flutter-tools" /// }, /// "local_time": { - /// "value": "2023-01-31 14:32:14.592898" + /// "value": "2023-01-31 14:32:14.592898 -0500" /// } /// } /// } @@ -294,7 +294,7 @@ class LogItem { } // Parse the local time from the string extracted - final DateTime localTime = DateTime.parse(localTimeString!); + final DateTime localTime = DateTime.parse(localTimeString!).toLocal(); return LogItem( eventName: eventName, diff --git a/pkgs/unified_analytics/lib/src/user_property.dart b/pkgs/unified_analytics/lib/src/user_property.dart index ee82e62331..b85870e6d0 100644 --- a/pkgs/unified_analytics/lib/src/user_property.dart +++ b/pkgs/unified_analytics/lib/src/user_property.dart @@ -8,6 +8,7 @@ import 'package:clock/clock.dart'; import 'constants.dart'; import 'session.dart'; +import 'utils.dart'; class UserProperty { final Session session; @@ -58,6 +59,6 @@ class UserProperty { 'dart_version': dartVersion, 'analytics_pkg_version': kPackageVersion, 'tool': tool, - 'local_time': '${clock.now()}', + 'local_time': formatDateTime(clock.now()), }; } diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart index f007ecb157..43f5d8e92a 100644 --- a/pkgs/unified_analytics/lib/src/utils.dart +++ b/pkgs/unified_analytics/lib/src/utils.dart @@ -10,6 +10,21 @@ import 'package:file/file.dart'; import 'enums.dart'; import 'user_property.dart'; +/// 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) { + final String sign = t.timeZoneOffset.isNegative ? '-' : '+'; + final Duration tzOffset = t.timeZoneOffset.abs(); + final int hoursOffset = tzOffset.inHours; + final int minutesOffset = + tzOffset.inMinutes - (Duration.minutesPerHour * hoursOffset); + assert(hoursOffset < 24); + assert(minutesOffset < 60); + + String twoDigits(int n) => (n >= 10) ? '$n' : '0$n'; + return '$t $sign${twoDigits(hoursOffset)}${twoDigits(minutesOffset)}'; +} + /// Construct the Map that will be converted to json for the /// body of the request /// diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index 22474f5ecc..234566ccee 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -848,7 +848,7 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion '"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},' '"dart_version":{"value":"Dart 2.19.0"},' // '"tool":{"value":"flutter-tools"},' NEEDS REMAIN REMOVED - '"local_time":{"value":"2023-01-31 14:32:14.592898"}}}'; + '"local_time":{"value":"2023-01-31 14:32:14.592898 -0500"}}}'; logFile.writeAsStringSync(malformedLog); final LogFileStats? query = analytics.logFileStats(); From e097e3db03a7fcbf0a5c8e7a7709062d4ef94062 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 10 Mar 2023 16:02:02 -0500 Subject: [PATCH 16/16] Updating documentation of `local_time` --- pkgs/unified_analytics/lib/src/utils.dart | 2 +- pkgs/unified_analytics/test/unified_analytics_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart index 43f5d8e92a..e9b5562478 100644 --- a/pkgs/unified_analytics/lib/src/utils.dart +++ b/pkgs/unified_analytics/lib/src/utils.dart @@ -41,7 +41,7 @@ String formatDateTime(DateTime t) { /// "flutter_version": { "value": "Flutter 3.6.0-7.0.pre.47" }, /// "dart_version": { "value": "Dart 2.19.0" }, /// "tool": { "value": "flutter-tools" }, -/// "local_time": { "value": "2023-01-11 14:53:31.471816" } +/// "local_time": { "value": "2023-01-11 14:53:31.471816 -0500" } /// } /// } /// ``` diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index 234566ccee..35ed449801 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -870,7 +870,7 @@ $initialToolName=${ConfigHandler.dateStamp},$toolsMessageVersion '"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},' '"dart_version":{"value":"Dart 2.19.0"},' '"tool":{"value":"flutter-tools"},' - '"local_time":{"value":"2023-xx-31 14:32:14.592898"}}}'; // PURPOSEFULLY MALFORMED + '"local_time":{"value":"2023-xx-31 14:32:14.592898 -0500"}}}'; // PURPOSEFULLY MALFORMED logFile.writeAsStringSync(malformedLog); final LogFileStats? query = analytics.logFileStats();