diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 54f9cfa4ac..4477d677b1 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -35,7 +35,6 @@ $injector.require("androidDebugService", "./services/android-debug-service"); $injector.require("userSettingsService", "./services/user-settings-service"); $injector.requirePublic("analyticsSettingsService", "./services/analytics-settings-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"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 0129c33f05..3869b2640b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -34,7 +34,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements const debugOptions = _.cloneDeep(this.$options.argv); - await this.$platformService.trackProjectType(this.$projectData); const selectedDeviceForDebug = await this.getDeviceForDebug(); const debugData = this.$debugDataService.createDebugData(this.$projectData, {device: selectedDeviceForDebug.deviceInfo.identifier}); diff --git a/lib/common/commands/analytics.ts b/lib/common/commands/analytics.ts index 3735c346c6..52e9d10871 100644 --- a/lib/common/commands/analytics.ts +++ b/lib/common/commands/analytics.ts @@ -31,11 +31,11 @@ class AnalyticsCommand implements ICommand { switch (arg.toLowerCase()) { case "enable": await this.$analyticsService.setStatus(this.settingName, true); - await this.$analyticsService.track(this.settingName, "enabled"); + // TODO(Analytics): await this.$analyticsService.track(this.settingName, "enabled"); this.$logger.info(`${this.humanReadableSettingName} is now enabled.`); break; case "disable": - await this.$analyticsService.track(this.settingName, "disabled"); + // TODO(Analytics): await this.$analyticsService.track(this.settingName, "disabled"); await this.$analyticsService.setStatus(this.settingName, false); this.$logger.info(`${this.humanReadableSettingName} is now disabled.`); break; diff --git a/lib/common/commands/proxy/proxy-base.ts b/lib/common/commands/proxy/proxy-base.ts index ecd448dc11..0dc9d879a2 100644 --- a/lib/common/commands/proxy/proxy-base.ts +++ b/lib/common/commands/proxy/proxy-base.ts @@ -12,7 +12,10 @@ export abstract class ProxyCommandBase implements ICommand { protected async tryTrackUsage() { try { - await this.$analyticsService.trackFeature(this.commandName); + // TODO(Analytics): Check why we have set the `disableAnalytics` to true and we track the command as separate one + // instead of tracking it through the commandsService. + this.$logger.trace(this.commandName); + // await this.$analyticsService.trackFeature(this.commandName); } catch (ex) { this.$logger.trace("Error in trying to track proxy command usage:"); this.$logger.trace(ex); diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 7035deb61f..bd7bb95b7f 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -186,11 +186,6 @@ declare const enum TrackingTypes { */ Initialization = "initialization", - /** - * Defines that the data contains feature that should be tracked. - */ - Feature = "feature", - /** * Defines that the data contains exception that should be tracked. */ @@ -670,19 +665,10 @@ interface IDictionary { interface IAnalyticsService { checkConsent(): Promise; - trackFeature(featureName: string): Promise; trackException(exception: any, message: string): Promise; setStatus(settingName: string, enabled: boolean): Promise; getStatusMessage(settingName: string, jsonFormat: boolean, readableSettingName: string): Promise; isEnabled(settingName: string): Promise; - track(featureName: string, featureValue: string): Promise; - - /** - * Tries to stop current eqatec monitor, clean it's state and remove the process.exit event handler. - * @param {string|number} code - Exit code as the method is used for process.exit event handler. - * @return void - */ - tryStopEqatecMonitors(code?: string | number): void; /** * Tracks the answer of question if user allows to be tracked. @@ -2035,4 +2021,4 @@ declare module "stringify-package" { declare module "detect-newline" { function detectNewline(data: string): string | null; export = detectNewline -} \ No newline at end of file +} diff --git a/lib/common/definitions/cli-global.d.ts b/lib/common/definitions/cli-global.d.ts index 80d691ecbf..368029f58d 100644 --- a/lib/common/definitions/cli-global.d.ts +++ b/lib/common/definitions/cli-global.d.ts @@ -7,11 +7,6 @@ interface ICliGlobal extends NodeJS.Global { */ _: any; - /** - * Eqatec analytics instance. - */ - _eqatec: IEqatec; - /** * Global instance of the module used for dependency injection. */ diff --git a/lib/common/definitions/config.d.ts b/lib/common/definitions/config.d.ts index eb92b74925..7d69012b0d 100644 --- a/lib/common/definitions/config.d.ts +++ b/lib/common/definitions/config.d.ts @@ -6,8 +6,6 @@ declare module Config { USER_AGENT_NAME: string; CLIENT_NAME_ALIAS?: string; FULL_CLIENT_NAME?: string; - ANALYTICS_API_KEY: string; - ANALYTICS_EXCEPTIONS_API_KEY: string; ANALYTICS_INSTALLATION_ID_SETTING_NAME: string; TRACK_FEATURE_USAGE_SETTING_NAME: string; ERROR_REPORT_SETTING_NAME: string; diff --git a/lib/common/definitions/eqatec-analytics.d.ts b/lib/common/definitions/eqatec-analytics.d.ts deleted file mode 100644 index 8880a99dbf..0000000000 --- a/lib/common/definitions/eqatec-analytics.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Describes the information that will be passed to analytics for tracking. - */ -interface ITrackingInformation { - /** - * The type of the data sent to analytics service - initalization data, feature to be tracked, error to be tracked, etc. - */ - type: TrackingTypes -} - -/** - * Describes information required for starting Eqatec Analytics tracking. - */ -interface IEqatecInitializeData extends ITrackingInformation { - /** - * The API key of the project in which we'll collect data. - */ - analyticsAPIKey: string; - - /** - * The number of the current session in this project for this user. In case the value is 1, Analytics will count the user as new one. - * Whenever a new session is started, we should increase the count. - */ - userSessionCount: number; - - /** - * The installation identifier of analytics. It is unique per user. - */ - analyticsInstallationId: string; - - /** - * The unique identifier of a user. - */ - userId: string; -} diff --git a/lib/common/definitions/eqatec.d.ts b/lib/common/definitions/eqatec.d.ts deleted file mode 100644 index 033ba41a4c..0000000000 --- a/lib/common/definitions/eqatec.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -interface IEqatecLogging { - logMessage(...args: string[]): void; - logError(...args: string[]): void; -} - -interface IEqatecSettings { - useHttps: boolean; - userAgent: string; - version: string; - useCookies: boolean; - loggingInterface: IEqatecLogging; -} - -interface IEqatecMonitor { - trackFeature(featureNameAndValue: string): void; - trackException(exception: Error, message: string): void; - stop(): void; - setInstallationID(guid: string): void; - setUserID(userId: string): void; - setStartCount(count: number): void; - start(): void; - status(): { isSending: boolean }; -} - -interface IEqatec { - createSettings(analyticsProjectKey: string): IEqatecSettings; - createMonitor(settings: IEqatecSettings): IEqatecMonitor; -} \ No newline at end of file diff --git a/lib/common/services/analytics-service-base.ts b/lib/common/services/analytics-service-base.ts deleted file mode 100644 index bf962a70d6..0000000000 --- a/lib/common/services/analytics-service-base.ts +++ /dev/null @@ -1,347 +0,0 @@ -import * as helpers from "../helpers"; -import { AnalyticsClients } from "../constants"; -import { cache } from "../decorators"; - -const cliGlobal = global; -// HACK -cliGlobal.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; -cliGlobal.XMLHttpRequest.prototype.withCredentials = false; -// HACK -end - -export abstract class AnalyticsServiceBase implements IAnalyticsService, IDisposable { - private static MAX_WAIT_SENDING_INTERVAL = 30000; // in milliseconds - protected eqatecMonitors: IDictionary = {}; - protected featureTrackingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_API_KEY - ]; - - protected acceptUsageReportingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_API_KEY - ]; - - protected exceptionsTrackingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_EXCEPTIONS_API_KEY - ]; - - protected shouldDisposeInstance: boolean = true; - - protected analyticsStatuses: IDictionary = {}; - - constructor(protected $logger: ILogger, - protected $options: IOptions, - protected $staticConfig: Config.IStaticConfig, - protected $processService: IProcessService, - private $prompter: IPrompter, - private $userSettingsService: UserSettings.IUserSettingsService, - private $analyticsSettingsService: IAnalyticsSettingsService, - private $osInfo: IOsInfo) { } - - protected get acceptTrackFeatureSetting(): string { - return `Accept${this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}`; - } - - public setShouldDispose(shouldDispose: boolean): void { - this.shouldDisposeInstance = shouldDispose; - } - - public abstract dispose(): void; - - public async checkConsent(): Promise { - if (await this.$analyticsSettingsService.canDoRequest()) { - const initialTrackFeatureUsageStatus = await this.getStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); - let trackFeatureUsage = initialTrackFeatureUsageStatus === AnalyticsStatus.enabled; - - if (await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME) && helpers.isInteractive()) { - this.$logger.out("Do you want to help us improve " - + this.$analyticsSettingsService.getClientName() - + " by automatically sending anonymous usage statistics? We will not use this information to identify or contact you." - + " You can read our official Privacy Policy at"); - - const message = this.$analyticsSettingsService.getPrivacyPolicyLink(); - trackFeatureUsage = await this.$prompter.confirm(message, () => true); - await this.setStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, trackFeatureUsage); - - await this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: trackFeatureUsage }); - } - - const isErrorReportingUnset = await this.isNotConfirmed(this.$staticConfig.ERROR_REPORT_SETTING_NAME); - const isUsageReportingConfirmed = !await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); - if (isErrorReportingUnset && isUsageReportingConfirmed) { - await this.setStatus(this.$staticConfig.ERROR_REPORT_SETTING_NAME, trackFeatureUsage); - } - } - } - - public async trackAcceptFeatureUsage(settings: { acceptTrackFeatureUsage: boolean }): Promise { - try { - await this.sendDataToEqatecMonitors(this.acceptUsageReportingAPIKeys, - (eqatecMonitor: IEqatecMonitor) => eqatecMonitor.trackFeature(`${this.acceptTrackFeatureSetting}.${settings.acceptTrackFeatureUsage}`)); - } catch (e) { - this.$logger.trace("Analytics exception: ", e); - } - } - - public trackFeature(featureName: string): Promise { - const category = this.$options.analyticsClient || - (helpers.isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.NonInteractive); - return this.track(category, featureName); - } - - public async track(featureName: string, featureValue: string): Promise { - await this.initAnalyticsStatuses(); - this.$logger.trace(`Trying to track feature '${featureName}' with value '${featureValue}'.`); - - if (this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === AnalyticsStatus.enabled) { - await this.trackFeatureCore(`${featureName}.${featureValue}`); - } - } - - public async trackException(exception: any, message: string): Promise { - await this.initAnalyticsStatuses(); - - if (this.analyticsStatuses[this.$staticConfig.ERROR_REPORT_SETTING_NAME] === AnalyticsStatus.enabled - && await this.$analyticsSettingsService.canDoRequest()) { - try { - this.$logger.trace(`Trying to track exception with message '${message}'.`); - - await this.sendDataToEqatecMonitors(this.exceptionsTrackingAPIKeys, - (eqatecMonitor: IEqatecMonitor) => eqatecMonitor.trackException(exception, message)); - } catch (e) { - this.$logger.trace("Analytics exception: ", e); - } - } - } - - public async trackInGoogleAnalytics(gaSettings: IGoogleAnalyticsData): Promise { - // Intentionally left blank. - } - - public async trackEventActionInGoogleAnalytics(data: IEventActionData): Promise { - // Intentionally left blank. - } - - public async setStatus(settingName: string, enabled: boolean): Promise { - this.analyticsStatuses[settingName] = enabled ? AnalyticsStatus.enabled : AnalyticsStatus.disabled; - await this.$userSettingsService.saveSetting(settingName, enabled.toString()); - - if (this.analyticsStatuses[settingName] === AnalyticsStatus.disabled - && this.analyticsStatuses[settingName] === AnalyticsStatus.disabled) { - this.tryStopEqatecMonitors(); - } - } - - public async isEnabled(settingName: string): Promise { - const analyticsStatus = await this.getStatus(settingName); - return analyticsStatus === AnalyticsStatus.enabled; - } - - public tryStopEqatecMonitors(code?: string | number): void { - for (const eqatecMonitorApiKey in this.eqatecMonitors) { - const eqatecMonitor = this.eqatecMonitors[eqatecMonitorApiKey]; - eqatecMonitor.stop(); - delete this.eqatecMonitors[eqatecMonitorApiKey]; - } - } - - public getStatusMessage(settingName: string, jsonFormat: boolean, readableSettingName: string): Promise { - if (jsonFormat) { - return this.getJsonStatusMessage(settingName); - } - - return this.getHumanReadableStatusMessage(settingName, readableSettingName); - } - - protected async trackFeatureCore(featureTrackString: string, settings?: { userInteraction: boolean }): Promise { - try { - if (await this.$analyticsSettingsService.canDoRequest()) { - await this.sendDataToEqatecMonitors(this.featureTrackingAPIKeys, (eqatecMonitor: IEqatecMonitor) => eqatecMonitor.trackFeature(featureTrackString)); - } - } catch (e) { - this.$logger.trace("Analytics exception: ", e); - } - } - - @cache() - protected async initAnalyticsStatuses(): Promise { - if (await this.$analyticsSettingsService.canDoRequest()) { - this.$logger.trace("Initializing analytics statuses."); - const settingsNames = [this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, this.$staticConfig.ERROR_REPORT_SETTING_NAME]; - - for (const settingName of settingsNames) { - await this.getStatus(settingName); - } - - this.$logger.trace("Analytics statuses: ", this.analyticsStatuses); - } - } - - private getIsSending(eqatecMonitor: IEqatecMonitor): boolean { - return eqatecMonitor.status().isSending; - } - - private waitForSending(eqatecMonitor: IEqatecMonitor): Promise { - return new Promise((resolve, reject) => { - const intervalTime = 100; - let remainingTime = AnalyticsServiceBase.MAX_WAIT_SENDING_INTERVAL; - - if (this.getIsSending(eqatecMonitor)) { - const message = `Waiting for analytics to send information. Will check in ${intervalTime}ms.`; - this.$logger.trace(message); - const interval = setInterval(() => { - if (!this.getIsSending(eqatecMonitor) || remainingTime <= 0) { - clearInterval(interval); - resolve(); - } - - remainingTime -= intervalTime; - this.$logger.trace(`${message} Remaining time is: ${remainingTime}`); - }, intervalTime); - } else { - resolve(); - } - }); - } - - private async sendDataToEqatecMonitors(analyticsAPIKeys: string[], eqatecMonitorAction: (eqatecMonitor: IEqatecMonitor) => void): Promise { - for (const eqatecAPIKey of analyticsAPIKeys) { - const eqatecMonitor = await this.start(eqatecAPIKey); - eqatecMonitorAction(eqatecMonitor); - await this.waitForSending(eqatecMonitor); - } - } - - private async getCurrentSessionCount(analyticsProjectKey: string): Promise { - let currentCount = await this.$analyticsSettingsService.getUserSessionsCount(analyticsProjectKey); - await this.$analyticsSettingsService.setUserSessionsCount(++currentCount, analyticsProjectKey); - - return currentCount; - } - - private async getEqatecSettings(analyticsAPIKey: string): Promise { - return { - analyticsAPIKey, - analyticsInstallationId: await this.$analyticsSettingsService.getClientId(), - type: TrackingTypes.Initialization, - userId: await this.$analyticsSettingsService.getUserId(), - userSessionCount: await this.getCurrentSessionCount(analyticsAPIKey) - }; - } - - private async getStatus(settingName: string): Promise { - if (!_.has(this.analyticsStatuses, settingName)) { - const settingValue = await this.$userSettingsService.getSettingValue(settingName); - - if (settingValue) { - const isEnabled = helpers.toBoolean(settingValue); - if (isEnabled) { - this.analyticsStatuses[settingName] = AnalyticsStatus.enabled; - } else { - this.analyticsStatuses[settingName] = AnalyticsStatus.disabled; - } - } else { - this.analyticsStatuses[settingName] = AnalyticsStatus.notConfirmed; - } - } - - return this.analyticsStatuses[settingName]; - } - - private async start(analyticsAPIKey: string): Promise { - const eqatecMonitorForSpecifiedAPIKey = this.eqatecMonitors[analyticsAPIKey]; - if (eqatecMonitorForSpecifiedAPIKey) { - return eqatecMonitorForSpecifiedAPIKey; - } - - const analyticsSettings = await this.getEqatecSettings(analyticsAPIKey); - return this.startEqatecMonitor(analyticsSettings); - } - - private async startEqatecMonitor(analyticsSettings: IEqatecInitializeData): Promise { - const eqatecMonitorForSpecifiedAPIKey = this.eqatecMonitors[analyticsSettings.analyticsAPIKey]; - if (eqatecMonitorForSpecifiedAPIKey) { - return eqatecMonitorForSpecifiedAPIKey; - } - - require("../vendor/EqatecMonitor.min"); - const analyticsProjectKey = analyticsSettings.analyticsAPIKey; - const settings = cliGlobal._eqatec.createSettings(analyticsProjectKey); - settings.useHttps = false; - settings.userAgent = this.getUserAgentString(); - settings.version = this.$staticConfig.version; - settings.useCookies = false; - settings.loggingInterface = { - logMessage: this.$logger.trace.bind(this.$logger), - logError: this.$logger.debug.bind(this.$logger) - }; - - const eqatecMonitor = cliGlobal._eqatec.createMonitor(settings); - this.eqatecMonitors[analyticsSettings.analyticsAPIKey] = eqatecMonitor; - - const analyticsInstallationId = analyticsSettings.analyticsInstallationId; - - this.$logger.trace(`${this.$staticConfig.ANALYTICS_INSTALLATION_ID_SETTING_NAME}: ${analyticsInstallationId}`); - eqatecMonitor.setInstallationID(analyticsInstallationId); - - try { - await eqatecMonitor.setUserID(analyticsSettings.userId); - - const currentCount = analyticsSettings.userSessionCount; - eqatecMonitor.setStartCount(currentCount); - } catch (e) { - // user not logged in. don't care. - this.$logger.trace("Error while initializing eqatecMonitor", e); - } - - eqatecMonitor.start(); - - // End the session on process.exit only or in case user disables both usage tracking and exceptions tracking. - this.$processService.attachToProcessExitSignals(this, this.tryStopEqatecMonitors); - - await this.reportNodeVersion(analyticsSettings.analyticsAPIKey); - - return eqatecMonitor; - } - - private async reportNodeVersion(apiKey: string): Promise { - const reportedVersion: string = process.version.slice(1).replace(/[.]/g, "_"); - await this.sendDataToEqatecMonitors([apiKey], (eqatecMonitor: IEqatecMonitor) => eqatecMonitor.trackFeature(`NodeJSVersion.${reportedVersion}`)); - } - - private getUserAgentString(): string { - let userAgentString: string; - const osType = this.$osInfo.type(); - if (osType === "Windows_NT") { - userAgentString = "(Windows NT " + this.$osInfo.release() + ")"; - } else if (osType === "Darwin") { - userAgentString = "(Mac OS X " + this.$osInfo.release() + ")"; - } else { - userAgentString = "(" + osType + ")"; - } - - return userAgentString; - } - - private async isNotConfirmed(settingName: string): Promise { - const analyticsStatus = await this.getStatus(settingName); - return analyticsStatus === AnalyticsStatus.notConfirmed; - } - - private async getHumanReadableStatusMessage(settingName: string, readableSettingName: string): Promise { - let status: string = null; - - if (await this.isNotConfirmed(settingName)) { - status = "disabled until confirmed"; - } else { - status = await this.getStatus(settingName); - } - - return `${readableSettingName} is ${status}.`; - } - - private async getJsonStatusMessage(settingName: string): Promise { - const status = await this.getStatus(settingName); - const enabled = status === AnalyticsStatus.notConfirmed ? null : status === AnalyticsStatus.enabled; - return JSON.stringify({ enabled }); - } - -} diff --git a/lib/common/services/commands-service.ts b/lib/common/services/commands-service.ts index 19c44ab0f2..3a84a7c23e 100644 --- a/lib/common/services/commands-service.ts +++ b/lib/common/services/commands-service.ts @@ -45,7 +45,6 @@ export class CommandsService implements ICommandsService { if (!this.$staticConfig.disableAnalytics && !command.disableAnalytics) { const analyticsService = this.$injector.resolve("analyticsService"); // This should be resolved here due to cyclic dependency await analyticsService.checkConsent(); - await analyticsService.trackFeature(commandName); const beautifiedCommandName = this.beautifyCommandName(commandName).replace(/\|/g, " "); diff --git a/lib/common/static-config-base.ts b/lib/common/static-config-base.ts index 0c297d9ecf..9f2ad357b5 100644 --- a/lib/common/static-config-base.ts +++ b/lib/common/static-config-base.ts @@ -5,8 +5,6 @@ import * as os from "os"; export abstract class StaticConfigBase implements Config.IStaticConfig { public PROJECT_FILE_NAME: string = null; public CLIENT_NAME: string = null; - public ANALYTICS_API_KEY: string = null; - public abstract ANALYTICS_EXCEPTIONS_API_KEY: string; public ANALYTICS_INSTALLATION_ID_SETTING_NAME: string = null; public TRACK_FEATURE_USAGE_SETTING_NAME: string = null; public ERROR_REPORT_SETTING_NAME: string = null; diff --git a/lib/common/test/unit-tests/analytics-service.ts b/lib/common/test/unit-tests/analytics-service.ts index ac659fe11c..75b544aacc 100644 --- a/lib/common/test/unit-tests/analytics-service.ts +++ b/lib/common/test/unit-tests/analytics-service.ts @@ -1,81 +1,13 @@ import { CommonLoggerStub, ErrorsStub } from "./stubs"; import { Yok } from "../../yok"; -import { AnalyticsServiceBase } from '../../services/analytics-service-base'; +import { AnalyticsService } from '../../../services/analytics/analytics-service'; import helpersLib = require("../../helpers"); import { HostInfo } from "../../host-info"; import { OsInfo } from "../../os-info"; -require("../../vendor/EqatecMonitor.min"); // note - it modifies global scope! const assert = require("chai").assert; -const cliGlobal = global; -const originalEqatec = cliGlobal._eqatec; -const originalIsInteractive = helpersLib.isInteractive; -const exception = "Exception"; -const message = "Track Exception Msg"; -let trackedFeatureNamesAndValues: string[] = []; +const originalIsInteractive = helpersLib.isInteractive; let savedSettingNamesAndValues = ""; -let trackedExceptionMessages: string[] = []; -let lastUsedEqatecSettings: IEqatecSettings; -let usedEqatecSettings: IEqatecSettings[] = []; -let apiKeysPassedToCreateEqatecSettings: string[] = []; -let isEqatecStopCalled = false; -let startCalledCount = 0; - -class AnalyticsServiceInheritor extends AnalyticsServiceBase { - public featureTrackingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_API_KEY - ]; - - public acceptUsageReportingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_API_KEY - ]; - - public exceptionsTrackingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_EXCEPTIONS_API_KEY - ]; - - public dispose(): void { - // nothing to do here - } -} - -function setGlobalEqatec(shouldSetUserThrowException: boolean, shouldStartThrow: boolean): void { - cliGlobal._eqatec = { - createSettings: (apiKey: string) => { - apiKeysPassedToCreateEqatecSettings.push(apiKey); - return {}; - }, - createMonitor: (settings: any) => { - lastUsedEqatecSettings = settings; - usedEqatecSettings.push(settings); - - return { - trackFeature: (featureNameAndValue: string) => { - trackedFeatureNamesAndValues.push(featureNameAndValue); - }, - trackException: (exceptionToTrack: any, messageToTrack: string) => { - trackedExceptionMessages.push(messageToTrack); - }, - stop: () => { isEqatecStopCalled = true; }, - setInstallationID: (guid: string) => { /*a mock*/ }, - setUserID: (userId: string) => { - if (shouldSetUserThrowException) { - throw new Error("setUserID throws"); - } - }, - start: () => { - startCalledCount++; - if (shouldStartThrow) { - throw new Error("start throws"); - } - }, - setStartCount: (count: number) => { /*a mock */ }, - status: () => ({ isSending: false }) - }; - }, - - }; -} class UserSettingsServiceStub { constructor(public featureTracking: boolean, @@ -110,12 +42,11 @@ interface ITestScenario { } function createTestInjector(testScenario: ITestScenario): IInjector { - setGlobalEqatec(testScenario.shouldSetUserThrowException, testScenario.shouldStartThrow); - const testInjector = new Yok(); + testInjector.register("logger", CommonLoggerStub); testInjector.register("errors", ErrorsStub); - testInjector.register("analyticsService", AnalyticsServiceBase); + testInjector.register("analyticsService", AnalyticsService); testInjector.register("analyticsSettingsService", { canDoRequest: () => { return Promise.resolve(testScenario.canDoRequest); @@ -162,12 +93,15 @@ function createTestInjector(testScenario: ITestScenario): IInjector { attachToProcessExitSignals: (context: any, callback: () => void): void => (undefined) }); + testInjector.register("childProcess", {}); + testInjector.register("projectDataService", {}); + testInjector.register("mobileHelper", {}); + return testInjector; } -describe("analytics-service-base", () => { +describe("analytics-service", () => { let baseTestScenario: ITestScenario; - const featureName = "unit tests feature"; let service: IAnalyticsService = null; beforeEach(() => { @@ -180,156 +114,15 @@ describe("analytics-service-base", () => { shouldSetUserThrowException: false, shouldStartThrow: false }; - trackedFeatureNamesAndValues = []; - trackedExceptionMessages = []; savedSettingNamesAndValues = ""; - isEqatecStopCalled = false; - lastUsedEqatecSettings = {}; - usedEqatecSettings = []; - apiKeysPassedToCreateEqatecSettings = []; service = null; - startCalledCount = 0; - }); - - afterEach(() => { - // clean up the process.exit event handler - if (service) { - service.tryStopEqatecMonitors(); - } }); after(() => { - cliGlobal._eqatec = originalEqatec; helpersLib.isInteractive = originalIsInteractive; }); - describe("trackFeature", () => { - it("tracks feature when console is interactive", async () => { - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`CLI.${featureName}`) !== -1); - service.tryStopEqatecMonitors(); - }); - - it("tracks feature when console is not interactive", async () => { - baseTestScenario.isInteractive = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`Non-interactive.${featureName}`) !== -1); - service.tryStopEqatecMonitors(); - }); - - it("does not track feature when console is interactive and feature tracking is disabled", async () => { - baseTestScenario.featureTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not track feature when console is not interactive and feature tracking is disabled", async () => { - baseTestScenario.featureTracking = baseTestScenario.isInteractive = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not track feature when console is interactive and feature tracking is enabled, but cannot make request", async () => { - baseTestScenario.canDoRequest = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not track feature when console is not interactive and feature tracking is enabled, but cannot make request", async () => { - baseTestScenario.canDoRequest = baseTestScenario.isInteractive = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not throw exception when eqatec start throws", async () => { - baseTestScenario.shouldStartThrow = true; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackFeature(featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - }); - - describe("trackException", () => { - it("tracks when all conditions are correct", async () => { - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackException(exception, message); - assert.isTrue(trackedExceptionMessages.indexOf(message) !== -1); - }); - - it("does not track when exception tracking is disabled", async () => { - baseTestScenario.exceptionsTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackException(exception, message); - assert.deepEqual(trackedExceptionMessages, []); - }); - - it("does not track when feature tracking is enabled, but cannot make request", async () => { - baseTestScenario.canDoRequest = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackException(exception, message); - assert.deepEqual(trackedExceptionMessages, []); - }); - - it("does not throw exception when eqatec start throws", async () => { - baseTestScenario.shouldStartThrow = true; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.trackException(exception, message); - assert.deepEqual(trackedExceptionMessages, []); - }); - }); - - describe("track", () => { - const name = "unitTests"; - it("tracks when all conditions are correct", async () => { - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.track(name, featureName); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`${name}.${featureName}`) !== -1); - }); - - it("does not track when feature tracking is disabled", async () => { - baseTestScenario.featureTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.track(name, featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not track when feature tracking is enabled, but cannot make request", async () => { - baseTestScenario.canDoRequest = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.track(name, featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - - it("does not throw exception when eqatec start throws", async () => { - baseTestScenario.shouldStartThrow = true; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.track(name, featureName); - assert.deepEqual(trackedFeatureNamesAndValues, []); - }); - }); - describe("isEnabled", () => { it("returns true when analytics status is enabled", async () => { const testInjector = createTestInjector(baseTestScenario); @@ -366,20 +159,6 @@ describe("analytics-service-base", () => { await service.setStatus(staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, false); assert.isTrue(savedSettingNamesAndValues.indexOf(`${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.false`) !== -1); }); - - it("calls eqatec stop when all analytics trackings are disabled", async () => { - baseTestScenario.exceptionsTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - const staticConfig: Config.IStaticConfig = testInjector.resolve("staticConfig"); - // start eqatec - await service.trackFeature(featureName); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`CLI.${featureName}`) !== -1); - await service.setStatus(staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, false); - assert.isTrue(savedSettingNamesAndValues.indexOf(`${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.false`) !== -1); - assert.isTrue(isEqatecStopCalled); - }); - }); describe("getStatusMessage", () => { @@ -515,173 +294,5 @@ describe("analytics-service-base", () => { await service.checkConsent(); assert.deepEqual(savedSettingNamesAndValues, ""); }); - - it("sends information that user had accepted feature tracking", async () => { - baseTestScenario.featureTracking = undefined; - baseTestScenario.exceptionsTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.checkConsent(); - const staticConfig: Config.IStaticConfig = testInjector.resolve("staticConfig"); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`Accept${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.true`) !== -1); - }); - - it("sends information that user had rejected feature tracking", async () => { - baseTestScenario.featureTracking = undefined; - baseTestScenario.prompterConfirmResult = false; - baseTestScenario.exceptionsTracking = false; - const testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - await service.checkConsent(); - const staticConfig: Config.IStaticConfig = testInjector.resolve("staticConfig"); - assert.isTrue(trackedFeatureNamesAndValues.indexOf(`Accept${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.false`) !== -1); - }); - }); - - describe("uses correct settings on different os-es", () => { - const name = "unitTests"; - let testInjector: IInjector; - let osInfo: IOsInfo; - let osType: () => string; - let osRelease: () => string; - const release = "1.0"; - - beforeEach(() => { - testInjector = createTestInjector(baseTestScenario); - service = testInjector.resolve("analyticsService"); - osInfo = testInjector.resolve("osInfo"); - osType = osInfo.type; - osRelease = osInfo.release; - }); - - afterEach(() => { - osInfo.type = osType; - osInfo.release = osRelease; - }); - - it("sets correct userAgent on Windows", async () => { - osInfo.type = () => { return "Windows_NT"; }; - osInfo.release = () => { return release; }; - await service.track(name, featureName); - assert.equal(lastUsedEqatecSettings.userAgent, `(Windows NT ${release})`); - }); - - it("sets correct userAgent on MacOS", async () => { - osInfo.type = () => { return "Darwin"; }; - osInfo.release = () => { return release; }; - await service.track(name, featureName); - assert.equal(lastUsedEqatecSettings.userAgent, `(Mac OS X ${release})`); - }); - - it("sets correct userAgent on other OSs", async () => { - osInfo.type = () => { return "Linux"; }; - osInfo.release = () => { return release; }; - await service.track(name, featureName); - assert.equal(lastUsedEqatecSettings.userAgent, `(Linux)`); - }); - }); - - describe("tracks to multiple projects simultaneously", () => { - let analyticsService: AnalyticsServiceInheritor; - const getNodeJsVersionString = () => { - const reportedVersion = process.version.slice(1).replace(/[.]/g, "_"); - return `NodeJSVersion.${reportedVersion}`; - }; - - it("features when featureTrackingAPIKeys setting contains multiple keys", async () => { - const testInjector = createTestInjector(baseTestScenario); - analyticsService = testInjector.resolve("analyticsService"); - analyticsService.featureTrackingAPIKeys = [ - "ApiKey1", - "ApiKey2" - ]; - - const secondFeatureName = "second feature"; - - await analyticsService.trackFeature(featureName); - await analyticsService.trackFeature(secondFeatureName); - - assert.equal(startCalledCount, 2, "When we have two API Keys, the start method should be called exactly two times."); - assert.deepEqual(apiKeysPassedToCreateEqatecSettings, analyticsService.featureTrackingAPIKeys); - - const featureFullName = `CLI.${featureName}`; - const secondFeatureFullName = `CLI.${secondFeatureName}`; - const nodeJsVersionString = getNodeJsVersionString(); - const expectedTrackedFeatures = [ - nodeJsVersionString, - featureFullName, - nodeJsVersionString, - featureFullName, - secondFeatureFullName, - secondFeatureFullName - ]; - - assert.deepEqual(trackedFeatureNamesAndValues, expectedTrackedFeatures); - }); - - it("exceptions when exceptionsTrackingAPIKeys setting contains multiple keys", async () => { - const testInjector = createTestInjector(baseTestScenario); - analyticsService = testInjector.resolve("analyticsService"); - analyticsService.exceptionsTrackingAPIKeys = [ - "ApiKey1", - "ApiKey2" - ]; - - const secondExceptionMessage = "second exception"; - - await analyticsService.trackException(exception, message); - await analyticsService.trackException(exception, secondExceptionMessage); - - assert.equal(startCalledCount, 2, "When we have two API Keys, the start method should be called exactly two times."); - assert.deepEqual(apiKeysPassedToCreateEqatecSettings, analyticsService.exceptionsTrackingAPIKeys); - - const expectedExceptionMessage = [ - message, - message, // for second project - secondExceptionMessage, - secondExceptionMessage // for second project - ]; - - assert.deepEqual(trackedExceptionMessages, expectedExceptionMessage); - - const nodeJsVersionString = getNodeJsVersionString(); - - // NodeJSVersion should be tracked in all processes for exceptions tracking. - const expectedTrackedMessages = [ - nodeJsVersionString, - nodeJsVersionString, - ]; - assert.deepEqual(trackedFeatureNamesAndValues, expectedTrackedMessages); - }); - - it("accept feature tracking when acceptUsageReportingAPIKeys setting contains multiple keys", async () => { - const testInjector = createTestInjector(baseTestScenario); - analyticsService = testInjector.resolve("analyticsService"); - analyticsService.acceptUsageReportingAPIKeys = [ - "ApiKey1", - "ApiKey2" - ]; - - await analyticsService.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: true }); - await analyticsService.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: false }); - - const staticConfig = testInjector.resolve("staticConfig"); - - assert.equal(startCalledCount, 2, "When we have two API Keys, the start method should be called exactly two times."); - assert.deepEqual(apiKeysPassedToCreateEqatecSettings, analyticsService.acceptUsageReportingAPIKeys); - const acceptTrackFeatureUsageTrue = `Accept${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.true`; - const acceptTrackFeatureUsageFalse = `Accept${staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME}.false`; - const nodeJsVersionString = getNodeJsVersionString(); - const expectedTrackedFeatures = [ - nodeJsVersionString, - acceptTrackFeatureUsageTrue, - nodeJsVersionString, - acceptTrackFeatureUsageTrue, - acceptTrackFeatureUsageFalse, - acceptTrackFeatureUsageFalse - ]; - - assert.deepEqual(trackedFeatureNamesAndValues, expectedTrackedFeatures); - }); }); }); diff --git a/lib/common/vendor/EqatecMonitor.min.js b/lib/common/vendor/EqatecMonitor.min.js deleted file mode 100644 index fc7aa39f00..0000000000 --- a/lib/common/vendor/EqatecMonitor.min.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(k){function z(b){var a=!1,f="",e=0,d=1,g={},h=[],i=[],l=function(a){if(c.isString(a)){var f=c.trim(c.asString(a));if(f){if(c.stringTooLong(b,f,1E3))return null;var a="eq_"+a,d=g[a];d||(d={featureName:f,sessionHitCount:0,timingStart:0},g[a]=d);return d}return null}};return{addFeatureUsage:function(a){if(a=l(a))a.sessionHitCount+=1,d++},startFeatureTiming:function(a){if((a=l(a))&&!(0b++;a+=51*b&52?(b^15?8^Math.random()*(b^20?16:4):4).toString(16):"-");return a},setCookie:function(b,a,f,e,d){var g=document;g&&(b=b+"="+a+";path="+f+";",e&&0f?(c.error(b,"Input is too long: "+a),!0):!1}};(function(){function b(a){return 10>a?"0"+a:a}function a(a){d.lastIndex=0;return d.test(a)?'"'+a.replace(d,function(a){var b=i[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+ -'"':'"'+a+'"'}function f(b,c){var d,e,i,k,n=g,m,j=c[b];j&&"object"===typeof j&&"function"===typeof j.toJSON&&(j=j.toJSON(b));"function"===typeof l&&(j=l.call(c,b,j));switch(typeof j){case "string":return a(j);case "number":return isFinite(j)?""+j:"null";case "boolean":case "null":return""+j;case "object":if(!j)return"null";g+=h;m=[];if("[object Array]"===Object.prototype.toString.apply(j)){k=j.length;for(d=0;d\s*\(/gm,"{anonymous}()@").split("\n")},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")}, -opera10:function(a){var a=a.stacktrace.split("\n"),b=/.*line (\d+), column (\d+) in ((/g,"{anonymous}");a[d++]=i+"@"+h}a.splice(d,a.length-d);return a},opera:function(a){var a=a.message.split("\n"),b=/Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i,c,d,g;c=4; -d=0;for(g=a.length;cc.length;){g=b.test(a.toString());h=[];try{h=Array.prototype.slice.call(a.arguments),a=a.caller}catch(i){a=null}g=g?RegExp.$1||"{anonymous}":"{anonymous}";c[d++]=g+"("+this.stringifyArguments(h)+")";if(!a)break}return c}, -stringifyArguments:function(a){for(var b=0;bc.length?"["+this.stringifyArguments(c)+"]":"["+this.stringifyArguments(Array.prototype.slice.call(c,0,1))+"..."+this.stringifyArguments(Array.prototype.slice.call(c,-1))+"]":c.constructor===Object?a[b]="#object":c.constructor===Function?a[b]="#function":c.constructor===String&&(a[b]='"'+c+'"'))}return a.join(",")}};c.stackTrace=b})(); -var t=k._eqatec||{};k._eqatec=t;t.createMonitor=function(b){var a=0,f="",e=!1,d={logError:function(){},logMessage:function(){}},g=0,h=!1,i=!1,l=0,t="",r="",w="",s={},x=!1,u=!0,n=!1,m=void 0,j=!1,y="",o={};try{b=b||{},f=c.trim(c.asString(b.productId))||"",d=b.loggingInterface||d,w=b.version,s=b.location||{},x=c.cookiesEnabled()&&b.useCookies,u=b.useHttps,m=b.userAgent,n=b.testMode,j=c.trackingDisabled(),y=c.getServerUri(d,f,b.serverUri,u),o=z(d),(32>f.length||36r.length||36; - /** - * Sends information to analytics for current project type. - * The information is sent once per process for each project. - * In long living process, where the project may change, each of the projects will be tracked after it's being opened. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {Promise} - */ - trackProjectType(projectData: IProjectData): Promise; - - /** - * Sends information to analytics for specific platform related action, for example Build, LiveSync, etc. - * @param {ITrackPlatformAction} actionData The data describing current action. - * @returns {Promise} - */ - trackActionForPlatform(actionData: ITrackPlatformAction): Promise; - /** * Saves build information in a proprietary file. * @param {string} platform The build platform. diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 7c1597ec7a..f91463af07 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -178,7 +178,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectName: this.$projectData.projectName } ); - await this.$platformService.trackProjectType(this.$projectData); } } } diff --git a/lib/options.ts b/lib/options.ts index 59e6a78798..821430db09 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -114,6 +114,7 @@ export class Options { var: { type: OptionType.Object }, default: { type: OptionType.Boolean }, count: { type: OptionType.Number }, + analyticsLogFile: { type: OptionType.String }, hooks: { type: OptionType.Boolean, default: true }, link: { type: OptionType.Boolean, default: false }, aab: { type: OptionType.Boolean } diff --git a/lib/services/analytics/analytics-broker-process.ts b/lib/services/analytics/analytics-broker-process.ts index 02b806aa75..6efd6d925f 100644 --- a/lib/services/analytics/analytics-broker-process.ts +++ b/lib/services/analytics/analytics-broker-process.ts @@ -2,16 +2,22 @@ // The instances here are not shared with the ones in main CLI process. import * as fs from "fs"; import { AnalyticsBroker } from "./analytics-broker"; +import { AnalyticsLoggingService } from "./analytics-logging-service"; const pathToBootstrap = process.argv[2]; if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) { throw new Error("Invalid path to bootstrap."); } +const logFile = process.argv[3]; // After requiring the bootstrap we can use $injector require(pathToBootstrap); -const analyticsBroker = $injector.resolve(AnalyticsBroker, { pathToBootstrap }); +const analyticsLoggingService = $injector.resolve(AnalyticsLoggingService, { logFile }); +analyticsLoggingService.logData({ message: "Initializing AnalyticsBroker." }); + +const analyticsBroker = $injector.resolve(AnalyticsBroker, { pathToBootstrap, analyticsLoggingService }); + let trackingQueue: Promise = Promise.resolve(); let sentFinishMsg = false; @@ -23,12 +29,15 @@ const sendDataForTracking = async (data: ITrackingInformation) => { }; const finishTracking = async (data?: ITrackingInformation) => { + analyticsLoggingService.logData({ message: `analytics-broker-process finish tracking started, sentFinishMsg: ${sentFinishMsg}, receivedFinishMsg: ${receivedFinishMsg}` }); + if (!sentFinishMsg) { sentFinishMsg = true; data = data || { type: TrackingTypes.Finish }; const action = async () => { - await sendDataForTracking(data); + await trackingQueue; + analyticsLoggingService.logData({ message: `analytics-broker-process tracking finished` }); process.disconnect(); }; @@ -46,6 +55,8 @@ const finishTracking = async (data?: ITrackingInformation) => { }; process.on("message", async (data: ITrackingInformation) => { + analyticsLoggingService.logData({ message: `analytics-broker-process received message of type: ${data.type}` }); + if (data.type === TrackingTypes.Finish) { receivedFinishMsg = true; await finishTracking(data); @@ -56,7 +67,10 @@ process.on("message", async (data: ITrackingInformation) => { }); process.on("disconnect", async () => { + analyticsLoggingService.logData({ message: "analytics-broker-process received process.disconnect event" }); await finishTracking(); }); +analyticsLoggingService.logData({ message: `analytics-broker-process will send ${AnalyticsMessages.BrokerReadyToReceive} message` }); + process.send(AnalyticsMessages.BrokerReadyToReceive); diff --git a/lib/services/analytics/analytics-broker.ts b/lib/services/analytics/analytics-broker.ts index 25d4bf1c3f..296ba02ff0 100644 --- a/lib/services/analytics/analytics-broker.ts +++ b/lib/services/analytics/analytics-broker.ts @@ -2,47 +2,22 @@ import { cache } from "../../common/decorators"; export class AnalyticsBroker implements IAnalyticsBroker { - @cache() - private async getEqatecAnalyticsProvider(): Promise { - return this.$injector.resolve("eqatecAnalyticsProvider"); - } - @cache() private async getGoogleAnalyticsProvider(): Promise { const clientId = await this.$analyticsSettingsService.getClientId(); - return this.$injector.resolve("googleAnalyticsProvider", { clientId }); + return this.$injector.resolve("googleAnalyticsProvider", { clientId, analyticsLoggingService: this.analyticsLoggingService }); } constructor(private $analyticsSettingsService: IAnalyticsSettingsService, - private $injector: IInjector) { } + private $injector: IInjector, + private analyticsLoggingService: IAnalyticsLoggingService) { } public async sendDataForTracking(trackInfo: ITrackingInformation): Promise { try { - const eqatecProvider = await this.getEqatecAnalyticsProvider(); const googleProvider = await this.getGoogleAnalyticsProvider(); - - switch (trackInfo.type) { - case TrackingTypes.Exception: - await eqatecProvider.trackError(trackInfo); - break; - case TrackingTypes.Feature: - await eqatecProvider.trackInformation(trackInfo); - break; - case TrackingTypes.AcceptTrackFeatureUsage: - await eqatecProvider.acceptFeatureUsageTracking(trackInfo); - break; - case TrackingTypes.GoogleAnalyticsData: - await googleProvider.trackHit(trackInfo); - break; - case TrackingTypes.Finish: - await eqatecProvider.finishTracking(); - break; - default: - throw new Error(`Invalid tracking type: ${trackInfo.type}`); - } + await googleProvider.trackHit(trackInfo); } catch (err) { - // So, lets ignore the error for now until we find out what to do with it. + this.analyticsLoggingService.logData({ message: `AnalyticsBroker unable to execute action in sendDataForTracking: ${err}`, type: AnalyticsLoggingMessageType.Error }); } } - } diff --git a/lib/services/analytics/analytics-constants.ts b/lib/services/analytics/analytics-constants.ts index 5aff79435c..aabd599f5b 100644 --- a/lib/services/analytics/analytics-constants.ts +++ b/lib/services/analytics/analytics-constants.ts @@ -5,10 +5,20 @@ const enum AnalyticsMessages { /** * Analytics Broker is initialized and is ready to receive information for tracking. */ - BrokerReadyToReceive = "BrokerReadyToReceive", + BrokerReadyToReceive = "BrokerReadyToReceive" +} + +/** + * Defines the type of the messages that should be written in the local analyitcs log file (in case such is specified). + */ +const enum AnalyticsLoggingMessageType { + /** + * Information message. This is the default value in case type is not specified. + */ + Info = "Info", /** - * Eqatec Analytics process is initialized and is ready to receive information for tracking. + * Error message - used to indicate that some action while trying to track information did not succeeded. */ - EqatecAnalyticsReadyToReceive = "EqatecAnalyticsReadyToReceive" + Error = "Error" } diff --git a/lib/services/analytics/analytics-logging-service.ts b/lib/services/analytics/analytics-logging-service.ts new file mode 100644 index 0000000000..feb1423d61 --- /dev/null +++ b/lib/services/analytics/analytics-logging-service.ts @@ -0,0 +1,41 @@ +import { EOL } from "os"; + +export class AnalyticsLoggingService implements IAnalyticsLoggingService { + constructor(private $fs: IFileSystem, + private logFile: string) { } + + public logData(analyticsLoggingMessage: IAnalyticsLoggingMessage): void { + if (this.logFile && analyticsLoggingMessage && analyticsLoggingMessage.message) { + analyticsLoggingMessage.type = analyticsLoggingMessage.type || AnalyticsLoggingMessageType.Info; + const formattedDate = this.getFormattedDate(); + this.$fs.appendFile(this.logFile, `[${formattedDate}] [${analyticsLoggingMessage.type}] ${analyticsLoggingMessage.message}${EOL}`); + } + } + + private getFormattedDate(): string { + const currentDate = new Date(); + const year = currentDate.getFullYear(); + const month = this.getFormattedDateComponent((currentDate.getMonth() + 1)); + const day = this.getFormattedDateComponent(currentDate.getDate()); + const hour = this.getFormattedDateComponent(currentDate.getHours()); + const minutes = this.getFormattedDateComponent(currentDate.getMinutes()); + const seconds = this.getFormattedDateComponent(currentDate.getSeconds()); + const milliseconds = this.getFormattedMilliseconds(currentDate); + + return `${[year, month, day].join('-')} ${[hour, minutes, seconds].join(":")}.${milliseconds}`; + } + + private getFormattedDateComponent(component: number): string { + const stringComponent = component.toString(); + return stringComponent.length === 1 ? `0${stringComponent}` : stringComponent; + } + + private getFormattedMilliseconds(date: Date): string { + let milliseconds = date.getMilliseconds().toString(); + while (milliseconds.length < 3) { + milliseconds = `0${milliseconds}`; + } + + return milliseconds; + } +} diff --git a/lib/services/analytics/analytics-service.ts b/lib/services/analytics/analytics-service.ts index 7f34c27a97..3ea2036cdb 100644 --- a/lib/services/analytics/analytics-service.ts +++ b/lib/services/analytics/analytics-service.ts @@ -1,46 +1,73 @@ -import { AnalyticsServiceBase } from "../../common/services/analytics-service-base"; import { ChildProcess } from "child_process"; import * as path from "path"; import { cache } from "../../common/decorators"; -import { isInteractive } from '../../common/helpers'; +import { isInteractive, toBoolean } from '../../common/helpers'; import { DeviceTypes, AnalyticsClients } from "../../common/constants"; -export class AnalyticsService extends AnalyticsServiceBase { +export class AnalyticsService implements IAnalyticsService, IDisposable { private static ANALYTICS_BROKER_START_TIMEOUT = 10 * 1000; private brokerProcess: ChildProcess; - - constructor(protected $logger: ILogger, - protected $options: IOptions, - protected $processService: IProcessService, - $staticConfig: Config.IStaticConfig, - $prompter: IPrompter, - $userSettingsService: UserSettings.IUserSettingsService, - $analyticsSettingsService: IAnalyticsSettingsService, - $osInfo: IOsInfo, + private shouldDisposeInstance: boolean = true; + private analyticsStatuses: IDictionary = {}; + + constructor(private $logger: ILogger, + private $options: IOptions, + private $processService: IProcessService, + private $staticConfig: Config.IStaticConfig, + private $prompter: IPrompter, + private $userSettingsService: UserSettings.IUserSettingsService, + private $analyticsSettingsService: IAnalyticsSettingsService, private $childProcess: IChildProcess, private $projectDataService: IProjectDataService, private $mobileHelper: Mobile.IMobileHelper) { - super($logger, $options, $staticConfig, $processService, $prompter, $userSettingsService, $analyticsSettingsService, $osInfo); } - public track(featureName: string, featureValue: string): Promise { - const data: IFeatureTrackingInformation = { - type: TrackingTypes.Feature, - featureName: featureName, - featureValue: featureValue - }; + public setShouldDispose(shouldDispose: boolean): void { + this.shouldDisposeInstance = shouldDispose; + } + + public async checkConsent(): Promise { + if (await this.$analyticsSettingsService.canDoRequest()) { + const initialTrackFeatureUsageStatus = await this.getStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); + let trackFeatureUsage = initialTrackFeatureUsageStatus === AnalyticsStatus.enabled; + + if (await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME) && isInteractive()) { + this.$logger.out("Do you want to help us improve " + + this.$analyticsSettingsService.getClientName() + + " by automatically sending anonymous usage statistics? We will not use this information to identify or contact you." + + " You can read our official Privacy Policy at"); + + const message = this.$analyticsSettingsService.getPrivacyPolicyLink(); + trackFeatureUsage = await this.$prompter.confirm(message, () => true); + await this.setStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, trackFeatureUsage); + + await this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: trackFeatureUsage }); + } - return this.sendInfoForTracking(data, this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); + const isErrorReportingUnset = await this.isNotConfirmed(this.$staticConfig.ERROR_REPORT_SETTING_NAME); + const isUsageReportingConfirmed = !await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); + if (isErrorReportingUnset && isUsageReportingConfirmed) { + await this.setStatus(this.$staticConfig.ERROR_REPORT_SETTING_NAME, trackFeatureUsage); + } + } } - public trackException(exception: any, message: string): Promise { - const data: IExceptionsTrackingInformation = { - type: TrackingTypes.Exception, - exception, - message - }; + public async setStatus(settingName: string, enabled: boolean): Promise { + this.analyticsStatuses[settingName] = enabled ? AnalyticsStatus.enabled : AnalyticsStatus.disabled; + await this.$userSettingsService.saveSetting(settingName, enabled.toString()); + } - return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME); + public async isEnabled(settingName: string): Promise { + const analyticsStatus = await this.getStatus(settingName); + return analyticsStatus === AnalyticsStatus.enabled; + } + + public getStatusMessage(settingName: string, jsonFormat: boolean, readableSettingName: string): Promise { + if (jsonFormat) { + return this.getJsonStatusMessage(settingName); + } + + return this.getHumanReadableStatusMessage(settingName, readableSettingName); } public async trackAcceptFeatureUsage(settings: { acceptTrackFeatureUsage: boolean }): Promise { @@ -120,10 +147,11 @@ export class AnalyticsService extends AnalyticsServiceBase { @cache() private getAnalyticsBroker(): Promise { return new Promise((resolve, reject) => { - const broker = this.$childProcess.spawn("node", + const broker = this.$childProcess.spawn(process.execPath, [ path.join(__dirname, "analytics-broker-process.js"), - this.$staticConfig.PATH_TO_BOOTSTRAP + this.$staticConfig.PATH_TO_BOOTSTRAP, + this.$options.analyticsLogFile // TODO: Check if passing path with space or quotes will work ], { stdio: ["ignore", "ignore", "ignore", "ipc"], @@ -203,6 +231,72 @@ export class AnalyticsService extends AnalyticsServiceBase { } }); } + + @cache() + private async initAnalyticsStatuses(): Promise { + if (await this.$analyticsSettingsService.canDoRequest()) { + this.$logger.trace("Initializing analytics statuses."); + const settingsNames = [this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, this.$staticConfig.ERROR_REPORT_SETTING_NAME]; + + for (const settingName of settingsNames) { + await this.getStatus(settingName); + } + + this.$logger.trace("Analytics statuses: ", this.analyticsStatuses); + } + } + + private async getStatus(settingName: string): Promise { + if (!_.has(this.analyticsStatuses, settingName)) { + const settingValue = await this.$userSettingsService.getSettingValue(settingName); + + if (settingValue) { + const isEnabled = toBoolean(settingValue); + if (isEnabled) { + this.analyticsStatuses[settingName] = AnalyticsStatus.enabled; + } else { + this.analyticsStatuses[settingName] = AnalyticsStatus.disabled; + } + } else { + this.analyticsStatuses[settingName] = AnalyticsStatus.notConfirmed; + } + } + + return this.analyticsStatuses[settingName]; + } + + private async isNotConfirmed(settingName: string): Promise { + const analyticsStatus = await this.getStatus(settingName); + return analyticsStatus === AnalyticsStatus.notConfirmed; + } + + private async getHumanReadableStatusMessage(settingName: string, readableSettingName: string): Promise { + let status: string = null; + + if (await this.isNotConfirmed(settingName)) { + status = "disabled until confirmed"; + } else { + status = await this.getStatus(settingName); + } + + return `${readableSettingName} is ${status}.`; + } + + private async getJsonStatusMessage(settingName: string): Promise { + const status = await this.getStatus(settingName); + const enabled = status === AnalyticsStatus.notConfirmed ? null : status === AnalyticsStatus.enabled; + return JSON.stringify({ enabled }); + } + + public trackException(exception: any, message: string): Promise { + const data: IExceptionsTrackingInformation = { + type: TrackingTypes.Exception, + exception, + message + }; + + return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME); + } } $injector.register("analyticsService", AnalyticsService); diff --git a/lib/services/analytics/analytics.d.ts b/lib/services/analytics/analytics.d.ts index ecb51c8b1f..0fdaf5e16d 100644 --- a/lib/services/analytics/analytics.d.ts +++ b/lib/services/analytics/analytics.d.ts @@ -1,26 +1,21 @@ /** - * Describes if the user allows to be tracked. + * Describes the information that will be passed to analytics for tracking. */ -interface IAcceptUsageReportingInformation extends ITrackingInformation { +interface ITrackingInformation { /** - * The answer of the question if user allows us to track them. + * The type of the data sent to analytics service - initalization data, feature to be tracked, error to be tracked, etc. */ - acceptTrackFeatureUsage: boolean; + type: TrackingTypes } /** - * Describes information used for tracking feature. + * Describes if the user allows to be tracked. */ -interface IFeatureTrackingInformation extends ITrackingInformation { - /** - * The name of the feature that should be tracked. - */ - featureName: string; - +interface IAcceptUsageReportingInformation extends ITrackingInformation { /** - * Value of the feature that should be tracked. + * The answer of the question if user allows us to track them. */ - featureValue: string; + acceptTrackFeatureUsage: boolean; } /** @@ -50,38 +45,6 @@ interface IAnalyticsBroker { sendDataForTracking(trackInfo: ITrackingInformation): Promise; } -/** - * Describes analytics provider used for tracking in a specific Analytics Service. - */ -interface IAnalyticsProvider { - /** - * Sends exception for tracking in the analytics service provider. - * @param {IExceptionsTrackingInformation} trackInfo The information for exception that should be tracked. - * @returns {Promise} - */ - trackError(trackInfo: IExceptionsTrackingInformation): Promise; - - /** - * Sends feature for tracking in the analytics service provider. - * @param {IFeatureTrackingInformation} trackInfo The information for feature that should be tracked. - * @returns {Promise} - */ - trackInformation(trackInfo: IFeatureTrackingInformation): Promise; - - /** - * Sends information if user accepts to be tracked. - * @param {IAcceptUsageReportingInformation} trackInfo The information, containing user's answer if they allow to be tracked. - * @returns {Promise} - */ - acceptFeatureUsageTracking(data: IAcceptUsageReportingInformation): Promise; - - /** - * Waits for execution of all pending requests and finishes tracking operation - * @returns {Promise} - */ - finishTracking(): Promise; -} - interface IGoogleAnalyticsTrackingInformation extends IGoogleAnalyticsData, ITrackingInformation { } /** @@ -95,3 +58,23 @@ interface IGoogleAnalyticsProvider { */ trackHit(data: IGoogleAnalyticsData): Promise; } + +/** + * Describes message that needs to be logged in the analytics logging file. + */ +interface IAnalyticsLoggingMessage { + message: string; + type?: AnalyticsLoggingMessageType +} + +/** + * Describes methods to get local logs from analytics tracking. + */ +interface IAnalyticsLoggingService { + /** + * Logs specified message to the file specified with `--analyticsLogFile`. + * @param {IAnalyticsLoggingMessage} analyticsLoggingMessage The message that has to be written to the logs file. + * @returns {void} + */ + logData(analyticsLoggingMessage: IAnalyticsLoggingMessage): void; +} diff --git a/lib/services/analytics/eqatec-analytics-provider.ts b/lib/services/analytics/eqatec-analytics-provider.ts deleted file mode 100644 index 93278c7b32..0000000000 --- a/lib/services/analytics/eqatec-analytics-provider.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { AnalyticsServiceBase } from "../../common/services/analytics-service-base"; - -export class EqatecAnalyticsProvider extends AnalyticsServiceBase implements IAnalyticsProvider { - private static ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY = "9912cff308334c6d9ad9c33f76a983e3"; - private static NEW_PROJECT_ANALYTICS_API_KEY = "b40f24fcb4f94bccaf64e4dc6337422e"; - - protected featureTrackingAPIKeys: string[] = [ - this.$staticConfig.ANALYTICS_API_KEY, - EqatecAnalyticsProvider.NEW_PROJECT_ANALYTICS_API_KEY - ]; - - protected acceptUsageReportingAPIKeys: string[] = [ - EqatecAnalyticsProvider.ANALYTICS_FEATURE_USAGE_TRACKING_API_KEY - ]; - - constructor(protected $logger: ILogger, - protected $options: IOptions, - $processService: IProcessService, - $staticConfig: Config.IStaticConfig, - $prompter: IPrompter, - $userSettingsService: UserSettings.IUserSettingsService, - $analyticsSettingsService: IAnalyticsSettingsService, - $osInfo: IOsInfo) { - super($logger, $options, $staticConfig, $processService, $prompter, $userSettingsService, $analyticsSettingsService, $osInfo); - } - - public async trackInformation(data: IFeatureTrackingInformation): Promise { - try { - await this.trackFeatureCore(`${data.featureName}.${data.featureValue}`); - } catch (e) { - this.$logger.trace(`Analytics exception: ${e}`); - } - } - - public async trackError(data: IExceptionsTrackingInformation): Promise { - try { - await this.trackException(data.exception, data.message); - } catch (e) { - this.$logger.trace(`Analytics exception: ${e}`); - } - } - - public async acceptFeatureUsageTracking(data: IAcceptUsageReportingInformation): Promise { - try { - await this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: data.acceptTrackFeatureUsage }); - } catch (e) { - this.$logger.trace(`Analytics exception: ${e}`); - } - } - - public async finishTracking(): Promise { - this.tryStopEqatecMonitors(); - } - - public dispose(): void { - // Intentionally left blank. - } - -} - -$injector.register("eqatecAnalyticsProvider", EqatecAnalyticsProvider); diff --git a/lib/services/analytics/google-analytics-provider.ts b/lib/services/analytics/google-analytics-provider.ts index 20806e9bc5..53cb7bcdd3 100644 --- a/lib/services/analytics/google-analytics-provider.ts +++ b/lib/services/analytics/google-analytics-provider.ts @@ -1,6 +1,7 @@ import * as uuid from "uuid"; import * as ua from "universal-analytics"; import { AnalyticsClients } from "../../common/constants"; +import { cache } from "../../common/decorators"; export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { private currentPage: string; @@ -10,7 +11,8 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { private $analyticsSettingsService: IAnalyticsSettingsService, private $logger: ILogger, private $proxyService: IProxyService, - private $config: IConfiguration) { + private $config: IConfiguration, + private analyticsLoggingService: IAnalyticsLoggingService) { } public async trackHit(trackInfo: IGoogleAnalyticsData): Promise { @@ -19,13 +21,14 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { try { await this.track(this.$config.GA_TRACKING_ID, trackInfo, sessionId); } catch (e) { + this.analyticsLoggingService.logData({ type: AnalyticsLoggingMessageType.Error, message: `Unable to track information ${JSON.stringify(trackInfo)}. Error is: ${e}` }); this.$logger.trace("Analytics exception: ", e); } } - private async track(gaTrackingId: string, trackInfo: IGoogleAnalyticsData, sessionId: string): Promise { - const proxySettings = await this.$proxyService.getCache(); - const proxy = proxySettings && proxySettings.proxy; + @cache() + private getVisitor(gaTrackingId: string, proxy: string): ua.Visitor { + this.analyticsLoggingService.logData({ message: `Initializing Google Analytics visitor for id: ${gaTrackingId} with clientId: ${this.clientId}.` }); const visitor = ua({ tid: gaTrackingId, cid: this.clientId, @@ -34,9 +37,20 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { }, requestOptions: { proxy - } + }, + https: true }); + this.analyticsLoggingService.logData({ message: `Successfully initialized Google Analytics visitor for id: ${gaTrackingId} with clientId: ${this.clientId}.` }); + return visitor; + } + + private async track(gaTrackingId: string, trackInfo: IGoogleAnalyticsData, sessionId: string): Promise { + const proxySettings = await this.$proxyService.getCache(); + const proxy = proxySettings && proxySettings.proxy; + + const visitor = this.getVisitor(gaTrackingId, proxy); + await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId); switch (trackInfo.googleAnalyticsDataType) { @@ -68,6 +82,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { customDimensions = _.merge(defaultValues, customDimensions); _.each(customDimensions, (value, key) => { + this.analyticsLoggingService.logData({ message: `Setting custom dimension ${key} to value ${value}` }); visitor.set(key, value); }); } @@ -76,10 +91,17 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { return new Promise((resolve, reject) => { visitor.event(trackInfo.category, trackInfo.action, trackInfo.label, trackInfo.value, { p: this.currentPage }, (err: Error) => { if (err) { + this.analyticsLoggingService.logData({ + message: `Unable to track event with category: '${trackInfo.category}', action: '${trackInfo.action}', label: '${trackInfo.label}', ` + + `value: '${trackInfo.value}' attached page: ${this.currentPage}. Error is: ${err}.`, + type: AnalyticsLoggingMessageType.Error + }); + reject(err); return; } + this.analyticsLoggingService.logData({ message: `Tracked event with category: '${trackInfo.category}', action: '${trackInfo.action}', label: '${trackInfo.label}', value: '${trackInfo.value}' attached page: ${this.currentPage}.` }); resolve(); }); }); @@ -96,10 +118,16 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { visitor.pageview(pageViewData, (err) => { if (err) { + this.analyticsLoggingService.logData({ + message: `Unable to track pageview with path '${trackInfo.path}' and title: '${trackInfo.title}' Error is: ${err}.`, + type: AnalyticsLoggingMessageType.Error + }); + reject(err); return; } + this.analyticsLoggingService.logData({ message: `Tracked pageview with path '${trackInfo.path}' and title: '${trackInfo.title}'.` }); resolve(); }); }); diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 43feda6b68..4e93ff0124 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -31,7 +31,8 @@ class DoctorService implements IDoctorService { } if (!configOptions || configOptions.trackResult) { - await this.$analyticsService.track("DoctorEnvironmentSetup", hasWarnings ? "incorrect" : "correct"); + // TODO(Analytics): Consider sending this information to Google Analytics + // await this.$analyticsService.track("DoctorEnvironmentSetup", hasWarnings ? "incorrect" : "correct"); } if (hasWarnings) { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index ecac15210b..1da9030853 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -182,8 +182,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { - await this.$platformService.trackProjectType(projectData); - const deviceAppData = liveSyncResultInfo.deviceAppData; const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; @@ -462,8 +460,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); } - await this.trackAction(action, platform, options); - const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); @@ -473,22 +469,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return appInstalledOnDeviceResult; } - private async trackAction(action: string, platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { - if (!options.settings[platform][options.device.deviceInfo.type]) { - let isForDevice = !options.device.isEmulator; - options.settings[platform][options.device.deviceInfo.type] = true; - if (this.$mobileHelper.isAndroidPlatform(platform)) { - options.settings[platform][DeviceTypes.Emulator] = true; - options.settings[platform][DeviceTypes.Device] = true; - isForDevice = null; - } - - await this.$platformService.trackActionForPlatform({ action, platform, isForDevice }); - } - - await this.$platformService.trackActionForPlatform({ action: LiveSyncTrackActionNames.DEVICE_INFO, platform, isForDevice: !options.device.isEmulator, deviceOsVersion: options.device.deviceInfo.version }); - } - private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { const rebuildInfo = _.find(options.rebuiltInformation, info => info.platform === platform && (this.$mobileHelper.isAndroidPlatform(platform) || info.isEmulator === options.device.isEmulator)); @@ -546,7 +526,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi liveSyncDeviceInfo: deviceBuildInfoDescriptor }); - await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index c594ba9971..d46fc27e6f 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -20,8 +20,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.$hooksService; } - private _trackedProjectFilePath: string = null; - constructor(private $devicesService: Mobile.IDevicesService, private $preparePlatformNativeService: IPreparePlatformService, private $preparePlatformJSService: IPreparePlatformService, @@ -294,9 +292,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const { platform, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare } = preparePlatformInfo; this.validatePlatform(platform, projectData); - await this.trackProjectType(projectData); - - //We need dev-dependencies here, so before-prepare hooks will be executed correctly. + // We need dev-dependencies here, so before-prepare hooks will be executed correctly. try { await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); } catch (err) { @@ -405,38 +401,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; } - public async trackProjectType(projectData: IProjectData): Promise { - // Track each project once per process. - // In long living process, where we may work with multiple projects, we would like to track the information for each of them. - if (projectData && (projectData.projectFilePath !== this._trackedProjectFilePath)) { - this._trackedProjectFilePath = projectData.projectFilePath; - - await this.$analyticsService.track("Working with project type", projectData.projectType); - } - } - - public async trackActionForPlatform(actionData: ITrackPlatformAction): Promise { - const normalizePlatformName = this.$mobileHelper.normalizePlatformName(actionData.platform); - let featureValue = normalizePlatformName; - if (actionData.isForDevice !== null) { - const deviceType = actionData.isForDevice ? "device" : "emulator"; - featureValue += `.${deviceType}`; - } - - await this.$analyticsService.track(actionData.action, featureValue); - - if (actionData.deviceOsVersion) { - await this.$analyticsService.track(`Device OS version`, `${normalizePlatformName}_${actionData.deviceOsVersion}`); - } - } - public async buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise { this.$logger.out("Building project..."); const action = constants.TrackActionNames.Build; - await this.trackProjectType(projectData); const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; - await this.trackActionForPlatform({ action, platform, isForDevice }); await this.$analyticsService.trackEventActionInGoogleAnalytics({ action, @@ -591,7 +560,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out("Skipping install."); } - await this.trackActionForPlatform({ action: constants.TrackActionNames.Deploy, platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); }; if (deployInfo.deployOptions.device) { diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index 30a655b197..d710ee8beb 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -36,8 +36,6 @@ export class ProjectTemplatesService implements IProjectTemplatesService { this.$fs.deleteDirectory(path.join(templatePath, constants.NODE_MODULES_FOLDER_NAME)); } - await this.$analyticsService.track("Template used for project creation", templateValue); - const templateNameToBeTracked = this.getTemplateNameToBeTracked(templateValue, templatePackageJsonContent); if (templateNameToBeTracked) { await this.$analyticsService.trackEventActionInGoogleAnalytics({ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d3562295b3..66e0f9a66b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8007,10 +8007,6 @@ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.21.tgz", "integrity": "sha1-op4SENqx8QwlZltegBKbqo1pqXs=" }, - "xmlhttprequest": { - "version": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", - "integrity": "sha512-SPnuri0YNyTOxSIU/E0voUTEOEGkwI0kFuLBe9BEsbG8aIRYa9ASuizKzK3XeqnBjypF6tou5NRpuEsAW0CBxg==" - }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", diff --git a/package.json b/package.json index 16fc03f38c..bcb78e78ae 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "xcode": "https://github.com/NativeScript/node-xcode/archive/NativeScript-1.5.2.tar.gz", "xml2js": "0.4.19", "xmldom": "0.1.21", - "xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "yargs": "6.0.0", "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master" }, diff --git a/test/commands/post-install.ts b/test/commands/post-install.ts index 81505cde77..78f0b04cea 100644 --- a/test/commands/post-install.ts +++ b/test/commands/post-install.ts @@ -26,7 +26,7 @@ const createTestInjector = (): IInjector => { testInjector.register("options", {}); testInjector.register("doctorService", { - printWarnings: async (configOptions?: { trackResult: boolean }): Promise => undefined + printWarnings: async (): Promise => undefined }); testInjector.register("analyticsService", { diff --git a/test/stubs.ts b/test/stubs.ts index aab3095ab6..be32216147 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -875,14 +875,6 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return Promise.resolve(""); } - public async trackProjectType(): Promise { - return null; - } - - public async trackActionForPlatform(actionData: ITrackPlatformAction): Promise { - return null; - } - public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { return null; }