diff --git a/packages/devtools_app/benchmark/test_infra/automators/devtools_automator.dart b/packages/devtools_app/benchmark/test_infra/automators/devtools_automator.dart index 1af23ed41f7..b3ed71d45c9 100644 --- a/packages/devtools_app/benchmark/test_infra/automators/devtools_automator.dart +++ b/packages/devtools_app/benchmark/test_infra/automators/devtools_automator.dart @@ -45,7 +45,11 @@ class DevToolsAutomater { Future.delayed(safePumpDuration, automateDevToolsGestures); return DevToolsApp( defaultScreens(sampleData: sampleData), - AnalyticsController(enabled: false, firstRun: false), + AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), ); } diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_stub.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_stub.dart index 292109c55dd..2d6a44e3b86 100644 --- a/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_stub.dart +++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_stub.dart @@ -4,8 +4,13 @@ import 'dart:async'; +import 'analytics.dart' as ga; import 'analytics_controller.dart'; -FutureOr get devToolsAnalyticsController => _controller; -AnalyticsController _controller = - AnalyticsController(enabled: false, firstRun: false); +FutureOr get devToolsAnalyticsController async { + return AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: await ga.fetchAnalyticsConsentMessage(), + ); +} diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_web.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_web.dart index 592d8a29dd0..01a254fe02a 100644 --- a/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_web.dart +++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_controller_web.dart @@ -33,6 +33,8 @@ Future get devToolsAnalyticsController async { ga.initializeGA(); ga.jsHookupListenerForGA(); }, + consentMessage: await ga.fetchAnalyticsConsentMessage(), + markConsentMessageAsShown: ga.markConsentMessageAsShown, ), ); return _controllerCompleter!.future; diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_stub.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_stub.dart index 5cc1b5d4ab2..fd1f332c2f7 100644 --- a/packages/devtools_app/lib/src/shared/analytics/_analytics_stub.dart +++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_stub.dart @@ -32,6 +32,11 @@ Future enableAnalytics() async {} Future disableAnalytics() async {} +Future fetchAnalyticsConsentMessage() async => + 'stubbed consent message'; + +Future markConsentMessageAsShown() async {} + void screen( String screenName, [ int value = 0, diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart index cc05e4c52e2..2d09203c399 100644 --- a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart +++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart @@ -795,6 +795,19 @@ Future disableAnalytics() async { return await setAnalyticsEnabled(false); } +/// Fetch the legal consent message for telemetry collection for +/// package:unified_analyitcs from the server +Future fetchAnalyticsConsentMessage() async { + return await server.fetchAnalyticsConsentMessage(); +} + +/// Communicates with the server to confirm with package:unified_analyitcs +/// that the consent message has successfully been shown and to allow events +/// to be recorded if the user has decided to remain opted in. +Future markConsentMessageAsShown() async { + return await server.markConsentMessageAsShown(); +} + /// Computes the DevTools application. Fills in the devtoolsPlatformType and /// devtoolsChrome. void computeDevToolsCustomGTagsData() { diff --git a/packages/devtools_app/lib/src/shared/analytics/analytics_controller.dart b/packages/devtools_app/lib/src/shared/analytics/analytics_controller.dart index b526829c25b..c1965596033 100644 --- a/packages/devtools_app/lib/src/shared/analytics/analytics_controller.dart +++ b/packages/devtools_app/lib/src/shared/analytics/analytics_controller.dart @@ -18,11 +18,15 @@ class AnalyticsController { AnalyticsController({ required bool enabled, required bool firstRun, + required this.consentMessage, this.onEnableAnalytics, this.onDisableAnalytics, this.onSetupAnalytics, + AsyncAnalyticsCallback? markConsentMessageAsShown, }) : analyticsEnabled = ValueNotifier(enabled), - _shouldPrompt = ValueNotifier(firstRun && !enabled) { + _shouldPrompt = + ValueNotifier(firstRun && consentMessage.isNotEmpty), + _markConsentMessageAsShown = markConsentMessageAsShown { if (_shouldPrompt.value) { unawaited(toggleAnalyticsEnabled(true)); } @@ -43,8 +47,17 @@ class AnalyticsController { final AsyncAnalyticsCallback? onDisableAnalytics; + /// Method to call to confirm with package:unified_analytics the user has + /// seen the consent message. + final AsyncAnalyticsCallback? _markConsentMessageAsShown; + Future markConsentMessageAsShown() async => + await _markConsentMessageAsShown?.call(); + final VoidCallback? onSetupAnalytics; + /// Consent message for package:unified_analytics to be shown on first run. + final String consentMessage; + Future toggleAnalyticsEnabled(bool? enable) async { if (enable == true) { analyticsEnabled.value = true; diff --git a/packages/devtools_app/lib/src/shared/analytics/prompt.dart b/packages/devtools_app/lib/src/shared/analytics/prompt.dart index 59a8bbfb43d..1944cc858dc 100644 --- a/packages/devtools_app/lib/src/shared/analytics/prompt.dart +++ b/packages/devtools_app/lib/src/shared/analytics/prompt.dart @@ -5,10 +5,9 @@ import 'dart:async'; import 'package:devtools_app_shared/ui.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import '../config_specific/launch_url/launch_url.dart'; +import '../common_widgets.dart'; import '../utils.dart'; import 'analytics_controller.dart'; @@ -35,9 +34,15 @@ class _AnalyticsPromptState extends State Widget build(BuildContext context) { final theme = Theme.of(context); final textTheme = theme.textTheme; + return ValueListenableBuilder( valueListenable: controller.shouldPrompt, builder: (context, showPrompt, child) { + // Mark the consent message as shown for unified_analytics so that devtools + // can be onboarded into the config file + // ~/.dart-tool/dart-flutter-telemetry.config + if (showPrompt) unawaited(controller.markConsentMessageAsShown()); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -89,30 +94,42 @@ class _AnalyticsPromptState extends State } Widget _analyticsDescription(TextTheme textTheme) { + final consentMessageRegExpResults = + parseAnalyticsConsentMessage(controller.consentMessage); + + // When failing to parse the consent message, fallback to + // displaying the consent message in its regular form + if (consentMessageRegExpResults == null) { + return RichText( + text: TextSpan( + children: [ + TextSpan( + text: controller.consentMessage, + style: textTheme.titleMedium, + ), + ], + ), + ); + } + return RichText( text: TextSpan( children: [ TextSpan( - text: 'DevTools reports feature usage statistics and basic ' - 'crash reports to Google in order to help Google improve ' - 'the tool over time. See Google\'s ', + text: consentMessageRegExpResults[0], style: textTheme.titleMedium, ), - TextSpan( - text: 'privacy policy', + LinkTextSpan( + link: Link( + display: consentMessageRegExpResults[1], + url: consentMessageRegExpResults[1], + ), + context: context, style: textTheme.titleMedium?.copyWith(color: const Color(0xFF54C1EF)), - recognizer: TapGestureRecognizer() - ..onTap = () { - unawaited( - launchUrl( - 'https://www.google.com/intl/en/policies/privacy', - ), - ); - }, ), TextSpan( - text: '.', + text: consentMessageRegExpResults[2], style: textTheme.titleMedium, ), ], @@ -146,3 +163,31 @@ class _AnalyticsPromptState extends State ); } } + +/// This method helps to parse the consent message from +/// `package:unified_analytics` so that the URL can be +/// separated from the block of text so that we can have a +/// hyperlink in the displayed consent message. +List? parseAnalyticsConsentMessage(String consentMessage) { + final results = []; + final RegExp pattern = + RegExp(r'^([\S\s]*)(https?:\/\/[^\s]+)(\)\.)$', multiLine: true); + + final matches = pattern.allMatches(consentMessage); + if (matches.isEmpty) { + return null; + } + + matches.first.groups([1, 2, 3]).forEach((element) { + results.add(element!); + }); + + // There should be 3 groups returned if correctly parsed, one + // for most of the text, one for the URL, and one for what comes + // after the URL + if (results.length != 3) { + return null; + } + + return results; +} diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index 8f645096567..bcdd43ee6be 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart @@ -1443,7 +1443,9 @@ class LinkIconLabel extends StatelessWidget { void _onLinkTap() { unawaited(launchUrl(link.url)); - ga.select(link.gaScreenName, link.gaSelectedItemDescription); + if (link.gaScreenName != null && link.gaSelectedItemDescription != null) { + ga.select(link.gaScreenName!, link.gaSelectedItemDescription!); + } } } @@ -1457,10 +1459,13 @@ class LinkTextSpan extends TextSpan { style: style ?? Theme.of(context).linkTextStyle, recognizer: TapGestureRecognizer() ..onTap = () async { - ga.select( - link.gaScreenName, - link.gaSelectedItemDescription, - ); + if (link.gaScreenName != null && + link.gaSelectedItemDescription != null) { + ga.select( + link.gaScreenName!, + link.gaSelectedItemDescription!, + ); + } await launchUrl(link.url); }, ); @@ -1470,17 +1475,17 @@ class Link { const Link({ required this.display, required this.url, - required this.gaScreenName, - required this.gaSelectedItemDescription, + this.gaScreenName, + this.gaSelectedItemDescription, }); final String display; final String url; - final String gaScreenName; + final String? gaScreenName; - final String gaSelectedItemDescription; + final String? gaSelectedItemDescription; } class Legend extends StatelessWidget { diff --git a/packages/devtools_app/lib/src/shared/server/_analytics_api.dart b/packages/devtools_app/lib/src/shared/server/_analytics_api.dart index 7ecc0853b23..01cad622e04 100644 --- a/packages/devtools_app/lib/src/shared/server/_analytics_api.dart +++ b/packages/devtools_app/lib/src/shared/server/_analytics_api.dart @@ -56,6 +56,27 @@ Future setAnalyticsEnabled([bool value = true]) async { return false; } +/// Fetch the consent message for package:unified_analytics. +Future fetchAnalyticsConsentMessage() async { + String? consentMessage = ''; + if (isDevToolsServerAvailable) { + final resp = await request(apiGetConsentMessage); + if (resp?.statusOk ?? false) { + consentMessage = resp!.body; + } + } + + return consentMessage; +} + +/// Confirm with package:unified_analytics that the consent message +/// has been shown to the user. +Future markConsentMessageAsShown() async { + if (isDevToolsServerAvailable) { + await request(apiMarkConsentMessageAsShown); + } +} + // TODO(terry): Move to an API scheme similar to the VM service extension where // '/api/devToolsEnabled' returns the value (identical VM service) and // '/api/devToolsEnabled?value=true' sets the value. diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index a615ba7c932..7a2bff34a10 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: stack_trace: ^1.10.0 stream_channel: ^2.1.1 string_scanner: ^1.1.0 + unified_analytics: ^5.8.1 url_launcher: ^6.1.0 url_launcher_web: ^2.0.6 vm_service: ^14.0.0 diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 5af94541a00..cc6100775bb 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -15,10 +15,11 @@ significantly improves the user experience when using DevTools embedded in an IDE. - [#7030](https://github.com/flutter/devtools/pull/7030) * Removed the "Dense mode" setting. - [#7086](https://github.com/flutter/devtools/pull/7086) * Added support for filtering with regular expressions in the Logging, Network, and CPU profiler -pages - [#7027](https://github.com/flutter/devtools/pull/7027) +pages. - [#7027](https://github.com/flutter/devtools/pull/7027) * Add a DevTools server interaction for getting the DTD uri. - [#7054](https://github.com/flutter/devtools/pull/7054), [#7164](https://github.com/flutter/devtools/pull/7164) * Enabled expression evaluation with scope for the web, allowing evaluation of inspected widgets. - [#7144](https://github.com/flutter/devtools/pull/7144) * Update `package:vm_service` constraint to `^14.0.0`. - [#6953](https://github.com/flutter/devtools/pull/6953) +* Onboarding devtoools to [`package:unified_analytics`](https://pub.dev/packages/unified_analytics) for unified telemetry logging across Flutter and Dart tooling. - [#7084](https://github.com/flutter/devtools/pull/7084) ## Inspector updates diff --git a/packages/devtools_app/test/shared/analytics_prompt_test.dart b/packages/devtools_app/test/shared/analytics_prompt_test.dart index bf90d5ab5e5..bbb38d553ad 100644 --- a/packages/devtools_app/test/shared/analytics_prompt_test.dart +++ b/packages/devtools_app/test/shared/analytics_prompt_test.dart @@ -11,6 +11,7 @@ import 'package:devtools_test/helpers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; +import 'package:unified_analytics/src/constants.dart' as unified_analytics; const windowSize = Size(2000.0, 1000.0); @@ -18,6 +19,7 @@ void main() { late AnalyticsController controller; late bool didCallEnableAnalytics; + late bool didMarkConsentMessageAsShown; Widget wrapWithAnalytics( Widget child, { @@ -33,9 +35,18 @@ void main() { ); } + test('Unit test parseAnalyticsConsentMessage with consent message', () { + final result = + parseAnalyticsConsentMessage(unified_analytics.kToolsMessage); + + expect(result, isNotEmpty); + expect(result, hasLength(3)); + }); + group('AnalyticsPrompt', () { setUp(() { didCallEnableAnalytics = false; + didMarkConsentMessageAsShown = false; setGlobal(ServiceConnectionManager, FakeServiceConnectionManager()); setGlobal(IdeTheme, IdeTheme()); }); @@ -49,15 +60,24 @@ void main() { onEnableAnalytics: () { didCallEnableAnalytics = true; }, + consentMessage: 'fake message', + markConsentMessageAsShown: () { + didMarkConsentMessageAsShown = true; + }, ); }); testWidgetsWithWindowSize( - 'does not display prompt or call enable analytics', + 'displays the prompt and calls enable analytics', windowSize, (WidgetTester tester) async { expect(controller.analyticsEnabled.value, isTrue); - expect(didCallEnableAnalytics, isFalse); + expect( + didCallEnableAnalytics, + isTrue, + reason: 'Analytics is enabled on first run', + ); + expect(didMarkConsentMessageAsShown, isFalse); final prompt = wrapWithAnalytics( const AnalyticsPrompt( child: Text('Child Text'), @@ -67,10 +87,16 @@ void main() { await tester.pump(); expect( find.text('Send usage statistics for DevTools?'), - findsNothing, + findsOne, + reason: 'The consent message should be shown on first run', ); expect(controller.analyticsEnabled.value, isTrue); - expect(didCallEnableAnalytics, isFalse); + expect( + didMarkConsentMessageAsShown, + isTrue, + reason: + 'The consent message should be marked as shown after displaying', + ); }, ); @@ -91,6 +117,7 @@ void main() { onEnableAnalytics: () { didCallEnableAnalytics = true; }, + consentMessage: 'fake message', ); }); @@ -133,8 +160,11 @@ void main() { const AnalyticsPrompt( child: Text('Child Text'), ), - controllerToUse: - AnalyticsController(enabled: true, firstRun: false), + controllerToUse: AnalyticsController( + enabled: true, + firstRun: false, + consentMessage: 'fake message', + ), ); await tester.pumpWidget(wrap(prompt)); await tester.pump(); @@ -152,6 +182,7 @@ void main() { onEnableAnalytics: () { didCallEnableAnalytics = true; }, + consentMessage: 'fake message', ); }); @@ -290,6 +321,7 @@ void main() { onEnableAnalytics: () { didCallEnableAnalytics = true; }, + consentMessage: 'fake message', ); }); @@ -332,8 +364,11 @@ void main() { const AnalyticsPrompt( child: Text('Child Text'), ), - controllerToUse: - AnalyticsController(enabled: false, firstRun: false), + controllerToUse: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), ); await tester.pumpWidget(wrap(prompt)); await tester.pump(); diff --git a/packages/devtools_app/test/shared/scaffold_debugger_test.dart b/packages/devtools_app/test/shared/scaffold_debugger_test.dart index f373826c1c9..2a302731638 100644 --- a/packages/devtools_app/test/shared/scaffold_debugger_test.dart +++ b/packages/devtools_app/test/shared/scaffold_debugger_test.dart @@ -82,7 +82,11 @@ void main() { ], ), debugger: mockDebuggerController, - analytics: AnalyticsController(enabled: false, firstRun: false), + analytics: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), releaseNotes: ReleaseNotesController(), ), ); diff --git a/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart b/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart index 649823f65da..6e645c06cc8 100644 --- a/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart +++ b/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart @@ -82,7 +82,11 @@ void main() { screens: const [_screen1, _screen2], ), debugger: mockDebuggerController, - analytics: AnalyticsController(enabled: false, firstRun: false), + analytics: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), releaseNotes: ReleaseNotesController(), ), ); diff --git a/packages/devtools_app/test/shared/scaffold_no_app_test.dart b/packages/devtools_app/test/shared/scaffold_no_app_test.dart index 36a6c77ea02..2ebec5813ad 100644 --- a/packages/devtools_app/test/shared/scaffold_no_app_test.dart +++ b/packages/devtools_app/test/shared/scaffold_no_app_test.dart @@ -48,7 +48,11 @@ void main() { Widget wrapScaffold(Widget child) { return wrapWithControllers( child, - analytics: AnalyticsController(enabled: false, firstRun: false), + analytics: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), releaseNotes: ReleaseNotesController(), ); } diff --git a/packages/devtools_app/test/shared/scaffold_profile_test.dart b/packages/devtools_app/test/shared/scaffold_profile_test.dart index bbdb4f26a3c..33620f16051 100644 --- a/packages/devtools_app/test/shared/scaffold_profile_test.dart +++ b/packages/devtools_app/test/shared/scaffold_profile_test.dart @@ -75,7 +75,11 @@ void main() { screens: const [_screen1, _screen2], ), debugger: mockDebuggerController, - analytics: AnalyticsController(enabled: false, firstRun: false), + analytics: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), releaseNotes: ReleaseNotesController(), ), ); diff --git a/packages/devtools_app/test/shared/scaffold_test.dart b/packages/devtools_app/test/shared/scaffold_test.dart index d6838d74702..2b37b567af7 100644 --- a/packages/devtools_app/test/shared/scaffold_test.dart +++ b/packages/devtools_app/test/shared/scaffold_test.dart @@ -47,7 +47,11 @@ void main() { Widget wrapScaffold(Widget child) { return wrapWithControllers( child, - analytics: AnalyticsController(enabled: false, firstRun: false), + analytics: AnalyticsController( + enabled: false, + firstRun: false, + consentMessage: 'fake message', + ), releaseNotes: ReleaseNotesController(), ); } diff --git a/packages/devtools_shared/lib/src/devtools_api.dart b/packages/devtools_shared/lib/src/devtools_api.dart index cc2a31289a4..0753b211231 100644 --- a/packages/devtools_shared/lib/src/devtools_api.dart +++ b/packages/devtools_shared/lib/src/devtools_api.dart @@ -15,6 +15,10 @@ const apiGetDevToolsFirstRun = '${apiPrefix}getDevToolsFirstRun'; const apiGetDevToolsEnabled = '${apiPrefix}getDevToolsEnabled'; const apiSetDevToolsEnabled = '${apiPrefix}setDevToolsEnabled'; +/// package:unified_analytics properties APIs: +const apiGetConsentMessage = '${apiPrefix}getConsentMessage'; +const apiMarkConsentMessageAsShown = '${apiPrefix}markConsentMessageAsShown'; + /// Property name to apiSetDevToolsEnabled the DevToolsEnabled is the name used /// in queryParameter: const devToolsEnabledPropertyName = 'enabled'; diff --git a/packages/devtools_shared/lib/src/server/server_api.dart b/packages/devtools_shared/lib/src/server/server_api.dart index 1c1c6d397d1..0b4c1fb35c7 100644 --- a/packages/devtools_shared/lib/src/server/server_api.dart +++ b/packages/devtools_shared/lib/src/server/server_api.dart @@ -9,6 +9,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:shelf/shelf.dart' as shelf; +import 'package:unified_analytics/unified_analytics.dart'; import '../deeplink/deeplink_manager.dart'; import '../devtools_api.dart'; @@ -37,6 +38,7 @@ class ServerApi { shelf.Request request, { required ExtensionsManager extensionsManager, required DeeplinkManager deeplinkManager, + required Analytics analytics, ServerApi? api, String? dtdUri, }) { @@ -70,23 +72,39 @@ class ServerApi { return api.getCompleted(json.encode(true)); case apiGetDevToolsFirstRun: // Has DevTools been run first time? To bring up analytics dialog. + // + // Additionally, package:unified_analytics will show a message if it + // is the first run with the package or the consent message version has + // been updated + final isFirstRun = + _devToolsUsage.isFirstRun || analytics.shouldShowMessage; return api.getCompleted( - json.encode(_devToolsUsage.isFirstRun), + json.encode(isFirstRun), ); case apiGetDevToolsEnabled: // Is DevTools Analytics collection enabled? + final isEnabled = + _devToolsUsage.analyticsEnabled && analytics.telemetryEnabled; return api.getCompleted( - json.encode(_devToolsUsage.analyticsEnabled), + json.encode(isEnabled), ); case apiSetDevToolsEnabled: // Enable or disable DevTools analytics collection. if (queryParams.containsKey(devToolsEnabledPropertyName)) { - _devToolsUsage.analyticsEnabled = + final analyticsEnabled = json.decode(queryParams[devToolsEnabledPropertyName]!); + + _devToolsUsage.analyticsEnabled = analyticsEnabled; + analytics.setTelemetry(analyticsEnabled); } return api.getCompleted( json.encode(_devToolsUsage.analyticsEnabled), ); + case apiGetConsentMessage: + return api.getCompleted(analytics.getConsentMessage); + case apiMarkConsentMessageAsShown: + analytics.clientShowedMessage(); + return api.getCompleted(json.encode(true)); // ----- DevTools survey store. ----- diff --git a/packages/devtools_shared/pubspec.yaml b/packages/devtools_shared/pubspec.yaml index 9136819658e..f684a24db48 100644 --- a/packages/devtools_shared/pubspec.yaml +++ b/packages/devtools_shared/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: path: ^1.8.0 shelf: ^1.1.0 sse: ^4.1.2 + unified_analytics: ^5.8.1 usage: ^4.0.0 vm_service: ">=13.0.0 <15.0.0" web_socket_channel: ^2.4.0 diff --git a/packages/devtools_shared/test/server/server_api_test.dart b/packages/devtools_shared/test/server/server_api_test.dart index e661531ea4c..16c1faf8cf4 100644 --- a/packages/devtools_shared/test/server/server_api_test.dart +++ b/packages/devtools_shared/test/server/server_api_test.dart @@ -11,6 +11,7 @@ import 'package:devtools_shared/src/extensions/extension_manager.dart'; import 'package:devtools_shared/src/server/server_api.dart'; import 'package:shelf/shelf.dart'; import 'package:test/test.dart'; +import 'package:unified_analytics/unified_analytics.dart'; void main() { test('handle deeplink api ${DeeplinkApi.androidBuildVariants}', () async { @@ -34,6 +35,7 @@ void main() { request, extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: fakeManager, + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.ok); expect(await response.readAsString(), '["debug", "release]'); @@ -55,6 +57,7 @@ void main() { request, extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: FakeDeeplinkManager(), + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.badRequest); }, @@ -84,6 +87,7 @@ void main() { request, extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: fakeManager, + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.ok); expect(await response.readAsString(), someMessage); @@ -113,6 +117,7 @@ void main() { request, extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: fakeManager, + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.ok); expect(await response.readAsString(), someMessage); @@ -145,6 +150,7 @@ void main() { request, extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: fakeManager, + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.ok); expect(await response.readAsString(), someMessage); @@ -168,6 +174,7 @@ void main() { extensionsManager: ExtensionsManager(buildDir: '/'), deeplinkManager: fakeManager, dtdUri: dtdUri, + analytics: NoOpAnalytics(), ); expect(response.statusCode, HttpStatus.ok); expect(