Fix empty string for `CLIENT_ID` from being sent when user decides to opt in (#144)
* unified_analytics and graphs: cleanup lints, bump pkg deps (#108)
* Fix to conditional logic for setting telemetry
* Test Client id not empty + different from original id
* Test using a new instance to check client id
* Reread the new client id after clearing it
diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart
index 6d72864..a1b18c6 100644
--- a/pkgs/unified_analytics/lib/src/analytics.dart
+++ b/pkgs/unified_analytics/lib/src/analytics.dart
@@ -286,7 +286,7 @@
late final ConfigHandler _configHandler;
final GAClient _gaClient;
final SurveyHandler _surveyHandler;
- late final String _clientId;
+ late String _clientId;
late final File _clientIdFile;
late final UserProperty userProperty;
late final LogHandler _logHandler;
@@ -556,32 +556,47 @@
final collectionEvent =
Event.analyticsCollectionEnabled(status: reportingBool);
- // Construct the body of the request to signal
- // telemetry status toggling
- //
- // We use don't use the sendEvent method because it may
- // be blocked by the [telemetryEnabled] getter
- final body = generateRequestBody(
- clientId: _clientId,
- eventName: collectionEvent.eventName,
- eventData: collectionEvent.eventData,
- userProperty: userProperty,
- );
+ // The body of the request that will be sent to GA4
+ final Map<String, Object?> body;
- _logHandler.save(data: body);
-
- // Conditional logic for clearing contents of persisted
- // files (except for config file) on opt out
- if (!reportingBool) {
- _sessionHandler.sessionFile.writeAsStringSync('');
- _logHandler.logFile.writeAsStringSync('');
- _clientIdFile.writeAsStringSync('');
- } else {
+ if (reportingBool) {
// Recreate the session and client id file; no need to
// recreate the log file since it will only receives events
// to persist from events sent
Initializer.createClientIdFile(clientFile: _clientIdFile);
Initializer.createSessionFile(sessionFile: _sessionHandler.sessionFile);
+
+ // Reread the client ID string so an empty string is not being
+ // sent to GA4 since the persisted files are cleared when a user
+ // decides to opt out of telemetry collection
+ _clientId = _clientIdFile.readAsStringSync();
+
+ // We must construct the body at this point after we have read in the
+ // new client id string that was generated
+ body = generateRequestBody(
+ clientId: _clientId,
+ eventName: collectionEvent.eventName,
+ eventData: collectionEvent.eventData,
+ userProperty: userProperty,
+ );
+
+ _logHandler.save(data: body);
+ } else {
+ // Construct the body of the request to signal
+ // telemetry status toggling
+ body = generateRequestBody(
+ clientId: _clientId,
+ eventName: collectionEvent.eventName,
+ eventData: collectionEvent.eventData,
+ userProperty: userProperty,
+ );
+
+ // For opted out users, data in the persisted files is cleared
+ _sessionHandler.sessionFile.writeAsStringSync('');
+ _logHandler.logFile.writeAsStringSync('');
+ _clientIdFile.writeAsStringSync('');
+
+ _clientId = _clientIdFile.readAsStringSync();
}
// Pass to the google analytics client to send
diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart
index 05973be..2eb3cda 100644
--- a/pkgs/unified_analytics/test/unified_analytics_test.dart
+++ b/pkgs/unified_analytics/test/unified_analytics_test.dart
@@ -205,6 +205,8 @@
});
test('Toggling telemetry boolean through Analytics class api', () async {
+ final originalClientId = clientIdFile.readAsStringSync();
+
expect(analytics.telemetryEnabled, true,
reason: 'Telemetry should be enabled by default '
'when initialized for the first time');
@@ -239,6 +241,50 @@
reason: 'Check on event name');
expect((lastLogItem['events'] as List).last['params']['status'], true,
reason: 'Status should be false');
+ expect((lastLogItem['client_id'] as String).isNotEmpty, true);
+ expect(originalClientId != lastLogItem['client_id'], true,
+ reason: 'When opting in again, the client id should be regenerated');
+ });
+
+ test('Confirm client id is not empty string after opting in', () async {
+ await analytics.setTelemetry(false);
+ expect(logFile.readAsLinesSync().length, 0,
+ reason: 'Log file should have been cleared after opting out');
+ expect(clientIdFile.readAsStringSync().length, 0,
+ reason: 'CLIENT ID file gets cleared on opt out');
+
+ // Start up a second instance to simulate starting another
+ // command being run
+ final secondAnalytics = Analytics.test(
+ tool: initialTool,
+ homeDirectory: home,
+ measurementId: measurementId,
+ apiSecret: apiSecret,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // Setting telemetry back on will emit a new event
+ // where the client id string should not be empty
+ await secondAnalytics.setTelemetry(true);
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Analytics telemetry should be enabled');
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'There should only one event since it was cleared on opt out');
+ expect(clientIdFile.readAsStringSync().length, greaterThan(0),
+ reason: 'CLIENT ID file gets regenerated on opt in');
+
+ // Extract the last log item to check for the keys
+ final lastLogItem =
+ jsonDecode(logFile.readAsLinesSync().last) as Map<String, Object?>;
+ expect((lastLogItem['client_id'] as String).isNotEmpty, true,
+ reason: 'The client id should have been regenerated and '
+ 'emitted in the opt in event');
});
test(