Skip to content

Commit 4b9f1a3

Browse files
author
Fatme
authored
Merge pull request #4287 from NativeScript/fatme/track-data-from-preview-app
feat: track data from preview app on `preview` and `run` commands
2 parents 949f9a4 + 973662f commit 4b9f1a3

18 files changed

+137
-21
lines changed

lib/commands/preview.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ export class PreviewCommand implements ICommand {
44
public allowedParameters: ICommandParameter[] = [];
55
private static MIN_SUPPORTED_WEBPACK_VERSION = "0.17.0";
66

7-
constructor(private $bundleValidatorHelper: IBundleValidatorHelper,
7+
constructor(private $analyticsService: IAnalyticsService,
8+
private $bundleValidatorHelper: IBundleValidatorHelper,
89
private $errors: IErrors,
910
private $liveSyncService: ILiveSyncService,
1011
private $logger: ILogger,
1112
private $networkConnectivityValidator: INetworkConnectivityValidator,
1213
private $projectData: IProjectData,
1314
private $options: IOptions,
1415
private $previewAppLogProvider: IPreviewAppLogProvider,
15-
private $previewQrCodeService: IPreviewQrCodeService) { }
16+
private $previewQrCodeService: IPreviewQrCodeService) {
17+
this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch);
18+
}
1619

