-
Notifications
You must be signed in to change notification settings - Fork 58
Adding more information to LogFileStats
+ minor updates to tests
#31
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
Changes from all commits
3e76c3f
14422b8
52b6bda
0e172dc
9e5a5b3
4c4050f
1b24317
2dfc9ae
364d9f8
38f6501
92373ba
b49ad61
1c5341b
52785cc
00ddb5d
696dc40
e097e3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,9 +17,15 @@ class LogFileStats { | |
/// The oldest timestamp in the log file | ||
final DateTime startDateTime; | ||
|
||
/// Number of minutes from [startDateTime] to [clock.now()] | ||
final int minsFromStartDateTime; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about just storing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh shoot, I totally forgot that I'm using So really, when we perform operations with these value, we don't even convert it from ints into DateTime. Thoughts on still leaving it this way? @override
String toString() => jsonEncode(<String, Object?>{
'startDateTime': startDateTime.toString(),
'minsFromStartDateTime': minsFromStartDateTime,
'endDateTime': endDateTime.toString(),
'minsFromEndDateTime': minsFromEndDateTime,
'sessionCount': sessionCount,
'flutterChannelCount': flutterChannelCount,
'toolCount': toolCount,
'recordCount': recordCount,
'eventCount': eventCount,
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh ok, in that case keeping it a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we don't actually serialize anything after the log file stats have been created. This data class will instead be used for checking conditions for a survey and returning a Boolean to show the survey or not. But these logs are persisted before this step on the user's machine in the same form that they are sent to GA. And in the local log file, it is being persisted as a date time string so that we know the time the user sent the event in their locale. GA also assigns its own time stamp in milliseconds since epoch time in UTC so that all events can be compared chronologically. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's never serialized, how is it part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the workflow starts with an event sent, and the events usually look like the below being sent in the body of a POST request
In the above, we do serialize the Once the event is sent, the entire payload of the POST request is saved into the log file. It is at this point that we read it in and deserialize the string back into a After we have created the final LogFileStats query = _logHandler.logFileStats();
if (query.toolCount > 2) {
print('The user has used more than 2 tools');
} It is only when we print out the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh ok, never mind, I misread what you said, you said it is persisted because we specifically want it in the user's locale? So GA is expecting a localized string? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
/// The latest timestamp in the log file | ||
final DateTime endDateTime; | ||
|
||
/// Number of minutes from [endDateTime] to [clock.now()] | ||
final int minsFromEndDateTime; | ||
|
||
/// The number of unique session ids found in the log file | ||
final int sessionCount; | ||
|
||
|
@@ -28,22 +35,37 @@ 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<String, int> eventCount; | ||
christopherfujino marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// 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, | ||
required this.eventCount, | ||
}); | ||
|
||
@override | ||
String toString() => jsonEncode(<String, Object?>{ | ||
'startDateTime': startDateTime.toString(), | ||
eliasyishak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'minsFromStartDateTime': minsFromStartDateTime, | ||
'endDateTime': endDateTime.toString(), | ||
'minsFromEndDateTime': minsFromEndDateTime, | ||
'sessionCount': sessionCount, | ||
'flutterChannelCount': flutterChannelCount, | ||
'toolCount': toolCount, | ||
'recordCount': recordCount, | ||
'eventCount': eventCount, | ||
}); | ||
} | ||
|
||
|
@@ -89,26 +111,42 @@ 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<String, Set<Object>> counter = <String, Set<Object>>{ | ||
'sessions': <int>{}, | ||
'flutter_channel': <String>{}, | ||
'tool': <String>{}, | ||
}; | ||
|
||
// Map of counters for each event | ||
final Map<String, int> eventCount = <String, int>{}; | ||
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; | ||
} | ||
|
||
final DateTime now = clock.now(); | ||
|
||
return LogFileStats( | ||
startDateTime: startDateTime, | ||
minsFromStartDateTime: now.difference(startDateTime).inMinutes, | ||
endDateTime: endDateTime, | ||
minsFromEndDateTime: now.difference(endDateTime).inMinutes, | ||
sessionCount: counter['sessions']!.length, | ||
flutterChannelCount: counter['flutter_channel']!.length, | ||
toolCount: counter['tool']!.length, | ||
eventCount: eventCount, | ||
recordCount: records.length, | ||
); | ||
} | ||
|
||
|
@@ -135,6 +173,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; | ||
|
@@ -144,6 +183,7 @@ class LogItem { | |
final DateTime localTime; | ||
|
||
LogItem({ | ||
required this.eventName, | ||
required this.sessionId, | ||
this.flutterChannel, | ||
required this.host, | ||
|
@@ -194,18 +234,27 @@ class LogItem { | |
/// "value": "flutter-tools" | ||
/// }, | ||
/// "local_time": { | ||
/// "value": "2023-01-31 14:32:14.592898" | ||
/// "value": "2023-01-31 14:32:14.592898 -0500" | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
static LogItem? fromRecord(Map<String, Object?> 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<String, Object?> eventProp = | ||
((record['events']! as List<Object?>).first as Map<String, Object?>); | ||
final String eventName = eventProp['name'] as String; | ||
|
||
// Parse the data out of the `user_properties` value | ||
final Map<String, Object?> userProps = | ||
record['user_properties'] as Map<String, Object?>; | ||
|
@@ -230,6 +279,10 @@ class LogItem { | |
// indicates the record is malformed; note that `flutter_version` | ||
// and `flutter_channel` are nullable fields in the log file | ||
final List<Object?> values = <Object?>[ | ||
// Values associated with the top level key = 'events' | ||
eventName, | ||
|
||
// Values associated with the top level key = 'events' | ||
sessionId, | ||
host, | ||
dartVersion, | ||
|
@@ -241,9 +294,10 @@ 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, | ||
sessionId: sessionId!, | ||
flutterChannel: flutterChannel, | ||
host: host!, | ||
|
Uh oh!
There was an error while loading. Please reload this page.