Unverified Commit 2b218fd1 authored by Elias Yishak's avatar Elias Yishak Committed by GitHub

Migrate command usage values (#139383)

Related to the tracker issue:
- https://github.com/flutter/flutter/issues/128251

This PR migrates the `Usage.command` static method that sent custom dimensions for each command (if applicable). The screenshot below shows the different places where the `usageValues` getter is overwritten to return the necessary custom dimensions for that command.

<img width="285" alt="image" src="https://github.com/flutter/flutter/assets/42216813/e32d5100-0e17-4a4d-8f21-327a8c113a19">
parent 2c22944d
......@@ -4,6 +4,7 @@
import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../artifacts.dart';
import '../base/common.dart';
......@@ -126,6 +127,8 @@ class AssembleCommand extends FlutterCommand {
final BuildSystem _buildSystem;
late final FlutterProject _flutterProject = FlutterProject.current();
@override
String get description => 'Assemble and build Flutter resources.';
......@@ -136,18 +139,18 @@ class AssembleCommand extends FlutterCommand {
String get category => FlutterCommandCategory.project;
@override
Future<CustomDimensions> get usageValues async {
final FlutterProject flutterProject = FlutterProject.current();
try {
return CustomDimensions(
commandBuildBundleTargetPlatform: _environment.defines[kTargetPlatform],
commandBuildBundleIsModule: flutterProject.isModule,
);
} on Exception {
// We've failed to send usage.
}
return const CustomDimensions();
}
Future<CustomDimensions> get usageValues async => CustomDimensions(
commandBuildBundleTargetPlatform: _environment.defines[kTargetPlatform],
commandBuildBundleIsModule: _flutterProject.isModule,
);
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async => Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildBundleTargetPlatform: _environment.defines[kTargetPlatform],
buildBundleIsModule: _flutterProject.isModule,
);
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
......@@ -208,22 +211,21 @@ class AssembleCommand extends FlutterCommand {
/// The environmental configuration for a build invocation.
Environment _createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current();
String? output = stringArg('output');
if (output == null) {
throwToolExit('--output directory is required for assemble.');
}
// If path is relative, make it absolute from flutter project.
if (globals.fs.path.isRelative(output)) {
output = globals.fs.path.join(flutterProject.directory.path, output);
output = globals.fs.path.join(_flutterProject.directory.path, output);
}
final Artifacts artifacts = globals.artifacts!;
final Environment result = Environment(
outputDir: globals.fs.directory(output),
buildDir: flutterProject.directory
buildDir: _flutterProject.directory
.childDirectory('.dart_tool')
.childDirectory('flutter_build'),
projectDir: flutterProject.directory,
projectDir: _flutterProject.directory,
defines: _parseDefines(stringsArg('define')),
inputs: _parseDefines(stringsArg('input')),
cacheDir: globals.cache.getRoot(),
......@@ -266,7 +268,7 @@ class AssembleCommand extends FlutterCommand {
}
results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
if (_flutterProject.manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true';
}
if (argumentResults.wasParsed(FlutterOptions.kExtraFrontEndOptions)) {
......@@ -297,7 +299,7 @@ class AssembleCommand extends FlutterCommand {
"Try re-running 'flutter build ios' or the appropriate build command."
);
}
if (FlutterProject.current().manifest.deferredComponents != null
if (_flutterProject.manifest.deferredComponents != null
&& decodedDefines.contains('validate-deferred-components=true')
&& deferredTargets.isNotEmpty
&& !isDebug()) {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:unified_analytics/unified_analytics.dart';
import '../android/android_builder.dart';
import '../android/android_sdk.dart';
import '../android/gradle_utils.dart';
......@@ -94,6 +96,25 @@ class BuildAarCommand extends BuildSubCommand {
);
}
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final String projectType;
if (project.manifest.isModule) {
projectType = 'module';
} else if (project.manifest.isPlugin) {
projectType = 'plugin';
} else {
projectType = 'app';
}
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildAarProjectType: projectType,
buildAarTargetPlatform: stringsArg('target-platform').join(','),
);
}
@override
final String description = 'Build a repository containing an AAR and a POM file.\n\n'
'By default, AARs are built for `release`, `debug` and `profile`.\n'
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:unified_analytics/unified_analytics.dart';
import '../android/android_builder.dart';
import '../android/build_validation.dart';
import '../android/gradle_utils.dart';
......@@ -98,6 +100,30 @@ class BuildApkCommand extends BuildSubCommand {
);
}
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final String buildMode;
if (boolArg('release')) {
buildMode = 'release';
} else if (boolArg('debug')) {
buildMode = 'debug';
} else if (boolArg('profile')) {
buildMode = 'profile';
} else {
// The build defaults to release.
buildMode = 'release';
}
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildApkTargetPlatform: stringsArg('target-platform').join(','),
buildApkBuildMode: buildMode,
buildApkSplitPerAbi: boolArg('split-per-abi'),
);
}
@override
Future<FlutterCommandResult> runCommand() async {
if (globals.androidSdk == null) {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:unified_analytics/unified_analytics.dart';
import '../android/android_builder.dart';
import '../android/build_validation.dart';
import '../android/deferred_components_prebuild_validator.dart';
......@@ -105,6 +107,29 @@ class BuildAppBundleCommand extends BuildSubCommand {
);
}
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final String buildMode;
if (boolArg('release')) {
buildMode = 'release';
} else if (boolArg('debug')) {
buildMode = 'debug';
} else if (boolArg('profile')) {
buildMode = 'profile';
} else {
// The build defaults to release.
buildMode = 'release';
}
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildAppBundleTargetPlatform: stringsArg('target-platform').join(','),
buildAppBundleBuildMode: buildMode,
);
}
@override
Future<FlutterCommandResult> runCommand() async {
if (globals.androidSdk == null) {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:unified_analytics/unified_analytics.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../bundle.dart';
......@@ -83,6 +85,18 @@ class BuildBundleCommand extends BuildSubCommand {
);
}
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final String projectDir = globals.fs.file(targetFile).parent.parent.path;
final FlutterProject flutterProject = FlutterProject.fromDirectory(globals.fs.directory(projectDir));
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildBundleTargetPlatform: stringArg('target-platform'),
buildBundleIsModule: flutterProject.isModule,
);
}
@override
Future<void> validateCommand() async {
if (boolArg('tree-shake-icons')) {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../android/gradle_utils.dart' as gradle;
import '../base/common.dart';
......@@ -91,6 +92,15 @@ class CreateCommand extends CreateBase {
);
}
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async => Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
createProjectType: stringArg('template'),
createAndroidLanguage: stringArg('android-language'),
createIosLanguage: stringArg('ios-language'),
);
// Lazy-initialize the net utilities with values from the context.
late final Net _net = Net(
httpClientFactory: context.get<HttpClientFactory>(),
......
......@@ -389,6 +389,24 @@ class PackagesGetCommand extends FlutterCommand {
return FlutterCommandResult.success();
}
late final Future<List<Plugin>> _pluginsFound = (() async {
final FlutterProject? rootProject = _rootProject;
if (rootProject == null) {
return <Plugin>[];
}
return findPlugins(rootProject, throwOnError: false);
})();
late final String? _androidEmbeddingVersion = (() {
final FlutterProject? rootProject = _rootProject;
if (rootProject == null) {
return null;
}
return rootProject.android.getEmbeddingVersion().toString().split('.').last;
})();
/// The pub packages usage values are incorrect since these are calculated/sent
/// before pub get completes. This needs to be performed after dependency resolution.
@override
......@@ -405,7 +423,7 @@ class PackagesGetCommand extends FlutterCommand {
if (hasPlugins) {
// Do not fail pub get if package config files are invalid before pub has
// had a chance to run.
final List<Plugin> plugins = await findPlugins(rootProject, throwOnError: false);
final List<Plugin> plugins = await _pluginsFound;
numberPlugins = plugins.length;
} else {
numberPlugins = 0;
......@@ -414,7 +432,38 @@ class PackagesGetCommand extends FlutterCommand {
return CustomDimensions(
commandPackagesNumberPlugins: numberPlugins,
commandPackagesProjectModule: rootProject.isModule,
commandPackagesAndroidEmbeddingVersion: rootProject.android.getEmbeddingVersion().toString().split('.').last,
commandPackagesAndroidEmbeddingVersion: _androidEmbeddingVersion,
);
}
/// The pub packages usage values are incorrect since these are calculated/sent
/// before pub get completes. This needs to be performed after dependency resolution.
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final FlutterProject? rootProject = _rootProject;
if (rootProject == null) {
return Event.commandUsageValues(workflow: commandPath, commandHasTerminal: hasTerminal);
}
final int numberPlugins;
// Do not send plugin analytics if pub has not run before.
final bool hasPlugins = rootProject.flutterPluginsDependenciesFile.existsSync()
&& rootProject.packageConfigFile.existsSync();
if (hasPlugins) {
// Do not fail pub get if package config files are invalid before pub has
// had a chance to run.
final List<Plugin> plugins = await _pluginsFound;
numberPlugins = plugins.length;
} else {
numberPlugins = 0;
}
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
packagesNumberPlugins: numberPlugins,
packagesProjectModule: rootProject.isModule,
packagesAndroidEmbeddingVersion: _androidEmbeddingVersion,
);
}
}
......@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart';
import '../android/android_device.dart';
......@@ -447,6 +448,43 @@ class RunCommand extends RunCommandBase {
@override
Future<CustomDimensions> get usageValues async {
final AnalyticsUsageValuesRecord record = await _sharedAnalyticsUsageValues;
return CustomDimensions(
commandRunIsEmulator: record.runIsEmulator,
commandRunTargetName: record.runTargetName,
commandRunTargetOsVersion: record.runTargetOsVersion,
commandRunModeName: record.runModeName,
commandRunProjectModule: record.runProjectModule,
commandRunProjectHostLanguage: record.runProjectHostLanguage,
commandRunAndroidEmbeddingVersion: record.runAndroidEmbeddingVersion,
commandRunEnableImpeller: record.runEnableImpeller,
commandRunIOSInterfaceType: record.runIOSInterfaceType,
commandRunIsTest: record.runIsTest,
);
}
@override
Future<analytics.Event> unifiedAnalyticsUsageValues(String commandPath) async {
final AnalyticsUsageValuesRecord record = await _sharedAnalyticsUsageValues;
return analytics.Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
runIsEmulator: record.runIsEmulator,
runTargetName: record.runTargetName,
runTargetOsVersion: record.runTargetOsVersion,
runModeName: record.runModeName,
runProjectModule: record.runProjectModule,
runProjectHostLanguage: record.runProjectHostLanguage,
runAndroidEmbeddingVersion: record.runAndroidEmbeddingVersion,
runEnableImpeller: record.runEnableImpeller,
runIOSInterfaceType: record.runIOSInterfaceType,
runIsTest: record.runIsTest,
);
}
late final Future<AnalyticsUsageValuesRecord> _sharedAnalyticsUsageValues = (() async {
String deviceType, deviceOsVersion;
bool isEmulator;
bool anyAndroidDevices = false;
......@@ -512,19 +550,19 @@ class RunCommand extends RunCommandBase {
final BuildInfo buildInfo = await getBuildInfo();
final String modeName = buildInfo.modeName;
return CustomDimensions(
commandRunIsEmulator: isEmulator,
commandRunTargetName: deviceType,
commandRunTargetOsVersion: deviceOsVersion,
commandRunModeName: modeName,
commandRunProjectModule: FlutterProject.current().isModule,
commandRunProjectHostLanguage: hostLanguage.join(','),
commandRunAndroidEmbeddingVersion: androidEmbeddingVersion,
commandRunEnableImpeller: enableImpeller.asBool,
commandRunIOSInterfaceType: iOSInterfaceType,
commandRunIsTest: targetFile.endsWith('_test.dart'),
return (
runIsEmulator: isEmulator,
runTargetName: deviceType,
runTargetOsVersion: deviceOsVersion,
runModeName: modeName,
runProjectModule: FlutterProject.current().isModule,
runProjectHostLanguage: hostLanguage.join(','),
runAndroidEmbeddingVersion: androidEmbeddingVersion,
runEnableImpeller: enableImpeller.asBool,
runIOSInterfaceType: iOSInterfaceType,
runIsTest: targetFile.endsWith('_test.dart'),
);
}
})();
@override
bool get shouldRunPub {
......@@ -801,3 +839,17 @@ class RunCommand extends RunCommandBase {
);
}
}
/// Schema for the usage values to send for analytics reporting.
typedef AnalyticsUsageValuesRecord = ({
String? runAndroidEmbeddingVersion,
bool? runEnableImpeller,
String? runIOSInterfaceType,
bool runIsEmulator,
bool runIsTest,
String runModeName,
String runProjectHostLanguage,
bool runProjectModule,
String runTargetName,
String runTargetOsVersion,
});
......@@ -366,6 +366,9 @@ abstract class FlutterCommand extends Command<void> {
return bundle.defaultMainPath;
}
/// Indicates if the currenet command running has a terminal attached.
bool get hasTerminal => globals.stdio.hasTerminal;
/// Path to the Dart's package config file.
///
/// This can be overridden by some of its subclasses.
......@@ -1359,6 +1362,14 @@ abstract class FlutterCommand extends Command<void> {
/// Additional usage values to be sent with the usage ping.
Future<CustomDimensions> get usageValues async => const CustomDimensions();
/// Additional usage values to be sent with the usage ping for
/// package:unified_analytics.
///
/// Implementations of [FlutterCommand] can override this getter in order
/// to add additional parameters in the [Event.commandUsageValues] constructor.
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async =>
Event.commandUsageValues(workflow: commandPath, commandHasTerminal: hasTerminal);
/// Runs this command.
///
/// Rather than overriding this method, subclasses should override
......@@ -1621,7 +1632,7 @@ abstract class FlutterCommand extends Command<void> {
commandPath: commandPath,
result: commandResult.toString(),
maxRss: maxRss,
commandHasTerminal: globals.stdio.hasTerminal,
commandHasTerminal: hasTerminal,
));
// Send timing.
......@@ -1748,9 +1759,17 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
setupApplicationPackages();
if (commandPath != null) {
// Until the GA4 migration is complete, we will continue to send to the GA3 instance
// as well as GA4. Once migration is complete, we will only make a call for GA4 values
final List<Object> pairOfUsageValues = await Future.wait<Object>(<Future<Object>>[
usageValues,
unifiedAnalyticsUsageValues(commandPath),
]);
Usage.command(commandPath, parameters: CustomDimensions(
commandHasTerminal: globals.stdio.hasTerminal,
).merge(await usageValues));
commandHasTerminal: hasTerminal,
).merge(pairOfUsageValues[0] as CustomDimensions));
analytics.send(pairOfUsageValues[1] as Event);
}
return runCommand();
......
......@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/commands/assemble.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -24,6 +25,14 @@ void main() {
Cache.disableLocking();
Cache.flutterRoot = '';
final StackTrace stackTrace = StackTrace.current;
late FakeAnalytics fakeAnalytics;
setUp(() {
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: MemoryFileSystem.test(),
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext('flutter assemble can run a build', () async {
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
......@@ -85,6 +94,31 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('flutter assemble sends usage values correctly with platform', () async {
final AssembleCommand command = AssembleCommand(
buildSystem: TestBuildSystem.all(BuildResult(success: true)));
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['assemble', '-o Output', '-dTargetPlatform=darwin', '-dDarwinArchs=x86_64', 'debug_macos_bundle_flutter_assets']);
expect(
fakeAnalytics.sentEvents,
contains(
Event.commandUsageValues(
workflow: 'assemble',
commandHasTerminal: false,
buildBundleTargetPlatform: 'darwin',
buildBundleIsModule: false,
),
),
);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
Analytics: () => fakeAnalytics,
});
testUsingContext('flutter assemble throws ToolExit if not provided with output', () async {
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
......
......@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -26,6 +27,7 @@ void main() {
late FakeProcessManager processManager;
late Platform platform;
late Cache cache;
late FakeAnalytics fakeAnalytics;
setUpAll(() {
Cache.disableLocking();
......@@ -44,6 +46,10 @@ void main() {
logger: logger,
processManager: processManager,
);
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fs,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext('will not build an AAR for a plugin', () async {
......@@ -126,9 +132,19 @@ flutter:
await createTestCommandRunner(command).run(const <String>['build', 'aar', '--no-pub']);
expect(processManager, hasNoRemainingExpectations);
expect(
fakeAnalytics.sentEvents,
contains(Event.commandUsageValues(
workflow: 'build/aar',
commandHasTerminal: false,
buildAarProjectType: 'module',
buildAarTargetPlatform: 'android-arm,android-arm64,android-x64',
)),
);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
Platform: () => platform,
ProcessManager: () => processManager,
Analytics: () => fakeAnalytics,
});
}
......@@ -33,6 +33,7 @@ import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:flutter_tools/src/web/compile.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart';
import '../../src/common.dart';
......@@ -192,6 +193,7 @@ void main() {
late Artifacts artifacts;
late TestUsage usage;
late FakeAnsiTerminal fakeTerminal;
late analytics.FakeAnalytics fakeAnalytics;
setUpAll(() {
Cache.disableLocking();
......@@ -211,6 +213,10 @@ void main() {
libDir.createSync();
final File mainFile = libDir.childFile('main.dart');
mainFile.writeAsStringSync('void main() {}');
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fs,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext('exits with a user message when no supported devices attached', () async {
......@@ -478,6 +484,23 @@ void main() {
'cd58': 'false',
})
)));
expect(
fakeAnalytics.sentEvents,
contains(
analytics.Event.commandUsageValues(
workflow: 'run',
commandHasTerminal: globals.stdio.hasTerminal,
runIsEmulator: false,
runTargetName: 'ios',
runTargetOsVersion: 'iOS 13',
runModeName: 'debug',
runProjectModule: false,
runProjectHostLanguage: 'swift',
runIOSInterfaceType: 'usb',
runIsTest: false,
),
),
);
}, overrides: <Type, Generator>{
AnsiTerminal: () => fakeTerminal,
Artifacts: () => artifacts,
......@@ -487,6 +510,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
Stdio: () => FakeStdio(),
Usage: () => usage,
analytics.Analytics: () => fakeAnalytics,
});
testUsingContext('correctly reports tests to usage', () async {
......@@ -513,6 +537,23 @@ void main() {
'cd58': 'true',
})),
));
expect(
fakeAnalytics.sentEvents,
contains(
analytics.Event.commandUsageValues(
workflow: 'run',
commandHasTerminal: globals.stdio.hasTerminal,
runIsEmulator: false,
runTargetName: 'ios',
runTargetOsVersion: 'iOS 13',
runModeName: 'debug',
runProjectModule: false,
runProjectHostLanguage: 'swift',
runIOSInterfaceType: 'usb',
runIsTest: true,
),
),
);
}, overrides: <Type, Generator>{
AnsiTerminal: () => fakeTerminal,
Artifacts: () => artifacts,
......@@ -522,6 +563,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
Stdio: () => FakeStdio(),
Usage: () => usage,
analytics.Analytics: () => fakeAnalytics,
});
group('--machine', () {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_builder.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
......@@ -16,11 +17,13 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/android_common.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart' show FakeFlutterVersion;
import '../../src/test_flutter_command_runner.dart';
void main() {
......@@ -29,10 +32,15 @@ void main() {
group('Usage', () {
late Directory tempDir;
late TestUsage testUsage;
late FakeAnalytics fakeAnalytics;
setUp(() {
testUsage = TestUsage();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: MemoryFileSystem.test(),
fakeFlutterVersion: FakeFlutterVersion(),
);
});
tearDown(() {
......@@ -46,8 +54,21 @@ void main() {
expect((await command.usageValues).commandBuildApkTargetPlatform, 'android-arm,android-arm64,android-x64');
expect(
fakeAnalytics.sentEvents,
contains(
Event.commandUsageValues(
workflow: 'apk',
commandHasTerminal: false,
buildApkTargetPlatform: 'android-arm,android-arm64,android-x64',
buildApkBuildMode: 'release',
buildApkSplitPerAbi: false,
),
),
);
}, overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(),
Analytics: () => fakeAnalytics,
});
testUsingContext('split per abi', () async {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_builder.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/file_system.dart';
......@@ -13,10 +14,12 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/android_common.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart' show FakeFlutterVersion;
import '../../src/test_flutter_command_runner.dart';
void main() {
......@@ -25,10 +28,15 @@ void main() {
group('Usage', () {
late Directory tempDir;
late TestUsage testUsage;
late FakeAnalytics fakeAnalytics;
setUp(() {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
testUsage = TestUsage();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: MemoryFileSystem.test(),
fakeFlutterVersion: FakeFlutterVersion(),
);
});
tearDown(() {
......@@ -42,8 +50,18 @@ void main() {
expect((await command.usageValues).commandBuildAppBundleTargetPlatform, 'android-arm,android-arm64,android-x64');
expect(
fakeAnalytics.sentEvents,
contains(Event.commandUsageValues(
workflow: 'appbundle',
commandHasTerminal: false,
buildAppBundleTargetPlatform: 'android-arm,android-arm64,android-x64',
buildAppBundleBuildMode: 'release',
)),
);
}, overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(),
Analytics: () => fakeAnalytics,
});
testUsingContext('build type', () async {
......
......@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:meta/meta.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -30,21 +31,26 @@ void main() {
late FakeBundleBuilder fakeBundleBuilder;
final FileSystemStyle fileSystemStyle = globals.fs.path.separator == '/' ?
FileSystemStyle.posix : FileSystemStyle.windows;
late FakeAnalytics fakeAnalytics;
MemoryFileSystem fsFactory() {
return MemoryFileSystem.test(style: fileSystemStyle);
}
setUp(() {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
fakeBundleBuilder = FakeBundleBuilder();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fsFactory(),
fakeFlutterVersion: FakeFlutterVersion(),
);
});
tearDown(() {
tryToDelete(tempDir);
});
MemoryFileSystem fsFactory() {
return MemoryFileSystem.test(style: fileSystemStyle);
}
Future<BuildBundleCommand> runCommandIn(String projectPath, { List<String>? arguments }) async {
final BuildBundleCommand command = BuildBundleCommand(
logger: BufferLogger.test(),
......@@ -67,6 +73,19 @@ void main() {
final BuildBundleCommand command = await runCommandIn(projectPath);
expect((await command.usageValues).commandBuildBundleIsModule, true);
expect(
fakeAnalytics.sentEvents,
contains(
Event.commandUsageValues(
workflow: 'bundle',
commandHasTerminal: false,
buildBundleTargetPlatform: 'android-arm',
buildBundleIsModule: true,
),
),
);
}, overrides: <Type, Generator>{
Analytics: () => fakeAnalytics,
});
testUsingContext('bundle getUsage indicate that project is not a module', () async {
......@@ -76,6 +95,19 @@ void main() {
final BuildBundleCommand command = await runCommandIn(projectPath);
expect((await command.usageValues).commandBuildBundleIsModule, false);
expect(
fakeAnalytics.sentEvents,
contains(
Event.commandUsageValues(
workflow: 'bundle',
commandHasTerminal: false,
buildBundleTargetPlatform: 'android-arm',
buildBundleIsModule: false,
),
),
);
}, overrides: <Type, Generator>{
Analytics: () => fakeAnalytics,
});
testUsingContext('bundle getUsage indicate the target platform', () async {
......
......@@ -7,6 +7,7 @@ import 'dart:convert';
import 'dart:io' as io;
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart' show templateAndroidGradlePluginVersion, templateAndroidGradlePluginVersionForModule, templateDefaultGradleVersion;
import 'package:flutter_tools/src/android/java.dart';
......@@ -30,6 +31,7 @@ import 'package:flutter_tools/src/version.dart';
import 'package:process/process.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:unified_analytics/unified_analytics.dart';
import 'package:uuid/uuid.dart';
import 'package:yaml/yaml.dart';
......@@ -71,6 +73,7 @@ void main() {
late FakeProcessManager fakeProcessManager;
late BufferLogger logger;
late FakeStdio mockStdio;
late FakeAnalytics fakeAnalytics;
setUpAll(() async {
Cache.disableLocking();
......@@ -88,6 +91,10 @@ void main() {
);
fakeProcessManager = FakeProcessManager.empty();
mockStdio = FakeStdio();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: MemoryFileSystem.test(),
fakeFlutterVersion: fakeFlutterVersion,
);
});
tearDown(() {
......@@ -171,10 +178,24 @@ void main() {
],
);
expect(logger.statusText, contains('In order to run your application, type:'));
// check that we're telling them about documentation
// Check that we're telling them about documentation
expect(logger.statusText, contains('https://docs.flutter.dev/'));
expect(logger.statusText, contains('https://api.flutter.dev/'));
// check that the tests run clean
// Check for usage values sent in analytics
expect(
fakeAnalytics.sentEvents,
contains(
Event.commandUsageValues(
workflow: 'create',
commandHasTerminal: false,
createAndroidLanguage: 'java',
createIosLanguage: 'objc',
),
),
);
// Check that the tests run clean
return _runFlutterTest(projectDir);
}, overrides: <Type, Generator>{
Pub: () => Pub.test(
......@@ -187,6 +208,7 @@ void main() {
stdio: mockStdio,
),
Logger: () => logger,
Analytics: () => fakeAnalytics,
});
testUsingContext('can create a skeleton (list/detail) app', () async {
......
......@@ -355,6 +355,11 @@ flutter:
final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
expect((await getCommand.usageValues).commandPackagesNumberPlugins, 0);
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesNumberPlugins'],
0,
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......@@ -380,6 +385,11 @@ flutter:
// A plugin example depends on the plugin itself, and integration_test.
expect((await getCommand.usageValues).commandPackagesNumberPlugins, 2);
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesNumberPlugins'],
2,
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......@@ -402,6 +412,11 @@ flutter:
final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
expect((await getCommand.usageValues).commandPackagesProjectModule, false);
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesProjectModule'],
false,
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......@@ -424,6 +439,11 @@ flutter:
final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
expect((await getCommand.usageValues).commandPackagesProjectModule, true);
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesProjectModule'],
true,
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......@@ -455,6 +475,11 @@ flutter:
final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
expect((await getCommand.usageValues).commandPackagesAndroidEmbeddingVersion, 'v1');
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesAndroidEmbeddingVersion'],
'v1',
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......@@ -477,6 +502,11 @@ flutter:
final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
expect((await getCommand.usageValues).commandPackagesAndroidEmbeddingVersion, 'v2');
expect(
(await getCommand.unifiedAnalyticsUsageValues('pub/get'))
.eventData['packagesAndroidEmbeddingVersion'],
'v2',
);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
......
......@@ -373,7 +373,7 @@ bool analyticsTimingEventExists({
};
for (final Event e in sentEvents) {
final Map<String, Object?> eventData = e.eventData;
final Map<String, Object?> eventData = <String, Object?>{...e.eventData};
eventData.remove('elapsedMilliseconds');
if (const DeepCollectionEquality().equals(lookup, eventData)) {
......
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