Skip to content

Commit e5353aa

Browse files
authored
Adding more information to LogFileStats + minor updates to tests (#31)
* Adding duration for start and end times + record count * Update USAGE_GUIDE.md * Update to include counts for each event in LogFileStats * Update CHANGELOG.md * Use package:clock for LogFileStats calculations * Update to test to include newly added data points * Added test to check CHANGELOG for matching versions * Prep for publishing * Remove redundant expect statement * Ensure that no events are being sent for the first run + test * Fixing tests to account for no events sent on tool first run * Clean up from dart format * dart format on test directory * Use one clock for `now` * Including formatDateTime to include timezone offset * Updating documentation of `local_time`
1 parent a51c094 commit e5353aa

9 files changed

+251
-68
lines changed

pkgs/unified_analytics/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 0.1.1-dev
1+
## 0.1.1
22

33
- Bumping intl package to 0.18.0 to fix version solving issue with flutter_tools
4+
- LogFileStats includes more information about how many events are persisted and total count of how many times each event was sent
45

56
## 0.1.0
67

pkgs/unified_analytics/USAGE_GUIDE.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,30 @@ print(analytics.logFileStats());
156156
157157
// Prints out the below
158158
// {
159-
// "startDateTime": "2023-02-08 15:07:10.293728",
160-
// "endDateTime": "2023-02-08 15:07:10.299678",
161-
// "sessionCount": 1,
162-
// "flutterChannelCount": 1,
163-
// "toolCount": 1
159+
// "startDateTime": "2023-02-22 15:23:24.410921",
160+
// "minsFromStartDateTime": 20319,
161+
// "endDateTime": "2023-03-08 15:46:36.318211",
162+
// "minsFromEndDateTime": 136,
163+
// "sessionCount": 7,
164+
// "flutterChannelCount": 2,
165+
// "toolCount": 1,
166+
// "recordCount": 23,
167+
// "eventCount": {
168+
// "hot_reload_time": 16,
169+
// "analytics_collection_enabled": 7,
170+
// ... scales up with number of events
171+
// }
164172
// }
165173
```
166174

167175
Explanation of the each key above
168176

169177
- startDateTime: the earliest event that was sent
178+
- minsFromStartDateTime: the number of minutes elapsed since the earliest message
170179
- endDateTime: the latest, most recent event that was sent
180+
- minsFromEndDateTime: the number of minutes elapsed since the latest message
171181
- sessionCount: count of sessions; sessions have a minimum time of 30 minutes
172182
- flutterChannelCount: count of flutter channels (can be 0 if developer is a Dart dev only)
173183
- toolCount: count of the Dart and Flutter tools sending analytics
184+
- recordCount: count of the total number of events in the log file
185+
- eventCount: counts each unique event and how many times they occurred in the log file

pkgs/unified_analytics/lib/src/analytics.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,14 @@ class AnalyticsImpl implements Analytics {
246246
required DashEvent eventName,
247247
Map<String, Object?> eventData = const {},
248248
}) {
249-
if (!telemetryEnabled) return null;
249+
// Checking the [telemetryEnabled] boolean reflects what the
250+
// config file reflects
251+
//
252+
// Checking the [_showMessage] boolean indicates if this the first
253+
// time the tool is using analytics or if there has been an update
254+
// the messaging found in constants.dart - in both cases, analytics
255+
// will not be sent until the second time the tool is used
256+
if (!telemetryEnabled || _showMessage) return null;
250257

251258
// Construct the body of the request
252259
final Map<String, Object?> body = generateRequestBody(
@@ -311,7 +318,7 @@ class TestAnalytics extends AnalyticsImpl {
311318
required DashEvent eventName,
312319
Map<String, Object?> eventData = const {},
313320
}) {
314-
if (!telemetryEnabled) return null;
321+
if (!telemetryEnabled || _showMessage) return null;
315322

316323
// Calling the [generateRequestBody] method will ensure that the
317324
// session file is getting updated without actually making any

pkgs/unified_analytics/lib/src/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const int kLogFileLength = 2500;
7070
const String kLogFileName = 'dash-analytics.log';
7171

7272
/// The current version of the package, should be in line with pubspec version.
73-
const String kPackageVersion = '0.1.1-dev';
73+
const String kPackageVersion = '0.1.1';
7474

7575
/// The minimum length for a session
7676
const int kSessionDurationMinutes = 30;

pkgs/unified_analytics/lib/src/log_handler.dart

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:convert';
66

7+
import 'package:clock/clock.dart';
78
import 'package:file/file.dart';
89
import 'package:path/path.dart' as p;
910

@@ -16,9 +17,15 @@ class LogFileStats {
1617
/// The oldest timestamp in the log file
1718
final DateTime startDateTime;
1819

20+
/// Number of minutes from [startDateTime] to [clock.now()]
21+
final int minsFromStartDateTime;
22+
1923
/// The latest timestamp in the log file
2024
final DateTime endDateTime;
2125

26+
/// Number of minutes from [endDateTime] to [clock.now()]
27+
final int minsFromEndDateTime;
28+
2229
/// The number of unique session ids found in the log file
2330
final int sessionCount;
2431

@@ -28,22 +35,37 @@ class LogFileStats {
2835
/// The number of unique tools found in the log file
2936
final int toolCount;
3037

38+
/// The map containing all of the events in the file along with
39+
/// how many times they have occured
40+
final Map<String, int> eventCount;
41+
42+
/// Total number of records in the log file
43+
final int recordCount;
44+
3145
/// Contains the data from the [LogHandler.logFileStats] method
3246
const LogFileStats({
3347
required this.startDateTime,
48+
required this.minsFromStartDateTime,
3449
required this.endDateTime,
50+
required this.minsFromEndDateTime,
3551
required this.sessionCount,
3652
required this.flutterChannelCount,
3753
required this.toolCount,
54+
required this.recordCount,
55+
required this.eventCount,
3856
});
3957

4058
@override
4159
String toString() => jsonEncode(<String, Object?>{
4260
'startDateTime': startDateTime.toString(),
61+
'minsFromStartDateTime': minsFromStartDateTime,
4362
'endDateTime': endDateTime.toString(),
63+
'minsFromEndDateTime': minsFromEndDateTime,
4464
'sessionCount': sessionCount,
4565
'flutterChannelCount': flutterChannelCount,
4666
'toolCount': toolCount,
67+
'recordCount': recordCount,
68+
'eventCount': eventCount,
4769
});
4870
}
4971

@@ -89,26 +111,42 @@ class LogHandler {
89111
final DateTime startDateTime = records.first.localTime;
90112
final DateTime endDateTime = records.last.localTime;
91113

92-
// Collection of unique sessions
114+
// Map with counters for user properties
93115
final Map<String, Set<Object>> counter = <String, Set<Object>>{
94116
'sessions': <int>{},
95117
'flutter_channel': <String>{},
96118
'tool': <String>{},
97119
};
120+
121+
// Map of counters for each event
122+
final Map<String, int> eventCount = <String, int>{};
98123
for (LogItem record in records) {
99124
counter['sessions']!.add(record.sessionId);
100125
counter['tool']!.add(record.tool);
101126
if (record.flutterChannel != null) {
102127
counter['flutter_channel']!.add(record.flutterChannel!);
103128
}
129+
130+
// Count each event, if it doesn't exist in the [eventCount]
131+
// it will be added first
132+
if (!eventCount.containsKey(record.eventName)) {
133+
eventCount[record.eventName] = 0;
134+
}
135+
eventCount[record.eventName] = eventCount[record.eventName]! + 1;
104136
}
105137

138+
final DateTime now = clock.now();
139+
106140
return LogFileStats(
107141
startDateTime: startDateTime,
142+
minsFromStartDateTime: now.difference(startDateTime).inMinutes,
108143
endDateTime: endDateTime,
144+
minsFromEndDateTime: now.difference(endDateTime).inMinutes,
109145
sessionCount: counter['sessions']!.length,
110146
flutterChannelCount: counter['flutter_channel']!.length,
111147
toolCount: counter['tool']!.length,
148+
eventCount: eventCount,
149+
recordCount: records.length,
112150
);
113151
}
114152

@@ -135,6 +173,7 @@ class LogHandler {
135173

136174
/// Data class for each record persisted on the client's machine
137175
class LogItem {
176+
final String eventName;
138177
final int sessionId;
139178
final String? flutterChannel;
140179
final String host;
@@ -144,6 +183,7 @@ class LogItem {
144183
final DateTime localTime;
145184

146185
LogItem({
186+
required this.eventName,
147187
required this.sessionId,
148188
this.flutterChannel,
149189
required this.host,
@@ -194,18 +234,27 @@ class LogItem {
194234
/// "value": "flutter-tools"
195235
/// },
196236
/// "local_time": {
197-
/// "value": "2023-01-31 14:32:14.592898"
237+
/// "value": "2023-01-31 14:32:14.592898 -0500"
198238
/// }
199239
/// }
200240
/// }
201241
/// ```
202242
static LogItem? fromRecord(Map<String, Object?> record) {
203-
if (!record.containsKey('user_properties')) return null;
243+
if (!record.containsKey('user_properties') ||
244+
!record.containsKey('events')) {
245+
return null;
246+
}
204247

205248
// Using a try/except here to parse out the fields if possible,
206249
// if not, it will quietly return null and won't get processed
207250
// downstream
208251
try {
252+
// Parse out values from the top level key = 'events' and return
253+
// a map for the one event in the value
254+
final Map<String, Object?> eventProp =
255+
((record['events']! as List<Object?>).first as Map<String, Object?>);
256+
final String eventName = eventProp['name'] as String;
257+
209258
// Parse the data out of the `user_properties` value
210259
final Map<String, Object?> userProps =
211260
record['user_properties'] as Map<String, Object?>;
@@ -230,6 +279,10 @@ class LogItem {
230279
// indicates the record is malformed; note that `flutter_version`
231280
// and `flutter_channel` are nullable fields in the log file
232281
final List<Object?> values = <Object?>[
282+
// Values associated with the top level key = 'events'
283+
eventName,
284+
285+
// Values associated with the top level key = 'events'
233286
sessionId,
234287
host,
235288
dartVersion,
@@ -241,9 +294,10 @@ class LogItem {
241294
}
242295

243296
// Parse the local time from the string extracted
244-
final DateTime localTime = DateTime.parse(localTimeString!);
297+
final DateTime localTime = DateTime.parse(localTimeString!).toLocal();
245298

246299
return LogItem(
300+
eventName: eventName,
247301
sessionId: sessionId!,
248302
flutterChannel: flutterChannel,
249303
host: host!,

pkgs/unified_analytics/lib/src/user_property.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:clock/clock.dart';
88

99
import 'constants.dart';
1010
import 'session.dart';
11+
import 'utils.dart';
1112

1213
class UserProperty {
1314
final Session session;
@@ -58,6 +59,6 @@ class UserProperty {
5859
'dart_version': dartVersion,
5960
'analytics_pkg_version': kPackageVersion,
6061
'tool': tool,
61-
'local_time': '${clock.now()}',
62+
'local_time': formatDateTime(clock.now()),
6263
};
6364
}

pkgs/unified_analytics/lib/src/utils.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ import 'package:file/file.dart';
1010
import 'enums.dart';
1111
import 'user_property.dart';
1212

13+
/// Format time as 'yyyy-MM-dd HH:mm:ss Z' where Z is the difference between the
14+
/// timezone of t and UTC formatted according to RFC 822.
15+
String formatDateTime(DateTime t) {
16+
final String sign = t.timeZoneOffset.isNegative ? '-' : '+';
17+
final Duration tzOffset = t.timeZoneOffset.abs();
18+
final int hoursOffset = tzOffset.inHours;
19+
final int minutesOffset =
20+
tzOffset.inMinutes - (Duration.minutesPerHour * hoursOffset);
21+
assert(hoursOffset < 24);
22+
assert(minutesOffset < 60);
23+
24+
String twoDigits(int n) => (n >= 10) ? '$n' : '0$n';
25+
return '$t $sign${twoDigits(hoursOffset)}${twoDigits(minutesOffset)}';
26+
}
27+
1328
/// Construct the Map that will be converted to json for the
1429
/// body of the request
1530
///
@@ -26,7 +41,7 @@ import 'user_property.dart';
2641
/// "flutter_version": { "value": "Flutter 3.6.0-7.0.pre.47" },
2742
/// "dart_version": { "value": "Dart 2.19.0" },
2843
/// "tool": { "value": "flutter-tools" },
29-
/// "local_time": { "value": "2023-01-11 14:53:31.471816" }
44+
/// "local_time": { "value": "2023-01-11 14:53:31.471816 -0500" }
3045
/// }
3146
/// }
3247
/// ```

pkgs/unified_analytics/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: >-
44
to Google Analytics.
55
# When updating this, keep the version consistent with the changelog and the
66
# value in lib/src/constants.dart.
7-
version: 0.1.1-dev
7+
version: 0.1.1
88
repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics
99

1010
environment:

0 commit comments

Comments
 (0)