Skip to content

Commit 226e887

Browse files
committed
feat: track data from preview app on preview and run commands
{N} CLI checks if there is a file on device written from preview app, gets its content if such file exists and sends an event to google analytics. This will allow us to know how much users are using the Playground site and {N} CLI.
1 parent f83c95e commit 226e887

File tree

12 files changed

+108
-12
lines changed

12 files changed

+108
-12
lines changed

lib/commands/run.ts

Lines changed: 4 additions & 1 deletion
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

Lines changed: 10 additions & 0 deletions
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
*/
@@ -700,6 +705,11 @@ interface IAnalyticsService {
700705
*/
701706
trackEventActionInGoogleAnalytics(data: IEventActionData): Promise<void>;
702707

708+
/**
709+
* Tracks preview's app data to Google Analytics project.
710+
*/
711+
trackPreviewAppData(platform: string, projectDir: string): Promise<void>
712+
703713
/**
704714
* Defines if the instance should be disposed.
705715
* @param {boolean} shouldDispose Defines if the instance should be disposed and the child processes should be disconnected.

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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ declare module Mobile {
368368
interface IDeviceFileSystem {
369369
listFiles(devicePath: string, appIdentifier?: string): Promise<any>;
370370
getFile(deviceFilePath: string, appIdentifier: string, outputFilePath?: string): Promise<void>;
371+
getFileContent(deviceFilePath: string, appIdentifier: string): Promise<string>;
371372
putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise<void>;
372373
deleteFile(deviceFilePath: string, appIdentifier: string): Promise<void>;
373374
transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]>;

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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 11 additions & 7 deletions
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

Lines changed: 6 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ export const enum TrackActionNames {
153153
CheckEnvironmentRequirements = "Check Environment Requirements",
154154
Options = "Options",
155155
AcceptTracking = "Accept Tracking",
156-
Performance = "Performance"
156+
Performance = "Performance",
157+
PreviewAppData = "Preview App Data"
157158
}
158159

159160
export const AnalyticsEventLabelDelimiter = "__";

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ 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) => {
5885
analyticsLoggingService.logData({ message: `analytics-broker-process received message of type: ${data.type}` });
5986

@@ -63,6 +90,10 @@ process.on("message", async (data: ITrackingInformation) => {
6390
return;
6491
}
6592

93+
if (data.type === TrackingTypes.PreviewAppData) {
94+
await trackPreviewAppData(<IPreviewAppTrackingInformation>data);
95+
}
96+
6697
await sendDataForTracking(data);
6798
});
6899

lib/services/analytics/analytics-service.ts

Lines changed: 19 additions & 1 deletion
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 12 additions & 1 deletion
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 { AnalyticsEventLabelDelimiter, 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,
@@ -33,6 +34,16 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA
3334
this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported.");
3435
}
3536

37+
const deviceUniqueId = (<any>device).uniqueId;
38+
39+
if (deviceUniqueId) {
40+
this.$analyticsService.trackEventActionInGoogleAnalytics({
41+
action: TrackActionNames.PreviewAppData,
42+
platform: device.platform,
43+
additionalData: `previewAppDeviceId${AnalyticsEventLabelDelimiter}${deviceUniqueId}`
44+
});
45+
}
46+
3647
if (this.deviceInitializationPromise[device.id]) {
3748
return this.deviceInitializationPromise[device.id];
3849
}

0 commit comments

Comments
 (0)