Revert "Convert the AnalyticsManager to use the analytics package"

This reverts commit 0b8986902e.

Reason for revert: Breaks downstream systems

Original change's description:
> Convert the AnalyticsManager to use the analytics package
>
> Change-Id: Id1822668e0ed7e12e88e990fa02247616b2e54e6
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285400
> Reviewed-by: Elias Yishak <eliasyishak@google.com>
> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
> Reviewed-by: Keerti Parthasarathy <keertip@google.com>

Change-Id: Ief9d611cf1259ae460c38fadb299f9e09d74d0f3
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286242
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2023-03-01 19:37:16 +00:00 committed by Commit Queue
parent ca6e3499b8
commit 07c5db4d53
10 changed files with 274 additions and 253 deletions

View file

@ -566,7 +566,7 @@ abstract class AnalysisServer {
});
@mustCallSuper
Future<void> shutdown() async {
void shutdown() {
// For now we record plugins only on shutdown. We might want to record them
// every time the set of plugins changes, in which case we'll need to listen
// to the `PluginManager.pluginsChanged` stream.
@ -578,7 +578,7 @@ abstract class AnalysisServer {
analyticsManager.createdAnalysisContexts(contextManager.analysisContexts);
pubPackageService.shutdown();
await analyticsManager.shutdown();
analyticsManager.shutdown();
}
/// Return the path to the location of the byte store on disk, or `null` if

View file

@ -17,7 +17,7 @@ import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:collection/collection.dart';
import 'package:dash_analytics/dash_analytics.dart';
import 'package:telemetry/telemetry.dart';
/// An interface for managing and reporting analytics.
///
@ -153,15 +153,21 @@ class AnalyticsManager {
}
/// The server is shutting down. Report any accumulated analytics data.
Future<void> shutdown() async {
void shutdown() {
final sessionData = _sessionData;
if (sessionData == null) {
return;
}
await _sendSessionData(sessionData);
await _sendPeriodicData();
_sendSessionData(sessionData);
_sendServerResponseTimes();
_sendPluginResponseTimes();
_sendNotificationHandlingTimes();
_sendLintUsageCounts();
_sendSeverityAdjustments();
analytics.close();
analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
analytics.close();
});
}
/// Record data from the given [params].
@ -205,17 +211,20 @@ class AnalyticsManager {
/// Record that the server was started at the given [time], that it was passed
/// the given command-line [arguments], that it was started by the client with
/// the given [clientId] and [clientVersion].
/// the given [clientId] and [clientVersion], and that it was invoked from an
/// SDK with the given [sdkVersion].
void startUp(
{required DateTime time,
required List<String> arguments,
required String clientId,
required String? clientVersion}) {
required String? clientVersion,
required String sdkVersion}) {
_sessionData = SessionData(
startTime: time,
commandLineArguments: arguments.join(','),
clientId: clientId,
clientVersion: clientVersion ?? '');
clientVersion: clientVersion ?? '',
sdkVersion: sdkVersion);
}
/// Return an HTML representation of the data that has been recorded.
@ -256,6 +265,7 @@ class AnalyticsManager {
li('duration: ${duration.toString()}');
li('flags: ${sessionData.commandLineArguments}');
li('parameters: ${sessionData.initializeParams}');
li('sdkVersion: ${sessionData.sdkVersion}');
li('plugins: ${_pluginData.usageCountData}');
buffer.writeln('</ul>');
@ -351,21 +361,18 @@ class AnalyticsManager {
requestData.responseTimes.addValue(responseTime);
}
/// Send information about the number of times each lint is enabled in an
/// analysis options file.
Future<void> _sendLintUsageCounts() async {
void _sendLintUsageCounts() {
if (_lintUsageCounts.isNotEmpty) {
await analytics
.sendEvent(eventName: DashEvent.lintUsageCounts, eventData: {
analytics.sendEvent('language_server', 'lintUsageCounts', parameters: {
'usageCounts': json.encode(_lintUsageCounts),
});
}
}
/// Send information about the notifications handled by the server.
Future<void> _sendNotificationHandlingTimes() async {
void _sendNotificationHandlingTimes() {
for (var data in _completedNotifications.values) {
await analytics.sendEvent(eventName: DashEvent.notification, eventData: {
analytics.sendEvent('language_server', 'notification', parameters: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.handlingTimes.toAnalyticsString(),
@ -373,23 +380,12 @@ class AnalyticsManager {
}
}
/// Send the information that is sent periodically, which is everything other
/// than the session data.
Future<void> _sendPeriodicData() async {
await _sendServerResponseTimes();
await _sendPluginResponseTimes();
await _sendNotificationHandlingTimes();
await _sendLintUsageCounts();
await _sendSeverityAdjustments();
}
/// Send information about the response times of plugins.
Future<void> _sendPluginResponseTimes() async {
void _sendPluginResponseTimes() {
var responseTimes = PluginManager.pluginResponseTimes;
for (var pluginEntry in responseTimes.entries) {
for (var responseEntry in pluginEntry.value.entries) {
await analytics
.sendEvent(eventName: DashEvent.pluginRequest, eventData: {
analytics.sendEvent('language_server', 'pluginRequest', parameters: {
'pluginId': pluginEntry.key.pluginId,
'method': responseEntry.key,
'duration': responseEntry.value.toAnalyticsString(),
@ -399,9 +395,9 @@ class AnalyticsManager {
}
/// Send information about the response times of server.
Future<void> _sendServerResponseTimes() async {
void _sendServerResponseTimes() {
for (var data in _completedRequests.values) {
await analytics.sendEvent(eventName: DashEvent.request, eventData: {
analytics.sendEvent('language_server', 'request', parameters: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.responseTimes.toAnalyticsString(),
@ -414,25 +410,24 @@ class AnalyticsManager {
}
/// Send information about the session.
Future<void> _sendSessionData(SessionData sessionData) async {
void _sendSessionData(SessionData sessionData) {
var endTime = DateTime.now().millisecondsSinceEpoch;
var duration = endTime - sessionData.startTime.millisecondsSinceEpoch;
await analytics.sendEvent(eventName: DashEvent.session, eventData: {
analytics.sendEvent('language_server', 'session', parameters: {
'flags': sessionData.commandLineArguments,
'parameters': sessionData.initializeParams,
'clientId': sessionData.clientId,
'clientVersion': sessionData.clientVersion,
'sdkVersion': sessionData.sdkVersion,
'duration': duration.toString(),
'plugins': _pluginData.usageCountData,
});
}
/// Send information about the number of times that the severity of a
/// diagnostic is changed in an analysis options file.
Future<void> _sendSeverityAdjustments() async {
void _sendSeverityAdjustments() {
if (_severityAdjustments.isNotEmpty) {
await analytics
.sendEvent(eventName: DashEvent.severityAdjustments, eventData: {
analytics
.sendEvent('language_server', 'severityAdjustments', parameters: {
'adjustmentCounts': json.encode(_severityAdjustments),
});
}

View file

@ -2,30 +2,29 @@
// 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 'package:dash_analytics/dash_analytics.dart';
import 'package:dash_analytics/src/config_handler.dart';
import 'package:http/src/response.dart';
import 'package:telemetry/telemetry.dart';
/// An implementation of [Analytics] that's appropriate to use when running
/// tests.
// TODO(brianwilkerson) Remove this class when it's easier to create a test
// version of the `Analytics` class.
class NoopAnalytics implements Analytics {
/// An implementation of [Analytics] that's appropriate to use when analytics
/// have not been enabled.
class NoopAnalytics extends Analytics {
@override
Map<String, ToolInfo> get parsedTools => throw UnimplementedError();
String? get applicationName => null;
@override
bool get shouldShowMessage => false;
String? get applicationVersion => null;
@override
bool get telemetryEnabled => false;
bool get enabled => false;
@override
String get toolsMessage => throw UnimplementedError();
set enabled(bool value) {
// Ignored
}
@override
Map<String, Map<String, Object?>> get userPropertyMap =>
throw UnimplementedError();
Stream<Map<String, dynamic>> get onSend async* {
// Ignored
}
@override
void close() {
@ -33,20 +32,50 @@ class NoopAnalytics implements Analytics {
}
@override
LogFileStats? logFileStats() {
throw UnimplementedError();
}
@override
Future<Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
getSessionValue(String param) {
// Ignored
return null;
}
@override
Future<void> setTelemetry(bool reportingBool) {
dynamic noSuchMethod(Invocation invocation) {
throw UnimplementedError();
}
@override
Future sendEvent(String category, String action,
{String? label, int? value, Map<String, String>? parameters}) async {
// Ignored
}
@override
Future sendException(String description, {bool? fatal}) async {
// Ignored
}
@override
Future sendScreenView(String viewName,
{Map<String, String>? parameters}) async {
// Ignored
}
@override
Future sendSocial(String network, String action, String target) async {
// Ignored
}
@override
Future sendTiming(String variableName, int time,
{String? category, String? label}) async {
// Ignored
}
@override
void setSessionValue(String param, value) {
// Ignored
}
@override
Future waitForLastPing({Duration? timeout}) async {
// Ignored
}
}

View file

@ -20,10 +20,14 @@ class SessionData {
/// no version information was provided.
final String clientVersion;
/// The version of the SDK from which the server was started.
final String sdkVersion;
/// Initialize a newly created data holder.
SessionData(
{required this.startTime,
required this.commandLineArguments,
required this.clientId,
required this.clientVersion});
required this.clientVersion,
required this.sdkVersion});
}

View file

@ -695,8 +695,8 @@ class LegacyAnalysisServer extends AnalysisServer {
}
@override
Future<void> shutdown() async {
await super.shutdown();
Future<void> shutdown() {
super.shutdown();
pubApi.close();
@ -704,21 +704,21 @@ class LegacyAnalysisServer extends AnalysisServer {
// analyticsManager is being correctly initialized.
var analytics = options.analytics;
if (analytics != null) {
unawaited(analytics
.waitForLastPing(timeout: Duration(milliseconds: 200))
.then((_) {
analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
analytics.close();
}));
});
}
detachableFileSystemManager?.dispose();
// Defer closing the channel and shutting down the instrumentation server so
// that the shutdown response can be sent and logged.
unawaited(Future(() {
Future(() {
instrumentationService.shutdown();
channel.close();
}));
});
return Future.value();
}
/// Implementation for `analysis.updateContent`.

View file

@ -368,8 +368,6 @@ class LspAnalysisServer extends AnalysisServer {
performanceAfterStartup = ServerPerformance();
performance = performanceAfterStartup!;
_checkAnalytics();
}
/// Handles a response from the client by invoking the completer that the
@ -685,7 +683,7 @@ class LspAnalysisServer extends AnalysisServer {
logErrorToClient(
'$message\n\n${error.message}\n\n${error.code}\n\n${error.data}');
unawaited(shutdown());
shutdown();
}
}
@ -846,17 +844,19 @@ class LspAnalysisServer extends AnalysisServer {
}
@override
Future<void> shutdown() async {
await super.shutdown();
Future<void> shutdown() {
super.shutdown();
detachableFileSystemManager?.dispose();
// Defer closing the channel so that the shutdown response can be sent and
// logged.
unawaited(Future(() {
Future(() {
channel.close();
}));
unawaited(_pluginChangeSubscription?.cancel());
});
_pluginChangeSubscription?.cancel();
return Future.value();
}
/// There was an error related to the socket from which messages are being
@ -896,20 +896,6 @@ class LspAnalysisServer extends AnalysisServer {
notifyFlutterWidgetDescriptions(path);
}
/// Display a message that will allow us to enable analytics on the next run.
void _checkAnalytics() {
var dashAnalytics = analyticsManager.analytics;
// The order of the conditions below is important. We need to check whether
// the client supports `showMessageRequest` before asking whether we need to
// show the message because the `Analytics` instance assumes that if
// `shouldShowMessage` returns `true` then the message will be shown.
if (_clientCapabilities!.supportsShowMessageRequest &&
dashAnalytics.shouldShowMessage) {
unawaited(
showUserPrompt(MessageType.Info, dashAnalytics.toolsMessage, ['Ok']));
}
}
/// Computes analysis roots for a set of open files.
///
/// This is used when there are no workspace folders open directly.

View file

@ -182,13 +182,13 @@ class Driver implements ServerStarter {
var sdkConfig = SdkConfiguration.readFromSdk();
analysisServerOptions.configurationOverrides = sdkConfig;
// Legacy Analytics
// Analytics
var disableAnalyticsForSession = results[SUPPRESS_ANALYTICS_FLAG] as bool;
if (results.wasParsed(TRAIN_USING)) {
disableAnalyticsForSession = true;
}
// Use sdkConfig to optionally override legacy analytics settings.
// Use sdkConfig to optionally override analytics settings.
final analyticsId = sdkConfig.analyticsId ?? 'UA-26406144-29';
final forceAnalyticsEnabled = sdkConfig.analyticsForceEnabled == true;
var analytics = telemetry.createAnalyticsInstance(
@ -204,6 +204,7 @@ class Driver implements ServerStarter {
if (analysisServerOptions.clientVersion != null) {
analytics.setSessionValue('cd1', analysisServerOptions.clientVersion);
}
var analyticsManager = AnalyticsManager(NoopAnalytics());
bool shouldSendCallback() {
// Check sdkConfig to optionally force reporting on.
@ -254,26 +255,15 @@ class Driver implements ServerStarter {
// can't be re-used, but the SDK is needed to create a package map provider
// in the case where we need to run `pub` in order to get the package map.
var defaultSdk = _createDefaultSdk(defaultSdkPath);
// Create the analytics manager.
// TODO(brianwilkerson) Add support for finding the version of the Flutter
// SDK, or `null` if we're not in a Flutter SDK.
// TODO(brianwilkerson) Delete the line below and uncomment the 3 lines
// below it when we have (a) fixed the `dart analyze` and `dart fix` tests
// so that they don't send events and (b) implemented a way for users to
// disable analytics.
var analyticsManager = AnalyticsManager(NoopAnalytics());
// var analyticsManager = AnalyticsManager(Analytics(
// tool: DashTool.languageServer,
// dartVersion: defaultSdk.sdkVersion,
// flutterVersion: null));
//
// Record the start of the session.
//
analyticsManager.startUp(
time: sessionStartTime,
arguments: _getArgumentsForAnalytics(results),
clientId: clientId,
clientVersion: analysisServerOptions.clientVersion);
clientVersion: analysisServerOptions.clientVersion,
sdkVersion: defaultSdk.sdkVersion);
//
// Initialize the instrumentation service.
//

View file

@ -10,6 +10,7 @@ import 'package:analysis_server/protocol/protocol_constants.dart'
show PROTOCOL_VERSION;
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/analytics/noop_analytics.dart';
import 'package:analysis_server/src/legacy_analysis_server.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'
show LspAnalysisServer;
@ -159,44 +160,39 @@ String writeOption(String name, dynamic value) {
return '$name: <code>$value</code><br> ';
}
// TODO(brianwilkerson) This page is disabled for production until we determine
// whether we want to display some information to the user and if so what
// information we want to display.
//
// class AnalyticsPage extends DiagnosticPageWithNav {
// AnalyticsPage(DiagnosticsSite site)
// : super(site, 'analytics', 'Analytics',
// description: 'Analytics gathered by the analysis server.');
//
// @override
// String? get navDetail => null;
//
// @override
// Future generateContent(Map<String, String> params) async {
// var manager = server.analyticsManager;
// //
// // Display the standard header.
// //
// if (!manager.analytics.telemetryEnabled) {
// p('Analytics reporting disabled. In order to enable it, run:');
// p('&nbsp;&nbsp;<code>dart --enable-analytics</code>');
// p('If analytics had been enabled, the information below would have been '
// 'reported.');
// } else {
// p('The Dart tool uses Google Analytics to report feature usage '
// 'statistics and to send basic crash reports. This data is used to '
// 'help improve the Dart platform and tools over time.');
// p('To disable reporting of analytics, run:');
// p('&nbsp;&nbsp;<code>dart --disable-analytics</code>');
// p('The information below will be reported the next time analytics are '
// 'sent.');
// }
// //
// // Display the analytics data that has been gathered.
// //
// manager.toHtml(buf);
// }
// }
class AnalyticsPage extends DiagnosticPageWithNav {
AnalyticsPage(DiagnosticsSite site)
: super(site, 'analytics', 'Analytics',
description: 'Analytics gathered by the analysis server.');
@override
String? get navDetail => null;
@override
Future generateContent(Map<String, String> params) async {
var manager = server.analyticsManager;
//
// Display the standard header.
//
if (manager.analytics is NoopAnalytics) {
p('Analytics reporting disabled. In order to enable it, run:');
p('&nbsp;&nbsp;<code>dart --enable-analytics</code>');
p('If analytics had been enabled, the information below would be '
'reported on shutdown.');
} else {
p('The Dart tool uses Google Analytics to report feature usage '
'statistics and to send basic crash reports. This data is used to '
'help improve the Dart platform and tools over time.');
p('To disable reporting of analytics, run:');
p('&nbsp;&nbsp;<code>dart --disable-analytics</code>');
p('The information below will be reported on shutdown.');
}
//
// Display the analytics data that has been gathered.
//
manager.toHtml(buf);
}
}
class AstPage extends DiagnosticPageWithNav {
String? _description;

View file

@ -15,7 +15,6 @@ dependencies:
convert: any
crypto: any
dart_style: any
dash_analytics: any
http: any
linter: any
meta: any

View file

@ -15,10 +15,8 @@ import 'package:analyzer/dart/analysis/context_root.dart' as analyzer;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:dash_analytics/dash_analytics.dart';
import 'package:dash_analytics/src/config_handler.dart';
import 'package:http/src/response.dart' as http;
import 'package:linter/src/rules.dart';
import 'package:telemetry/telemetry.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@ -37,7 +35,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
String get testPackageRootPath => testPackageRoot.path;
Future<void> test_createAnalysisContexts_lints() async {
void test_createAnalysisContexts_lints() {
_createAnalysisOptionsFile(lints: [
'avoid_dynamic_calls',
'await_only_futures',
@ -46,17 +44,17 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var collection = _createContexts();
_defaultStartup();
manager.createdAnalysisContexts(collection.contexts);
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.lintUsageCounts(eventData: {
_ExpectedEvent.lintUsageCounts(parameters: {
'usageCounts':
'{"avoid_dynamic_calls":1,"await_only_futures":1,"unawaited_futures":1}',
}),
]);
}
Future<void> test_createAnalysisContexts_severityAdjustments() async {
void test_createAnalysisContexts_severityAdjustments() {
_createAnalysisOptionsFile(errors: {
'avoid_dynamic_calls': 'error',
'await_only_futures': 'ignore',
@ -64,25 +62,25 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var collection = _createContexts();
_defaultStartup();
manager.createdAnalysisContexts(collection.contexts);
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.severityAdjustments(eventData: {
_ExpectedEvent.severityAdjustments(parameters: {
'adjustmentCounts':
'{"AVOID_DYNAMIC_CALLS":{"ERROR":1},"AWAIT_ONLY_FUTURES":{"ignore":1}}',
}),
]);
}
Future<void> test_plugin_request() async {
void test_plugin_request() {
_defaultStartup();
PluginManager.pluginResponseTimes[_MockPluginInfo('a')] = {
'analysis.getNavigation': PercentileCalculator(),
};
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.pluginRequest(eventData: {
_ExpectedEvent.pluginRequest(parameters: {
'pluginId': 'a',
'method': 'analysis.getNavigation',
'duration': _IsPercentiles(),
@ -91,7 +89,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
PluginManager.pluginResponseTimes.clear();
}
Future<void> test_server_notification() async {
void test_server_notification() {
_defaultStartup();
manager.handledNotificationMessage(
notification: NotificationMessage(
@ -100,10 +98,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
method: Method.workspace_didCreateFiles),
startTime: _now(),
endTime: _now());
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.notification(eventData: {
_ExpectedEvent.notification(parameters: {
'latency': _IsPercentiles(),
'method': Method.workspace_didCreateFiles.toString(),
'duration': _IsPercentiles(),
@ -111,7 +109,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_analysisDidChangeWorkspaceFolders() async {
void test_server_request_analysisDidChangeWorkspaceFolders() {
_defaultStartup();
var params = DidChangeWorkspaceFoldersParams(
event: WorkspaceFoldersChangeEvent(added: [], removed: []));
@ -125,10 +123,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
.changedWorkspaceFolders(added: ['a', 'b', 'c'], removed: ['d', 'e']);
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': Method.workspace_didChangeWorkspaceFolders.toString(),
'duration': _IsPercentiles(),
@ -140,7 +138,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_analysisSetAnalysisRoots() async {
void test_server_request_analysisSetAnalysisRoots() {
_defaultStartup();
var params = AnalysisSetAnalysisRootsParams(['a', 'b', 'c'], ['d', 'e']);
var request =
@ -148,10 +146,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedSetAnalysisRoots(params);
manager.sentResponse(response: Response('1'));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS,
'duration': _IsPercentiles(),
@ -163,7 +161,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_analysisSetPriorityFiles() async {
void test_server_request_analysisSetPriorityFiles() {
_defaultStartup();
var params = AnalysisSetPriorityFilesParams(['a']);
var request =
@ -171,10 +169,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedSetPriorityFiles(params);
manager.sentResponse(response: Response('1'));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': ANALYSIS_REQUEST_SET_PRIORITY_FILES,
'duration': _IsPercentiles(),
@ -184,7 +182,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_editGetRefactoring() async {
void test_server_request_editGetRefactoring() {
_defaultStartup();
var params =
EditGetRefactoringParams(RefactoringKind.RENAME, '', 0, 0, true);
@ -192,10 +190,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedGetRefactoring(params);
manager.sentResponse(response: Response('1'));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': EDIT_REQUEST_GET_REFACTORING,
'duration': _IsPercentiles(),
@ -204,7 +202,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_initialize() async {
void test_server_request_initialize() {
_defaultStartup();
var params = InitializeParams(
capabilities: ClientCapabilities(),
@ -214,16 +212,16 @@ class AnalyticsManagerTest with ResourceProviderMixin {
'onlyAnalyzeProjectsWithOpenFiles': true,
});
manager.initialize(params);
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(eventData: {
_ExpectedEvent.session(parameters: {
'parameters':
'closingLabels,onlyAnalyzeProjectsWithOpenFiles,suggestFromUnimportedLibraries',
}),
]);
}
Future<void> test_server_request_initialized() async {
void test_server_request_initialized() {
_defaultStartup();
var params = InitializedParams();
var request = RequestMessage(
@ -235,10 +233,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.initialized(openWorkspacePaths: ['a', 'b', 'c']);
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': Method.initialized.toString(),
'duration': _IsPercentiles(),
@ -248,15 +246,15 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_noAdditional() async {
void test_server_request_noAdditional() {
_defaultStartup();
manager.startedRequest(
request: Request('1', SERVER_REQUEST_SHUTDOWN), startTime: _now());
manager.sentResponse(response: Response('1'));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': SERVER_REQUEST_SHUTDOWN,
'duration': _IsPercentiles(),
@ -264,7 +262,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_server_request_workspaceExecuteCommand() async {
void test_server_request_workspaceExecuteCommand() {
_defaultStartup();
var params = ExecuteCommandParams(command: 'doIt');
var request = RequestMessage(
@ -277,10 +275,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.executedCommand('doIt');
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
await manager.shutdown();
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(eventData: {
_ExpectedEvent.request(parameters: {
'latency': _IsPercentiles(),
'method': Method.workspace_executeCommand.toString(),
'duration': _IsPercentiles(),
@ -289,62 +287,68 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
Future<void> test_shutdownWithoutStartup() async {
await manager.shutdown();
void test_shutdownWithoutStartup() {
manager.shutdown();
analytics.assertNoEvents();
}
Future<void> test_startup_withoutVersion() async {
void test_startup_withoutVersion() {
var arguments = ['a', 'b'];
var clientId = 'clientId';
var sdkVersion = 'sdkVersion';
manager.startUp(
time: DateTime.now(),
arguments: arguments,
clientId: clientId,
clientVersion: null);
await manager.shutdown();
clientVersion: null,
sdkVersion: sdkVersion);
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(eventData: {
_ExpectedEvent.session(parameters: {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': '',
'sdkVersion': sdkVersion,
'duration': _IsStringEncodedPositiveInt(),
}),
]);
}
Future<void> test_startup_withPlugins() async {
void test_startup_withPlugins() {
_defaultStartup();
manager.changedPlugins(_MockPluginManager(plugins: [
_MockPluginInfo('a'),
_MockPluginInfo('b'),
]));
await manager.shutdown();
manager.shutdown();
var counts =
'{"count":1,"percentiles":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}';
analytics.assertEvents([
_ExpectedEvent.session(eventData: {
_ExpectedEvent.session(parameters: {
'plugins': '{"recordCount":1,"rootCounts":{"a":$counts,"b":$counts}}'
}),
]);
}
Future<void> test_startup_withVersion() async {
void test_startup_withVersion() {
var arguments = ['a', 'b'];
var clientId = 'clientId';
var clientVersion = 'clientVersion';
var sdkVersion = 'sdkVersion';
manager.startUp(
time: DateTime.now(),
arguments: arguments,
clientId: clientId,
clientVersion: clientVersion);
await manager.shutdown();
clientVersion: clientVersion,
sdkVersion: sdkVersion);
manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(eventData: {
_ExpectedEvent.session(parameters: {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': clientVersion,
'': isNull,
'sdkVersion': sdkVersion,
'duration': _IsStringEncodedPositiveInt(),
}),
]);
@ -407,7 +411,11 @@ class AnalyticsManagerTest with ResourceProviderMixin {
void _defaultStartup() {
manager.startUp(
time: DateTime.now(), arguments: [], clientId: '', clientVersion: null);
time: DateTime.now(),
arguments: [],
clientId: '',
clientVersion: null,
sdkVersion: '');
}
DateTime _now() => DateTime.now();
@ -415,47 +423,66 @@ class AnalyticsManagerTest with ResourceProviderMixin {
/// A record of an event that was reported to analytics.
class _Event {
final DashEvent eventName;
final Map<String, Object?> eventData;
final String category;
final String action;
final String? label;
final int? value;
final Map<String, String>? parameters;
_Event(this.eventName, this.eventData);
_Event(this.category, this.action, this.label, this.value, this.parameters);
}
/// A record of an event that was reported to analytics.
class _ExpectedEvent {
final DashEvent eventName;
final Map<String, Object?>? eventData;
final String category;
final String action;
final String? label;
final int? value;
final Map<String, Object>? parameters;
_ExpectedEvent(this.eventName, this.eventData);
_ExpectedEvent(this.category, this.action,
{this.label, // ignore: unused_element
this.value, // ignore: unused_element
this.parameters});
_ExpectedEvent.lintUsageCounts({Map<String, Object?>? eventData})
: this(DashEvent.lintUsageCounts, eventData);
_ExpectedEvent.lintUsageCounts({Map<String, Object>? parameters})
: this('language_server', 'lintUsageCounts', parameters: parameters);
_ExpectedEvent.notification({Map<String, Object?>? eventData})
: this(DashEvent.notification, eventData);
_ExpectedEvent.notification({Map<String, Object>? parameters})
: this('language_server', 'notification', parameters: parameters);
_ExpectedEvent.pluginRequest({Map<String, Object?>? eventData})
: this(DashEvent.pluginRequest, eventData);
_ExpectedEvent.pluginRequest({Map<String, Object>? parameters})
: this('language_server', 'pluginRequest', parameters: parameters);
_ExpectedEvent.request({Map<String, Object?>? eventData})
: this(DashEvent.request, eventData);
_ExpectedEvent.request({Map<String, Object>? parameters})
: this('language_server', 'request', parameters: parameters);
_ExpectedEvent.session({Map<String, Object?>? eventData})
: this(DashEvent.session, eventData);
_ExpectedEvent.session({Map<String, Object>? parameters})
: this('language_server', 'session', parameters: parameters);
_ExpectedEvent.severityAdjustments({Map<String, Object?>? eventData})
: this(DashEvent.severityAdjustments, eventData);
_ExpectedEvent.severityAdjustments({Map<String, Object>? parameters})
: this('language_server', 'severityAdjustments', parameters: parameters);
/// Compare the expected event with the [actual] event, failing if the actual
/// doesn't match the expected.
void matches(_Event actual) {
expect(actual.eventName, eventName);
final actualData = actual.eventData;
final expectedData = eventData;
if (expectedData != null) {
for (var expectedKey in expectedData.keys) {
var actualValue = actualData[expectedKey];
var expectedValue = expectedData[expectedKey];
expect(actual.category, category);
expect(actual.action, action);
if (label != null) {
expect(actual.label, label);
}
if (value != null) {
expect(actual.value, value);
}
final actualParameters = actual.parameters;
final expectedParameters = parameters;
if (expectedParameters != null) {
if (actualParameters == null) {
fail('Expected parameters but found none');
}
for (var expectedKey in expectedParameters.keys) {
var actualValue = actualParameters[expectedKey];
var expectedValue = expectedParameters[expectedKey];
expect(actualValue, expectedValue, reason: 'For key $expectedKey');
}
}
@ -464,11 +491,17 @@ class _ExpectedEvent {
@override
String toString() {
var buffer = StringBuffer();
buffer.write('eventData: ');
buffer.writeln(eventData);
var data = eventData;
if (data != null) {
for (var entry in data.entries) {
buffer.write('category: ');
buffer.writeln(category);
buffer.write('action: ');
buffer.writeln(action);
buffer.write('label: ');
buffer.writeln(label);
buffer.write('value: ');
buffer.writeln(value);
var parameterMap = parameters;
if (parameterMap != null) {
for (var entry in parameterMap.entries) {
buffer.write('value: ');
buffer.writeln('${entry.key}: ${entry.value}');
}
@ -533,15 +566,6 @@ class _MockAnalytics implements Analytics {
_MockAnalytics();
@override
Map<String, ToolInfo> get parsedTools => throw UnimplementedError();
@override
bool get shouldShowMessage => false;
@override
bool get telemetryEnabled => false;
void assertEvents(List<_ExpectedEvent> expectedEvents) {
var expectedCount = expectedEvents.length;
expect(events, hasLength(expectedCount));
@ -556,23 +580,21 @@ class _MockAnalytics implements Analytics {
@override
void close() {
// Ignored
}
@override
LogFileStats? logFileStats() {
throw UnimplementedError();
// ignored
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
Future<http.Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
events.add(_Event(eventName, eventData));
return null;
Future sendEvent(String category, String action,
{String? label, int? value, Map<String, String>? parameters}) async {
events.add(_Event(category, action, label, value, parameters));
}
@override
Future waitForLastPing({Duration? timeout}) async {
// ignored
}
}