Skip to content

Improve analytics #3088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,19 @@
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 5858,
"name": "Attach to Broker Process",
// In case you want to debug Analytics Broker process, add `--debug-brk=9897` (or --inspect-brk=9897) when spawning analytics-broker-process.
"port": 9897,
"sourceMaps": true
},

{
"type": "node",
"request": "attach",
"name": "Attach to Eqatec Process",
// In case you want to debug Eqatec Analytics process, add `--debug-brk=9855` (or --inspect-brk=9855) when spawning eqatec-analytics-process.
// NOTE: Ensure you set it only for one of the analytics processes.
"port": 9855,
"sourceMaps": true
}

Expand Down
15 changes: 0 additions & 15 deletions PublicAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,21 +394,6 @@ tns.npm.view(["nativescript"], {}).then(result => {
});
```

## analyticsService
Provides a way to configure analytics.

### startEqatecMonitor
* Definition:
```TypeScript
/**
* Starts analytics monitor with provided key.
* @param {string} projectApiKey API key with which to start analytics monitor.
* @returns {Promise<void>}.
*/
startEqatecMonitor(projectApiKey: string): Promise<void>;
```


## debugService
Provides methods for debugging applications on devices. The service is also event emitter, that raises the following events:
* `connectionError` event - this event is raised when the debug operation cannot start on iOS device. The causes can be:
Expand Down
4 changes: 3 additions & 1 deletion lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ $injector.require("androidDebugService", "./services/android-debug-service");

$injector.require("userSettingsService", "./services/user-settings-service");
$injector.require("analyticsSettingsService", "./services/analytics-settings-service");
$injector.requirePublic("analyticsService", "./services/analytics-service");
$injector.require("analyticsService", "./services/analytics/analytics-service");
$injector.require("eqatecAnalyticsProvider", "./services/analytics/eqatec-analytics-provider");
$injector.require("googleAnalyticsProvider", "./services/analytics/google-analytics-provider");

$injector.require("emulatorSettingsService", "./services/emulator-settings-service");

Expand Down
1 change: 0 additions & 1 deletion lib/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export class RunCommandBase implements ICommand {

public platform: string;
constructor(protected $platformService: IPlatformService,
protected $liveSyncService: ILiveSyncService,
protected $projectData: IProjectData,
protected $options: IOptions,
protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
Expand Down
2 changes: 1 addition & 1 deletion lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class StaticConfig extends StaticConfigBase implements IStaticConfig {
}

public get PATH_TO_BOOTSTRAP(): string {
return path.join(__dirname, "bootstrap");
return path.join(__dirname, "bootstrap.js");
}

public async getAdbFilePath(): Promise<string> {
Expand Down
18 changes: 18 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,21 @@ export const enum NativePlatformStatus {
requiresPrepare = "2",
alreadyPrepared = "3"
}

export const enum DebugTools {
Chrome = "Chrome",
Inspector = "Inspector"
}

export const enum TrackActionNames {
Build = "Build",
CreateProject = "Create project",
Debug = "Debug",
Deploy = "Deploy",
LiveSync = "LiveSync"
}

export const enum BuildStates {
Clean = "Clean",
Incremental = "Incremental"
}
1 change: 1 addition & 0 deletions lib/nativescript-cli-lib-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ $injector.requirePublicClass("localBuildService", "./services/local-build-servic
// We need this because some services check if (!$options.justlaunch) to start the device log after some operation.
// We don't want this behaviour when the CLI is required as library.
$injector.resolve("options").justlaunch = true;
$injector.resolve<IStaticConfig>("staticConfig").disableAnalytics = true;
34 changes: 0 additions & 34 deletions lib/services/analytics-service.ts

This file was deleted.

37 changes: 19 additions & 18 deletions lib/services/analytics-settings-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createGUID } from "../common/helpers";

class AnalyticsSettingsService implements IAnalyticsSettingsService {
private static SESSIONS_STARTED_OBSOLETE_KEY = "SESSIONS_STARTED";
private static SESSIONS_STARTED_KEY_PREFIX = "SESSIONS_STARTED_";

constructor(private $userSettingsService: UserSettings.IUserSettingsService,
Expand All @@ -12,16 +11,12 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService {
return true;
}

public async getUserId(): Promise<string> {
let currentUserId = await this.$userSettingsService.getSettingValue<string>("USER_ID");
if (!currentUserId) {
currentUserId = createGUID(false);

this.$logger.trace(`Setting new USER_ID: ${currentUserId}.`);
await this.$userSettingsService.saveSetting<string>("USER_ID", currentUserId);
}
public getUserId(): Promise<string> {
return this.getSettingValueOrDefault("USER_ID");
}

return currentUserId;
public getClientId(): Promise<string> {
return this.getSettingValueOrDefault(this.$staticConfig.ANALYTICS_INSTALLATION_ID_SETTING_NAME);
}

public getClientName(): string {
Expand All @@ -33,14 +28,8 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService {
}

public async getUserSessionsCount(projectName: string): Promise<number> {
const oldSessionCount = await this.$userSettingsService.getSettingValue<number>(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY);

if (oldSessionCount) {
// remove the old property for sessions count
await this.$userSettingsService.removeSetting(AnalyticsSettingsService.SESSIONS_STARTED_OBSOLETE_KEY);
}

return await this.$userSettingsService.getSettingValue<number>(this.getSessionsProjectKey(projectName)) || oldSessionCount || 0;
const sessionsCountForProject = await this.$userSettingsService.getSettingValue<number>(this.getSessionsProjectKey(projectName));
return sessionsCountForProject || 0;
}

public async setUserSessionsCount(count: number, projectName: string): Promise<void> {
Expand All @@ -50,5 +39,17 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService {
private getSessionsProjectKey(projectName: string): string {
return `${AnalyticsSettingsService.SESSIONS_STARTED_KEY_PREFIX}${projectName}`;
}

private async getSettingValueOrDefault(settingName: string): Promise<string> {
let guid = await this.$userSettingsService.getSettingValue<string>(settingName);
if (!guid) {
guid = createGUID(false);

this.$logger.trace(`Setting new ${settingName}: ${guid}.`);
await this.$userSettingsService.saveSetting<string>(settingName, guid);
}

return guid;
}
}
$injector.register("analyticsSettingsService", AnalyticsSettingsService);
60 changes: 60 additions & 0 deletions lib/services/analytics/analytics-broker-process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as fs from "fs";
import { AnalyticsBroker } from "./analytics-broker";

const pathToBootstrap = process.argv[2];
if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) {
throw new Error("Invalid path to bootstrap.");
}

// After requiring the bootstrap we can use $injector
require(pathToBootstrap);

const analyticsBroker = $injector.resolve<IAnalyticsBroker>(AnalyticsBroker, { pathToBootstrap });
let trackingQueue: Promise<void> = Promise.resolve();

let sentFinishMsg = false;
let receivedFinishMsg = false;

const sendDataForTracking = async (data: ITrackingInformation) => {
trackingQueue = trackingQueue.then(() => analyticsBroker.sendDataForTracking(data));
await trackingQueue;
};

const finishTracking = async (data?: ITrackingInformation) => {
if (!sentFinishMsg) {
sentFinishMsg = true;

data = data || { type: TrackingTypes.Finish };
const action = async () => {
await sendDataForTracking(data);
process.disconnect();
};

if (receivedFinishMsg) {
await action();
} else {
// In case we've got here without receiving "finish" message from parent (receivedFinishMsg is false)
// there might be various reasons, but most probably the parent is dead.
// However, there's no guarantee that we've received all messages. So wait some time before sending finish message to children.
setTimeout(async () => {
await action();
}, 1000);
}
}
};

process.on("message", async (data: ITrackingInformation) => {
if (data.type === TrackingTypes.Finish) {
receivedFinishMsg = true;
await finishTracking(data);
return;
}

await sendDataForTracking(data);
});

process.on("disconnect", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can shorten the syntax here with

process.on("disconnect", finishTracking);

you don't need the this context and there's no one actually await-ing this promise

await finishTracking();
});

process.send(AnalyticsMessages.BrokerReadyToReceive);
45 changes: 45 additions & 0 deletions lib/services/analytics/analytics-broker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { cache } from "../../common/decorators";

export class AnalyticsBroker implements IAnalyticsBroker {

@cache()
private async getEqatecAnalyticsProvider(): Promise<IAnalyticsProvider> {
return this.$injector.resolve("eqatecAnalyticsProvider");
}

@cache()
private async getGoogleAnalyticsProvider(): Promise<IGoogleAnalyticsProvider> {
const clientId = await this.$analyticsSettingsService.getClientId();
return this.$injector.resolve("googleAnalyticsProvider", { clientId });
}

constructor(private $analyticsSettingsService: IAnalyticsSettingsService,
private $injector: IInjector) { }

public async sendDataForTracking(trackInfo: ITrackingInformation): Promise<void> {
const eqatecProvider = await this.getEqatecAnalyticsProvider();
const googleProvider = await this.getGoogleAnalyticsProvider();

switch (trackInfo.type) {
case TrackingTypes.Exception:
await eqatecProvider.trackError(<IExceptionsTrackingInformation>trackInfo);
break;
case TrackingTypes.Feature:
await eqatecProvider.trackInformation(<IFeatureTrackingInformation>trackInfo);
break;
case TrackingTypes.AcceptTrackFeatureUsage:
await eqatecProvider.acceptFeatureUsageTracking(<IAcceptUsageReportingInformation>trackInfo);
break;
case TrackingTypes.GoogleAnalyticsData:
await googleProvider.trackHit(<IGoogleAnalyticsTrackingInformation>trackInfo);
break;
case TrackingTypes.Finish:
await eqatecProvider.finishTracking();
break;
default:
throw new Error(`Invalid tracking type: ${trackInfo.type}`);
}

}

}
14 changes: 14 additions & 0 deletions lib/services/analytics/analytics-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Defines messages used in communication between CLI's process and analytics subprocesses.
*/
const enum AnalyticsMessages {
/**
* Analytics Broker is initialized and is ready to receive information for tracking.
*/
BrokerReadyToReceive = "BrokerReadyToReceive",

/**
* Eqatec Analytics process is initialized and is ready to receive information for tracking.
*/
EqatecAnalyticsReadyToReceive = "EqatecAnalyticsReadyToReceive"
}
Loading