Unverified Commit 81052a7d authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Add usage event to track when a iOS network device is used (#118915)

* Add usage event to track when a iOS network device is used

* update usage event to track percentage of iOS network vs usb devices, update and fix tests

* refactor tracking to happen in usageValues with a custom dimension
parent e85547b3
......@@ -19,6 +19,8 @@ import '../daemon.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_runner.dart';
......@@ -407,18 +409,23 @@ class RunCommand extends RunCommandBase {
bool isEmulator;
bool anyAndroidDevices = false;
bool anyIOSDevices = false;
bool anyIOSNetworkDevices = false;
if (devices == null || devices!.isEmpty) {
deviceType = 'none';
deviceOsVersion = 'none';
isEmulator = false;
} else if (devices!.length == 1) {
final TargetPlatform platform = await devices![0].targetPlatform;
final Device device = devices![0];
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = platform == TargetPlatform.android;
anyIOSDevices = platform == TargetPlatform.ios;
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
deviceType = getNameForTargetPlatform(platform);
deviceOsVersion = await devices![0].sdkNameAndVersion;
isEmulator = await devices![0].isLocalEmulator;
deviceOsVersion = await device.sdkNameAndVersion;
isEmulator = await device.isLocalEmulator;
} else {
deviceType = 'multiple';
deviceOsVersion = 'multiple';
......@@ -427,12 +434,20 @@ class RunCommand extends RunCommandBase {
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android);
anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios);
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
if (anyAndroidDevices && anyIOSDevices) {
break;
}
}
}
String? iOSInterfaceType;
if (anyIOSDevices) {
iOSInterfaceType = anyIOSNetworkDevices ? 'wireless' : 'usb';
}
String? androidEmbeddingVersion;
final List<String> hostLanguage = <String>[];
if (anyAndroidDevices) {
......@@ -464,6 +479,7 @@ class RunCommand extends RunCommandBase {
commandRunProjectHostLanguage: hostLanguage.join(','),
commandRunAndroidEmbeddingVersion: androidEmbeddingVersion,
commandRunEnableImpeller: enableImpeller,
commandRunIOSInterfaceType: iOSInterfaceType,
);
}
......
......@@ -67,6 +67,7 @@ class CustomDimensions {
this.hotEventReassembleTimeInMs,
this.hotEventReloadVMTimeInMs,
this.commandRunEnableImpeller,
this.commandRunIOSInterfaceType,
});
final String? sessionHostOsDetails; // cd1
......@@ -125,6 +126,7 @@ class CustomDimensions {
final int? hotEventReassembleTimeInMs; // cd 54
final int? hotEventReloadVMTimeInMs; // cd 55
final bool? commandRunEnableImpeller; // cd 56
final String? commandRunIOSInterfaceType; // cd 57
/// Convert to a map that will be used to upload to the analytics backend.
Map<String, String> toMap() => <String, String>{
......@@ -184,6 +186,7 @@ class CustomDimensions {
if (hotEventReassembleTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReassembleTimeInMs): hotEventReassembleTimeInMs.toString(),
if (hotEventReloadVMTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReloadVMTimeInMs): hotEventReloadVMTimeInMs.toString(),
if (commandRunEnableImpeller != null) cdKey(CustomDimensionsEnum.commandRunEnableImpeller): commandRunEnableImpeller.toString(),
if (commandRunIOSInterfaceType != null) cdKey(CustomDimensionsEnum.commandRunIOSInterfaceType): commandRunIOSInterfaceType.toString(),
};
/// Merge the values of two [CustomDimensions] into one. If a value is defined
......@@ -250,6 +253,7 @@ class CustomDimensions {
hotEventReassembleTimeInMs: other.hotEventReassembleTimeInMs ?? hotEventReassembleTimeInMs,
hotEventReloadVMTimeInMs: other.hotEventReloadVMTimeInMs ?? hotEventReloadVMTimeInMs,
commandRunEnableImpeller: other.commandRunEnableImpeller ?? commandRunEnableImpeller,
commandRunIOSInterfaceType: other.commandRunIOSInterfaceType ?? commandRunIOSInterfaceType,
);
}
......@@ -310,6 +314,7 @@ class CustomDimensions {
hotEventReassembleTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReassembleTimeInMs),
hotEventReloadVMTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReloadVMTimeInMs),
commandRunEnableImpeller: _extractBool(map, CustomDimensionsEnum.commandRunEnableImpeller),
commandRunIOSInterfaceType: _extractString(map, CustomDimensionsEnum.commandRunIOSInterfaceType),
);
static bool? _extractBool(Map<String, String> map, CustomDimensionsEnum field) =>
......@@ -396,6 +401,7 @@ enum CustomDimensionsEnum {
hotEventReassembleTimeInMs, // cd54
hotEventReloadVMTimeInMs, // cd55
commandRunEnableImpeller, // cd56
commandRunIOSInterfaceType, // cd57
}
String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}';
......@@ -24,6 +24,8 @@ import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
......@@ -426,7 +428,7 @@ void main() {
TestUsageCommand('run', parameters: CustomDimensions.fromMap(<String, String>{
'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13',
'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'true',
'cd56': 'false',
'cd56': 'false', 'cd57': 'usb',
})
)));
}, overrides: <Type, Generator>{
......@@ -664,6 +666,104 @@ void main() {
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
group('usageValues', () {
testUsingContext('with only non-iOS usb device', () async {
final List<Device> devices = <Device>[
FakeDevice(targetPlatform: TargetPlatform.android_arm, platformType: PlatformType.android),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'android-arm',
commandRunTargetOsVersion: '',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with only iOS usb device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'ios',
commandRunTargetOsVersion: 'iOS 16.2',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'usb',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with only iOS network device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'ios',
commandRunTargetOsVersion: 'iOS 16.2',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'wireless',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with both iOS usb and network devices', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'multiple',
commandRunTargetOsVersion: 'multiple',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'wireless',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
});
});
group('dart-defines and web-renderer options', () {
......@@ -1032,6 +1132,49 @@ class FakeDevice extends Fake implements Device {
}
}
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({
this.interfaceType = IOSDeviceConnectionInterface.none,
bool isLocalEmulator = false,
String sdkNameAndVersion = '',
}): _isLocalEmulator = isLocalEmulator,
_sdkNameAndVersion = sdkNameAndVersion;
final bool _isLocalEmulator;
final String _sdkNameAndVersion;
@override
Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);
@override
Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion);
@override
final IOSDeviceConnectionInterface interfaceType;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
}
class TestRunCommandForUsageValues extends RunCommand {
TestRunCommandForUsageValues({
this.devices,
});
@override
// devices is not set within usageValues, so we override the field
// ignore: overridden_fields
List<Device>? devices;
@override
Future<BuildInfo> getBuildInfo({ BuildMode? forcedBuildMode, File? forcedTargetFile }) async {
return const BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
}
}
class TestRunCommandWithFakeResidentRunner extends RunCommand {
late FakeResidentRunner fakeResidentRunner;
......
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