1720
public async execute(): Promise<void> {
1821
this.$previewAppLogProvider.on(DEVICE_LOG_EVENT_NAME, (deviceId: string, message: string) => {

lib/commands/run.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export class RunCommandBase implements ICommand {
66
private liveSyncCommandHelperAdditionalOptions: ILiveSyncCommandHelperAdditionalOptions = <ILiveSyncCommandHelperAdditionalOptions>{};
77

88
public platform: string;
9-
constructor(private $projectData: IProjectData,
9+
constructor(
10+
private $analyticsService: IAnalyticsService,
11+
private $projectData: IProjectData,
1012
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
1113
private $errors: IErrors,
1214
private $hostInfo: IHostInfo,
@@ -15,6 +17,7 @@ export class RunCommandBase implements ICommand {
1517

1618
public allowedParameters: ICommandParameter[] = [];
1719
public async execute(args: string[]): Promise<void> {
20+
await this.$analyticsService.trackPreviewAppData(this.platform, this.$projectData.projectDir);
1821
return this.$liveSyncCommandHelper.executeCommandLiveSync(this.platform, this.liveSyncCommandHelperAdditionalOptions);
1922
}
2023

lib/common/declarations.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ declare const enum TrackingTypes {
203203
*/
204204
GoogleAnalyticsData = "googleAnalyticsData",
205205

206+
/**
207+
* Defines that the broker process should get and track the data from preview app to Google Analytics
208+
*/
209+
PreviewAppData = "PreviewAppData",
210+
206211
/**
207212
* Defines that all information has been sent and no more data will be tracked in current session.
208213
*/
@@ -690,6 +695,11 @@ interface IAnalyticsService {
690695
*/
691696
trackEventActionInGoogleAnalytics(data: IEventActionData): Promise<void>;
692697

698+
/**
699+
* Tracks preview's app data to Google Analytics project.
700+
*/
701+
trackPreviewAppData(platform: string, projectDir: string): Promise<void>
702+
693703
/**
694704
* Defines if the instance should be disposed.
695705
* @param {boolean} shouldDispose Defines if the instance should be disposed and the child processes should be disconnected.

lib/common/definitions/google-analytics.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ interface IGoogleAnalyticsData {
1313
customDimensions?: IStringDictionary;
1414
}
1515

16+
interface IPreviewAppGoogleAnalyticsData {
17+
platform: string;
18+
additionalData?: string;
19+
}
20+
1621
/**
1722
* Describes information about event that should be tracked.
1823
*/

lib/common/definitions/mobile.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ declare module Mobile {
320320
interface IDeviceFileSystem {
321321
listFiles(devicePath: string, appIdentifier?: string): Promise<any>;
322322
getFile(deviceFilePath: string, appIdentifier: string, outputFilePath?: string): Promise<void>;
323+
getFileContent(deviceFilePath: string, appIdentifier: string): Promise<string>;
323324
putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise<void>;
324325
deleteFile(deviceFilePath: string, appIdentifier: string): Promise<void>;
325326
transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]>;

lib/common/mobile/android/android-device-file-system.ts

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export class AndroidDeviceFileSystem implements Mobile.IDeviceFileSystem {
4848
}
4949
}
5050

51+
public async getFileContent(deviceFilePath: string, appIdentifier: string): Promise<string> {
52+
const result = await this.adb.executeShellCommand(["cat", deviceFilePath]);
53+
return result;
54+
}
55+
5156
public async putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise<void> {
5257
await this.adb.pushFile(localFilePath, deviceFilePath);
5358
}

lib/common/mobile/ios/device/ios-device-file-system.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,19 @@ export class IOSDeviceFileSystem implements Mobile.IDeviceFileSystem {
2323
}
2424

2525
public async getFile(deviceFilePath: string, appIdentifier: string, outputFilePath?: string): Promise<void> {
26-
if (!outputFilePath) {
27-
const result = await this.$iosDeviceOperations.readFiles([{ deviceId: this.device.deviceInfo.identifier, path: deviceFilePath, appId: appIdentifier }]);
28-
const response = result[this.device.deviceInfo.identifier][0];
29-
if (response) {
30-
this.$logger.out(response.response);
31-
}
32-
} else {
26+
if (outputFilePath) {
3327
await this.$iosDeviceOperations.downloadFiles([{ appId: appIdentifier, deviceId: this.device.deviceInfo.identifier, source: deviceFilePath, destination: outputFilePath }]);
28+
return;
3429
}
30+
31+
const fileContent = await this.getFileContent(deviceFilePath, appIdentifier);
32+
this.$logger.out(fileContent);
33+
}
34+
35+
public async getFileContent(deviceFilePath: string, appIdentifier: string): Promise<string> {
36+
const result = await this.$iosDeviceOperations.readFiles([{ deviceId: this.device.deviceInfo.identifier, path: deviceFilePath, appId: appIdentifier }]);
37+
const response = result[this.device.deviceInfo.identifier][0];
38+
return response.response;
3539
}
3640

3741
public async putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise<void> {

lib/common/mobile/ios/simulator/ios-simulator-file-system.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export class IOSSimulatorFileSystem implements Mobile.IDeviceFileSystem {
1616
}
1717
}
1818

19+
public async getFileContent(deviceFilePath: string, appIdentifier: string): Promise<string> {
20+
const result = this.$fs.readText(deviceFilePath);
21+
return result;
22+
}
23+
1924
public async putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise<void> {
2025
shelljs.cp("-f", localFilePath, deviceFilePath);
2126
}
@@ -27,7 +32,7 @@ export class IOSSimulatorFileSystem implements Mobile.IDeviceFileSystem {
2732
public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
2833
await Promise.all(
2934
_.map(localToDevicePaths, localToDevicePathData => this.transferFile(localToDevicePathData.getLocalPath(), localToDevicePathData.getDevicePath())
30-
));
35+
));
3136
return localToDevicePaths;
3237
}
3338

lib/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ export const enum TrackActionNames {
156156
CheckEnvironmentRequirements = "Check Environment Requirements",
157157
Options = "Options",
158158
AcceptTracking = "Accept Tracking",
159-
Performance = "Performance"
159+
Performance = "Performance",
160+
PreviewAppData = "Preview App Data"
160161
}
161162

162163
export const AnalyticsEventLabelDelimiter = "__";

lib/services/analytics/analytics-broker-process.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,47 @@ const finishTracking = async (data?: ITrackingInformation) => {
5454
}
5555
};
5656

57+
const trackPreviewAppData = async (data: any) => {
58+
const mobileHelper = $injector.resolve<Mobile.IMobileHelper>("mobileHelper");
59+
const devicesService = $injector.resolve<Mobile.IDevicesService>("devicesService");
60+
await devicesService.initialize({ platform: data.platform, skipDeviceDetectionInterval: true, skipEmulatorStart: true });
61+
62+
const devices = await devicesService.getDevicesForPlatform(data.platform);
63+
_.each(devices, async (device: Mobile.IDevice) => {
64+
try {
65+
let previewAppFilePath = null;
66+
if (mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) {
67+
previewAppFilePath = "/sdcard/org.nativescript.preview/device.json";
68+
} else if (mobileHelper.isiOSPlatform(device.deviceInfo.platform)) {
69+
previewAppFilePath = "Documents/device.json";
70+
}
71+
72+
const previewAppFileContent = await device.fileSystem.getFileContent(previewAppFilePath, "org.nativescript.preview");
73+
const previewAppDeviceId = JSON.parse(previewAppFileContent).id;
74+
data.label += `_${previewAppDeviceId}`;
75+
76+
analyticsLoggingService.logData({ message: `analytics-broker-process will send the data from preview app: ${data}` });
77+
await sendDataForTracking(data);
78+
} catch (err) {
79+
// ignore the error
80+
}
81+
});
82+
};
83+
5784
process.on("message", async (data: ITrackingInformation) => {
58-
analyticsLoggingService.logData({ message: `analytics-broker-process received message of type: ${data.type}` });
85+
analyticsLoggingService.logData({ message: `analytics-broker-process received message of type: ${JSON.stringify(data)}` });
5986

6087
if (data.type === TrackingTypes.Finish) {
6188
receivedFinishMsg = true;
6289
await finishTracking(data);
6390
return;
6491
}
6592

93+
if (data.type === TrackingTypes.PreviewAppData) {
94+
await trackPreviewAppData(<IPreviewAppTrackingInformation>data);
95+
return;
96+
}
97+
6698
await sendDataForTracking(data);
6799
});
68100

lib/services/analytics/analytics-service.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
9696
const platform = device ? device.deviceInfo.platform : data.platform;
9797
const normalizedPlatform = platform ? this.$mobileHelper.normalizePlatformName(platform) : platform;
9898
const isForDevice = device ? !device.isEmulator : data.isForDevice;
99-
10099
let label: string = "";
101100
label = this.addDataToLabel(label, normalizedPlatform);
102101

@@ -129,6 +128,25 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
129128
await this.trackInGoogleAnalytics(googleAnalyticsEventData);
130129
}
131130

131+
public async trackPreviewAppData(platform: string, projectDir: string): Promise<void> {
132+
const customDimensions: IStringDictionary = {};
133+
this.setProjectRelatedCustomDimensions(customDimensions, projectDir);
134+
135+
let label: string = "";
136+
label = this.addDataToLabel(label, this.$mobileHelper.normalizePlatformName(platform));
137+
138+
const eventActionData = {
139+
googleAnalyticsDataType: GoogleAnalyticsDataType.Event,
140+
action: TrackActionNames.PreviewAppData,
141+
platform,
142+
label,
143+
customDimensions,
144+
type: TrackingTypes.PreviewAppData
145+
};
146+
147+
await this.trackInGoogleAnalytics(eventActionData);
148+
}
149+
132150
private forcefullyTrackInGoogleAnalytics(gaSettings: IGoogleAnalyticsData): Promise<void> {
133151
gaSettings.customDimensions = gaSettings.customDimensions || {};
134152
gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown);

lib/services/analytics/analytics.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ interface IAnalyticsBroker {
3737

3838
interface IGoogleAnalyticsTrackingInformation extends IGoogleAnalyticsData, ITrackingInformation { }
3939

40+
interface IPreviewAppTrackingInformation extends IPreviewAppGoogleAnalyticsData, ITrackingInformation { }
41+
4042
/**
4143
* Describes methods required to track in Google Analytics.
4244
*/

lib/services/livesync/playground/preview-app-livesync-service.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as path from "path";
22
import { Device, FilesPayload } from "nativescript-preview-sdk";
3-
import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME } from "../../../constants";
3+
import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME, TrackActionNames } from "../../../constants";
44
import { PreviewAppLiveSyncEvents } from "./preview-app-constants";
55
import { HmrConstants } from "../../../common/constants";
66
import { stringify } from "../../../common/helpers";
@@ -12,6 +12,7 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA
1212
private deviceInitializationPromise: IDictionary<Promise<FilesPayload>> = {};
1313

1414
constructor(
15+
private $analyticsService: IAnalyticsService,
1516
private $errors: IErrors,
1617
private $hooksService: IHooksService,
1718
private $logger: ILogger,
@@ -37,6 +38,14 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA
3738
return this.deviceInitializationPromise[device.id];
3839
}
3940

41+
if (device.uniqueId) {
42+
await this.$analyticsService.trackEventActionInGoogleAnalytics({
43+
action: TrackActionNames.PreviewAppData,
44+
platform: device.platform,
45+
additionalData: device.uniqueId
46+
});
47+
}
48+
4049
this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device);
4150
try {
4251
const payloads = await this.deviceInitializationPromise[device.id];

npm-shrinkwrap.json

+16-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"mkdirp": "0.5.1",
5959
"mute-stream": "0.0.5",
6060
"nativescript-doctor": "1.8.1",
61-
"nativescript-preview-sdk": "0.3.2",
61+
"nativescript-preview-sdk": "0.3.3",
6262
"open": "0.0.5",
6363
"ora": "2.0.0",
6464
"osenv": "0.1.3",

test/services/playground/preview-app-livesync-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ function createTestInjector(options?: {
158158
getConnectedDevices: () => [deviceMockData]
159159
});
160160
injector.register("previewAppFilesService", PreviewAppFilesService);
161+
injector.register("analyticsService", {
162+
trackEventActionInGoogleAnalytics: () => ({})
163+
});
161164

162165
return injector;
163166
}

test/services/playground/preview-app-plugins-service.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ function createDevice(plugins: string): Device {
5959
previewAppVersion: "28.0.0",
6060
runtimeVersion: "4.3.0",
6161
plugins,
62-
pluginsExpanded: false
62+
pluginsExpanded: false,
63+
uniqueId: "testId"
6364
};
6465
}
6566

test/services/preview-devices-service.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ function createDevice(id: string): Device {
2929
name: "my test name",
3030
osVersion: "10.0.0",
3131
previewAppVersion: "19.0.0",
32-
runtimeVersion: "5.0.0"
32+
runtimeVersion: "5.0.0",
33+
uniqueId: "testUniqueId"
3334
};
3435
}
3536

0 commit comments

Comments
 (0)