diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md index d826cda8b..1c736205a 100644 --- a/pkgs/unified_analytics/CHANGELOG.md +++ b/pkgs/unified_analytics/CHANGELOG.md @@ -5,6 +5,7 @@ - Align supported tool list with PDD - Exposing a new instance method that will need to be invoked when a client has successfully shown the consent message to the user `clientShowedMessage()` - Adding and incrementing a tool's version will automatically use the current consent message version instead of incrementing by 1 +- Default constructor has disabled the usage of local log file and session json file until revisions have landed to the privacy document ## 0.1.2 diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index 116963e8a..b1a8a5bdd 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -22,17 +22,68 @@ import 'user_property.dart'; import 'utils.dart'; abstract class Analytics { - /// The default factory constructor that will return an implementation - /// of the [Analytics] abstract class using the [LocalFileSystem] + // TODO: (eliasyishak) enable again once revision has landed; + // also remove all instances of [pddFlag] + + // /// The default factory constructor that will return an implementation + // /// of the [Analytics] abstract class using the [LocalFileSystem] + // factory Analytics({ + // required DashTool tool, + // required String dartVersion, + // String? flutterChannel, + // String? flutterVersion, + // }) { + // // Create the instance of the file system so clients don't need + // // resolve on their own + // const FileSystem fs = LocalFileSystem(); + + // // Resolve the OS using dart:io + // final DevicePlatform platform; + // if (io.Platform.operatingSystem == 'linux') { + // platform = DevicePlatform.linux; + // } else if (io.Platform.operatingSystem == 'macos') { + // platform = DevicePlatform.macos; + // } else { + // platform = DevicePlatform.windows; + // } + + // // Create the instance of the GA Client which will create + // // an [http.Client] to send requests + // final GAClient gaClient = GAClient( + // measurementId: kGoogleAnalyticsMeasurementId, + // apiSecret: kGoogleAnalyticsApiSecret, + // ); + + // return AnalyticsImpl( + // tool: tool, + // homeDirectory: getHomeDirectory(fs), + // flutterChannel: flutterChannel, + // flutterVersion: flutterVersion, + // dartVersion: dartVersion, + // platform: platform, + // toolsMessageVersion: kToolsMessageVersion, + // fs: fs, + // gaClient: gaClient, + // ); + // } + + // TODO: (eliasyishak) remove this contructor once revision has landed + + /// Prevents the unapproved files for logging and session handling + /// from being saved on to the developer's disk until privacy revision + /// has landed factory Analytics({ required DashTool tool, required String dartVersion, String? flutterChannel, String? flutterVersion, + FileSystem? fsOverride, + Directory? homeOverride, + DevicePlatform? platformOverride, }) { // Create the instance of the file system so clients don't need // resolve on their own - const FileSystem fs = LocalFileSystem(); + final FileSystem fs = fsOverride ?? LocalFileSystem(); // Resolve the OS using dart:io final DevicePlatform platform; @@ -46,21 +97,27 @@ abstract class Analytics { // Create the instance of the GA Client which will create // an [http.Client] to send requests - final GAClient gaClient = GAClient( - measurementId: kGoogleAnalyticsMeasurementId, - apiSecret: kGoogleAnalyticsApiSecret, - ); + // + // When a [fsOverride] is passed in, we can assume to + // use the fake Google Analytics client + final GAClient gaClient = fsOverride != null + ? FakeGAClient() + : GAClient( + measurementId: kGoogleAnalyticsMeasurementId, + apiSecret: kGoogleAnalyticsApiSecret, + ); return AnalyticsImpl( tool: tool, - homeDirectory: getHomeDirectory(fs), + homeDirectory: homeOverride ?? getHomeDirectory(fs), flutterChannel: flutterChannel, flutterVersion: flutterVersion, dartVersion: dartVersion, - platform: platform, + platform: platformOverride ?? platform, toolsMessageVersion: kToolsMessageVersion, fs: fs, gaClient: gaClient, + pddFlag: true, ); } @@ -232,6 +289,7 @@ class AnalyticsImpl implements Analytics { required this.toolsMessageVersion, required this.fs, required gaClient, + bool pddFlag = false, }) : _gaClient = gaClient { // This initializer class will let the instance know // if it was the first run; if it is, nothing will be sent @@ -241,6 +299,7 @@ class AnalyticsImpl implements Analytics { tool: tool.label, homeDirectory: homeDirectory, toolsMessageVersion: toolsMessageVersion, + pddFlag: pddFlag, ); initializer.run(); _showMessage = initializer.firstRun; @@ -276,12 +335,21 @@ class AnalyticsImpl implements Analytics { homeDirectory.path, kDartToolDirectoryName, kClientIdFileName)) .readAsStringSync(); + // Create the session instance that will be responsible for managing + // all the sessions across every client tool using this pakage + final Session session; + if (pddFlag) { + session = NoopSession(); + } else { + session = Session(homeDirectory: homeDirectory, fs: fs); + } + // Initialize the user property class that will be attached to // each event that is sent to Google Analytics -- it will be responsible // for getting the session id or rolling the session if the duration // exceeds [kSessionDurationMinutes] userProperty = UserProperty( - session: Session(homeDirectory: homeDirectory, fs: fs), + session: session, flutterChannel: flutterChannel, host: platform.label, flutterVersion: flutterVersion, @@ -290,7 +358,11 @@ class AnalyticsImpl implements Analytics { ); // Initialize the log handler to persist events that are being sent - _logHandler = LogHandler(fs: fs, homeDirectory: homeDirectory); + if (pddFlag) { + _logHandler = NoopLogHandler(); + } else { + _logHandler = LogHandler(fs: fs, homeDirectory: homeDirectory); + } } @override diff --git a/pkgs/unified_analytics/lib/src/initializer.dart b/pkgs/unified_analytics/lib/src/initializer.dart index ca2f5a24e..981a74708 100644 --- a/pkgs/unified_analytics/lib/src/initializer.dart +++ b/pkgs/unified_analytics/lib/src/initializer.dart @@ -18,6 +18,7 @@ class Initializer { final Directory homeDirectory; final int toolsMessageVersion; bool firstRun = false; + final bool pddFlag; /// Responsibe for the initialization of the files /// necessary for analytics reporting @@ -33,6 +34,7 @@ class Initializer { required this.tool, required this.homeDirectory, required this.toolsMessageVersion, + required this.pddFlag, }); /// Get a string representation of the current date in the following format @@ -114,14 +116,14 @@ class Initializer { // Begin initialization checks for the session file final File sessionFile = fs.file( p.join(homeDirectory.path, kDartToolDirectoryName, kSessionFileName)); - if (!sessionFile.existsSync()) { + if (!sessionFile.existsSync() && !pddFlag) { createSessionFile(sessionFile: sessionFile); } // Begin initialization checks for the log file to persist events locally final File logFile = fs .file(p.join(homeDirectory.path, kDartToolDirectoryName, kLogFileName)); - if (!logFile.existsSync()) { + if (!logFile.existsSync() && !pddFlag) { createLogFile(logFile: logFile); } } diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart index 2caf2b532..31af059f2 100644 --- a/pkgs/unified_analytics/lib/src/log_handler.dart +++ b/pkgs/unified_analytics/lib/src/log_handler.dart @@ -313,3 +313,20 @@ class LogItem { } } } + +class NoopLogHandler implements LogHandler { + @override + FileSystem get fs => throw UnimplementedError(); + + @override + Directory get homeDirectory => throw UnimplementedError(); + + @override + File get logFile => throw UnimplementedError(); + + @override + LogFileStats? logFileStats() => null; + + @override + void save({required Map data}) {} +} diff --git a/pkgs/unified_analytics/lib/src/session.dart b/pkgs/unified_analytics/lib/src/session.dart index 4fc694ddf..9fcc104bf 100644 --- a/pkgs/unified_analytics/lib/src/session.dart +++ b/pkgs/unified_analytics/lib/src/session.dart @@ -98,3 +98,37 @@ class Session { } } } + +class NoopSession implements Session { + @override + final int _lastPing = 0; + + @override + final int _sessionId = DateTime.now().millisecondsSinceEpoch; + + @override + void _refreshSessionData() {} + + @override + File get _sessionFile => throw UnimplementedError(); + + @override + FileSystem get fs => throw UnimplementedError(); + + @override + int getSessionId() => _sessionId; + + @override + Directory get homeDirectory => throw UnimplementedError(); + + @override + String toJson() { + throw UnimplementedError(); + } + + @override + set _lastPing(int lastPing) {} + + @override + set _sessionId(int sessionId) {} +} diff --git a/pkgs/unified_analytics/test/pdd_approved_test.dart b/pkgs/unified_analytics/test/pdd_approved_test.dart new file mode 100644 index 000000000..2e5852eb7 --- /dev/null +++ b/pkgs/unified_analytics/test/pdd_approved_test.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// 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 'dart:io' as io; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:test/test.dart'; + +import 'package:unified_analytics/src/constants.dart'; +import 'package:unified_analytics/unified_analytics.dart'; + +void main() { + late FileSystem fs; + late Directory home; + late Directory dartToolDirectory; + late Analytics initializationAnalytics; + late Analytics analytics; + late File clientIdFile; + late File sessionFile; + late File configFile; + late File logFile; + + const String homeDirName = 'home'; + const DashTool initialTool = DashTool.flutterTool; + const String dartVersion = 'dartVersion'; + + setUp(() { + // Setup the filesystem with the home directory + final FileSystemStyle fsStyle = + io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix; + fs = MemoryFileSystem.test(style: fsStyle); + home = fs.directory(homeDirName); + dartToolDirectory = home.childDirectory(kDartToolDirectoryName); + + // Create the initial analytics instance that will onboard the tool + initializationAnalytics = Analytics( + tool: initialTool, + dartVersion: dartVersion, + fsOverride: fs, + homeOverride: home, + ); + initializationAnalytics.clientShowedMessage(); + + // The main analytics instance that will be used for the + // tests after having the tool onboarded + analytics = Analytics( + tool: initialTool, + dartVersion: dartVersion, + fsOverride: fs, + homeOverride: home, + ); + + // The 3 files that should have been generated + clientIdFile = home + .childDirectory(kDartToolDirectoryName) + .childFile(kClientIdFileName); + sessionFile = + home.childDirectory(kDartToolDirectoryName).childFile(kSessionFileName); + configFile = + home.childDirectory(kDartToolDirectoryName).childFile(kConfigFileName); + logFile = + home.childDirectory(kDartToolDirectoryName).childFile(kLogFileName); + }); + + test('Initializer properly sets up on first run', () { + expect(dartToolDirectory.existsSync(), true, + reason: 'The directory should have been created'); + expect(clientIdFile.existsSync(), true, + reason: 'The $kClientIdFileName file was not found'); + expect(sessionFile.existsSync(), false, + reason: 'The session handling has been disabled'); + expect(configFile.existsSync(), true, + reason: 'The $kConfigFileName was not found'); + expect(logFile.existsSync(), false, + reason: 'The log file has been disabled'); + expect(dartToolDirectory.listSync().length, equals(2), + reason: + 'There should only be 2 files in the $kDartToolDirectoryName directory'); + expect(initializationAnalytics.shouldShowMessage, true, + reason: 'For the first run, the message should be shown'); + expect(configFile.readAsLinesSync().length, + kConfigString.split('\n').length + 1, + reason: 'The number of lines should equal lines in constant value + 1 ' + 'for the initialized tool'); + }); + + test('Sending events does not cause any errors', () async { + await expectLater( + () => analytics.sendEvent(eventName: DashEvent.hotReloadTime), + returnsNormally); + }); +} diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart index 90cf2c3e1..4e555c1fd 100644 --- a/pkgs/unified_analytics/test/unified_analytics_test.dart +++ b/pkgs/unified_analytics/test/unified_analytics_test.dart @@ -125,7 +125,7 @@ void main() { reason: 'There should only be 4 files in the $kDartToolDirectoryName directory'); expect(initializationAnalytics.shouldShowMessage, true, - reason: 'For the first run, analytics should default to being enabled'); + reason: 'For the first run, the message should be shown'); expect(configFile.readAsLinesSync().length, kConfigString.split('\n').length + 1, reason: 'The number of lines should equal lines in constant value + 1 '