diff --git a/docs/documentation/tips-and-tricks.mdx b/docs/documentation/tips-and-tricks.mdx index 042c20949..6bed14f8a 100644 --- a/docs/documentation/tips-and-tricks.mdx +++ b/docs/documentation/tips-and-tricks.mdx @@ -66,7 +66,6 @@ an example: $ cat .patrol.env EMAIL=user@example.com PASSWORD=ny4ncat -DISABLE_ANALYTICS=true ``` ### Granting sensitive permission through the Settings app diff --git a/docs/tips-and-tricks.mdx b/docs/tips-and-tricks.mdx index 042c20949..6bed14f8a 100644 --- a/docs/tips-and-tricks.mdx +++ b/docs/tips-and-tricks.mdx @@ -66,7 +66,6 @@ an example: $ cat .patrol.env EMAIL=user@example.com PASSWORD=ny4ncat -DISABLE_ANALYTICS=true ``` ### Granting sensitive permission through the Settings app diff --git a/packages/patrol_cli/CHANGELOG.md b/packages/patrol_cli/CHANGELOG.md index 3fa63465d..296b75bd4 100644 --- a/packages/patrol_cli/CHANGELOG.md +++ b/packages/patrol_cli/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased + +- Add `PATROL_ANALYTICS_ENABLED` environment variable to disable analytics. (#2483) +- Enable analytics by default. (#2483) + ## 3.4.1 - Add android product flavor to dart-define. (#2425) diff --git a/packages/patrol_cli/README.md b/packages/patrol_cli/README.md index b7bcd0b00..a5556a478 100644 --- a/packages/patrol_cli/README.md +++ b/packages/patrol_cli/README.md @@ -34,6 +34,11 @@ directories to PATH: - on Unix-like systems, add `$HOME/.pub-cache/bin` - on Windows, add `%USERPROFILE%\AppData\Local\Pub\Cache\bin` +### Disabling analytics + +To disable analytics, set the `PATROL_ANALYTICS_ENABLED` environment variable to +`false`. + ### Shell completion Patrol CLI supports shell completion for bash, zsh and fish, thanks to the diff --git a/packages/patrol_cli/lib/src/analytics/analytics.dart b/packages/patrol_cli/lib/src/analytics/analytics.dart index da305c3a7..9db20f35c 100644 --- a/packages/patrol_cli/lib/src/analytics/analytics.dart +++ b/packages/patrol_cli/lib/src/analytics/analytics.dart @@ -40,11 +40,13 @@ class Analytics { required Platform platform, http.Client? httpClient, required bool isCI, + required bool? envAnalyticsEnabled, }) : _fs = fs, _platform = platform, _httpClient = httpClient ?? http.Client(), _postUrl = _getAnalyticsUrl(measurementId, apiSecret), - _isCI = isCI; + _isCI = isCI, + _envAnalyticsEnabled = envAnalyticsEnabled; final FileSystem _fs; final Platform _platform; @@ -53,6 +55,7 @@ class Analytics { final String _postUrl; final bool _isCI; + final bool? _envAnalyticsEnabled; /// Sends an event to Google Analytics that command [name] run. /// @@ -67,7 +70,10 @@ class Analytics { return false; } - final enabled = _config?.enabled ?? false; + /// If the environment variable `PATROL_ANALYTICS_ENABLED` is set, + /// use it to determine if the command should be sent. + /// If not set, use the value from the config file. + final enabled = _envAnalyticsEnabled ?? _config?.enabled ?? true; if (!enabled) { return false; } diff --git a/packages/patrol_cli/lib/src/runner/patrol_command.dart b/packages/patrol_cli/lib/src/runner/patrol_command.dart index f6c049acf..6b0dfd8df 100644 --- a/packages/patrol_cli/lib/src/runner/patrol_command.dart +++ b/packages/patrol_cli/lib/src/runner/patrol_command.dart @@ -238,7 +238,7 @@ abstract class PatrolCommand extends Command { } FlutterCommand get flutterCommand { - final arg = globalResults!['flutter-command'] as String?; + final arg = globalResults?['flutter-command'] as String?; var cmd = arg; if (cmd == null || cmd.isEmpty) { diff --git a/packages/patrol_cli/lib/src/runner/patrol_command_runner.dart b/packages/patrol_cli/lib/src/runner/patrol_command_runner.dart index 371f7ddd7..0c1ef4d5d 100644 --- a/packages/patrol_cli/lib/src/runner/patrol_command_runner.dart +++ b/packages/patrol_cli/lib/src/runner/patrol_command_runner.dart @@ -1,4 +1,5 @@ import 'dart:io' show ProcessSignal, stdin; +import 'dart:io' as p show Platform; import 'package:adb/adb.dart'; import 'package:args/args.dart'; @@ -41,6 +42,8 @@ Future patrolCommandRunner(List args) async { const platform = LocalPlatform(); final processManager = LoggingLocalProcessManager(logger: logger); final isCI = ci.isCI; + final analyticsEnv = p.Platform.environment[_patrolAnalyticsEnvName]; + final analyticsEnabled = bool.tryParse(analyticsEnv ?? ''); final runner = PatrolCommandRunner( pubUpdater: pubUpdater, @@ -53,6 +56,7 @@ Future patrolCommandRunner(List args) async { fs: fs, platform: platform, isCI: isCI, + envAnalyticsEnabled: analyticsEnabled, ), processManager: processManager, isCI: isCI, @@ -79,6 +83,20 @@ Future patrolCommandRunner(List args) async { const _gaTrackingId = 'G-W8XN8GS5BC'; const _gaApiSecret = 'CUIwI1nCQWGJQAK8E0AIfg'; +const _patrolAnalyticsEnvName = 'PATROL_ANALYTICS_ENABLED'; +const _helloPatrol = ''' ++---------------------------------------------------+ +| Patrol - Ready for action! | ++---------------------------------------------------+ +| We would like to collect anonymous usage data | +| to improve Patrol CLI. No sensitive or private | +| information will ever leave your machine. | +| | +| By default, analytics is enabled. If you want to | +| disable it, please set the environment variable: | +| `PATROL_ANALYTICS_ENABLED=false` | ++---------------------------------------------------+ +'''; class PatrolCommandRunner extends CompletionCommandRunner { PatrolCommandRunner({ @@ -380,24 +398,16 @@ Ask questions, get support at https://github.com/leancodepl/patrol/discussions'' } void _handleAnalytics() { - _logger.info( - ''' -\n -+---------------------------------------------------+ -| Patrol - Ready for action! | -+---------------------------------------------------+ -| We would like to collect anonymous usage data | -| to improve Patrol CLI. No sensitive or private | -| information will ever leave your machine. | -+---------------------------------------------------+ -\n''', - ); - final analyticsEnabled = _logger.confirm( - 'Enable analytics?', - defaultValue: true, - ); - _analytics.enabled = analyticsEnabled; - if (analyticsEnabled) { + _logger.info(_helloPatrol); + + /// If the environment variable `PATROL_ANALYTICS_ENABLED` is set, + /// use it to determine if the command should be sent. + /// If not, analytics will be enabled by default. + final patrolAnalyticsEnabled = + p.Platform.environment[_patrolAnalyticsEnvName]; + _analytics.enabled = + bool.tryParse(patrolAnalyticsEnabled ?? 'true') ?? true; + if (_analytics.enabled) { _logger.info('Analytics enabled. Thank you!'); } else { _logger.info('Analytics disabled.'); diff --git a/packages/patrol_cli/test/analytics/analytics_test.dart b/packages/patrol_cli/test/analytics/analytics_test.dart index ffeb01341..66e04978d 100644 --- a/packages/patrol_cli/test/analytics/analytics_test.dart +++ b/packages/patrol_cli/test/analytics/analytics_test.dart @@ -11,7 +11,7 @@ import '../src/fakes.dart'; import '../src/mocks.dart'; void main() { - group('Analytics', () { + group('Analytics with no env variable', () { late Analytics analytics; late FileSystem fs; @@ -36,6 +36,7 @@ void main() { platform: fakePlatform('/Users/john'), httpClient: httpClient, isCI: false, + envAnalyticsEnabled: null, ); }); @@ -63,6 +64,114 @@ void main() { expect(sent, false); }); }); + + group('Analytics with env variable enabled', () { + late Analytics analytics; + late FileSystem fs; + + setUp(() { + setUpFakes(); + + fs = MemoryFileSystem.test(); + + final httpClient = MockHttpClient(); + when( + () => httpClient.post( + any(), + body: any(named: 'body'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => http.Response('', 200)); + + analytics = Analytics( + measurementId: 'measurementId', + apiSecret: 'apiSecret', + fs: fs, + platform: fakePlatform('/Users/john'), + httpClient: httpClient, + isCI: false, + envAnalyticsEnabled: true, + ); + }); + + test('sends data when enabled', () async { + // given + _createFakeFileSystem(fs, analyticsEnabled: true); + + // when + final sent = + await analytics.sendCommand(FlutterVersion.test(), 'test command'); + + // then + expect(sent, true); + }); + + test('does not send data when disabled', () async { + // given + _createFakeFileSystem(fs, analyticsEnabled: false); + + // when + final sent = + await analytics.sendCommand(FlutterVersion.test(), 'test command'); + + // then + expect(sent, true); + }); + }); + + group('Analytics with env variable disabled', () { + late Analytics analytics; + late FileSystem fs; + + setUp(() { + setUpFakes(); + + fs = MemoryFileSystem.test(); + + final httpClient = MockHttpClient(); + when( + () => httpClient.post( + any(), + body: any(named: 'body'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => http.Response('', 200)); + + analytics = Analytics( + measurementId: 'measurementId', + apiSecret: 'apiSecret', + fs: fs, + platform: fakePlatform('/Users/john'), + httpClient: httpClient, + isCI: false, + envAnalyticsEnabled: false, + ); + }); + + test('sends data when enabled', () async { + // given + _createFakeFileSystem(fs, analyticsEnabled: true); + + // when + final sent = + await analytics.sendCommand(FlutterVersion.test(), 'test command'); + + // then + expect(sent, false); + }); + + test('does not send data when disabled', () async { + // given + _createFakeFileSystem(fs, analyticsEnabled: false); + + // when + final sent = + await analytics.sendCommand(FlutterVersion.test(), 'test command'); + + // then + expect(sent, false); + }); + }); } void _createFakeFileSystem(FileSystem fs, {required bool analyticsEnabled}) {