Unverified Commit 76cbbeb6 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Send the local time to analytics with screens and events (#36545)

parent 6830edd0
...@@ -12,6 +12,7 @@ import '../base/context.dart'; ...@@ -12,6 +12,7 @@ import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/time.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -19,6 +20,9 @@ import '../version.dart'; ...@@ -19,6 +20,9 @@ import '../version.dart';
const String _kFlutterUA = 'UA-67589403-6'; const String _kFlutterUA = 'UA-67589403-6';
// Attached to all `Usage.sendCommand` and `Usage.sendEvent`.
const String _kLocalTimeParameter = 'cd33';
const String kSessionHostOsDetails = 'cd1'; const String kSessionHostOsDetails = 'cd1';
const String kSessionChannelName = 'cd2'; const String kSessionChannelName = 'cd2';
...@@ -59,7 +63,7 @@ const String reloadExceptionEmulator = 'cd29'; ...@@ -59,7 +63,7 @@ const String reloadExceptionEmulator = 'cd29';
const String reloadExceptionFullRestart = 'cd30'; const String reloadExceptionFullRestart = 'cd30';
const String enabledFlutterFeatures = 'cd32'; const String enabledFlutterFeatures = 'cd32';
// Next ID: cd33 // Next ID: cd34
Usage get flutterUsage => Usage.instance; Usage get flutterUsage => Usage.instance;
...@@ -141,12 +145,16 @@ class Usage { ...@@ -141,12 +145,16 @@ class Usage {
String get clientId => _analytics.clientId; String get clientId => _analytics.clientId;
void sendCommand(String command, { Map<String, String> parameters }) { void sendCommand(String command, { Map<String, String> parameters }) {
if (suppressAnalytics) if (suppressAnalytics) {
return; return;
}
parameters ??= const <String, String>{}; final Map<String, String> paramsWithLocalTime = <String, String>{
...?parameters,
_kLocalTimeParameter: systemClock.now().toString(),
};
_analytics.sendScreenView(command, parameters: parameters); _analytics.sendScreenView(command, parameters: paramsWithLocalTime);
} }
void sendEvent( void sendEvent(
...@@ -154,12 +162,16 @@ class Usage { ...@@ -154,12 +162,16 @@ class Usage {
String parameter, { String parameter, {
Map<String, String> parameters, Map<String, String> parameters,
}) { }) {
if (suppressAnalytics) if (suppressAnalytics) {
return; return;
}
parameters ??= const <String, String>{}; final Map<String, String> paramsWithLocalTime = <String, String>{
...?parameters,
_kLocalTimeParameter: systemClock.now().toString(),
};
_analytics.sendEvent(category, parameter, parameters: parameters); _analytics.sendEvent(category, parameter, parameters: paramsWithLocalTime);
} }
void sendTiming( void sendTiming(
...@@ -168,19 +180,22 @@ class Usage { ...@@ -168,19 +180,22 @@ class Usage {
Duration duration, { Duration duration, {
String label, String label,
}) { }) {
if (!suppressAnalytics) { if (suppressAnalytics) {
_analytics.sendTiming( return;
variableName,
duration.inMilliseconds,
category: category,
label: label,
);
} }
_analytics.sendTiming(
variableName,
duration.inMilliseconds,
category: category,
label: label,
);
} }
void sendException(dynamic exception) { void sendException(dynamic exception) {
if (!suppressAnalytics) if (suppressAnalytics) {
_analytics.sendException(exception.runtimeType.toString()); return;
}
_analytics.sendException(exception.runtimeType.toString());
} }
/// Fires whenever analytics data is sent over the network. /// Fires whenever analytics data is sent over the network.
...@@ -199,8 +214,9 @@ class Usage { ...@@ -199,8 +214,9 @@ class Usage {
void printWelcome() { void printWelcome() {
// This gets called if it's the first run by the selected command, if any, // This gets called if it's the first run by the selected command, if any,
// and on exit, in case there was no command. // and on exit, in case there was no command.
if (_printedWelcome) if (_printedWelcome) {
return; return;
}
_printedWelcome = true; _printedWelcome = true;
printStatus(''); printStatus('');
...@@ -239,7 +255,17 @@ class LogToFileAnalytics extends AnalyticsMock { ...@@ -239,7 +255,17 @@ class LogToFileAnalytics extends AnalyticsMock {
Future<void> sendScreenView(String viewName, {Map<String, String> parameters}) { Future<void> sendScreenView(String viewName, {Map<String, String> parameters}) {
parameters ??= <String, String>{}; parameters ??= <String, String>{};
parameters['viewName'] = viewName; parameters['viewName'] = viewName;
logFile.writeAsStringSync('screenView $parameters\n'); logFile.writeAsStringSync('screenView $parameters\n', mode: FileMode.append);
return Future<void>.value(null);
}
@override
Future<void> sendEvent(String category, String action,
{String label, int value, Map<String, String> parameters}) {
parameters ??= <String, String>{};
parameters['category'] = category;
parameters['action'] = action;
logFile.writeAsStringSync('event $parameters\n', mode: FileMode.append);
return Future<void>.value(null); return Future<void>.value(null);
} }
} }
...@@ -3,23 +3,26 @@ ...@@ -3,23 +3,26 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/config.dart'; import 'package:flutter_tools/src/commands/config.dart';
import 'package:flutter_tools/src/commands/doctor.dart'; import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/usage.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/mocks.dart';
void main() { void main() {
group('analytics', () { group('analytics', () {
...@@ -123,12 +126,16 @@ void main() { ...@@ -123,12 +126,16 @@ void main() {
}); });
group('analytics with mocks', () { group('analytics with mocks', () {
MemoryFileSystem memoryFileSystem;
MockStdio mockStdio;
Usage mockUsage; Usage mockUsage;
SystemClock mockClock; SystemClock mockClock;
Doctor mockDoctor; Doctor mockDoctor;
List<int> mockTimes; List<int> mockTimes;
setUp(() { setUp(() {
memoryFileSystem = MemoryFileSystem();
mockStdio = MockStdio();
mockUsage = MockUsage(); mockUsage = MockUsage();
when(mockUsage.isFirstRun).thenReturn(false); when(mockUsage.isFirstRun).thenReturn(false);
mockClock = MockClock(); mockClock = MockClock();
...@@ -190,6 +197,56 @@ void main() { ...@@ -190,6 +197,56 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => mockUsage, Usage: () => mockUsage,
}); });
testUsingContext('command sends localtime', () async {
const int kMillis = 1000;
mockTimes = <int>[kMillis];
// Since FLUTTER_ANALYTICS_LOG_FILE is set in the environment, analytics
// will be written to a file.
final Usage usage = Usage(versionOverride: 'test');
usage.suppressAnalytics = false;
usage.enabled = true;
usage.sendCommand('test');
final String log = fs.file('analytics.log').readAsStringSync();
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(kMillis);
expect(log.contains(dateTime.toString()), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
SystemClock: () => mockClock,
Platform: () => FakePlatform(
environment: <String, String>{
'FLUTTER_ANALYTICS_LOG_FILE': 'analytics.log',
},
),
Stdio: () => mockStdio,
});
testUsingContext('event sends localtime', () async {
const int kMillis = 1000;
mockTimes = <int>[kMillis];
// Since FLUTTER_ANALYTICS_LOG_FILE is set in the environment, analytics
// will be written to a file.
final Usage usage = Usage(versionOverride: 'test');
usage.suppressAnalytics = false;
usage.enabled = true;
usage.sendEvent('test', 'test');
final String log = fs.file('analytics.log').readAsStringSync();
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(kMillis);
expect(log.contains(dateTime.toString()), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
SystemClock: () => mockClock,
Platform: () => FakePlatform(
environment: <String, String>{
'FLUTTER_ANALYTICS_LOG_FILE': 'analytics.log',
},
),
Stdio: () => mockStdio,
});
}); });
group('analytics bots', () { group('analytics bots', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment