Skip to content

fix: project properties are not tracked on every analytics hit #4194

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 1 commit into from
Dec 4, 2018
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
3 changes: 3 additions & 0 deletions lib/common/test/unit-tests/analytics-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ function createTestInjector(testScenario: ITestScenario): IInjector {
testInjector.register("childProcess", {});
testInjector.register("projectDataService", {});
testInjector.register("mobileHelper", {});
testInjector.register("projectHelper", {
projectDir: ""
});

return testInjector;
}
Expand Down
47 changes: 34 additions & 13 deletions lib/services/analytics/analytics-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
private $analyticsSettingsService: IAnalyticsSettingsService,
private $childProcess: IChildProcess,
private $projectDataService: IProjectDataService,
private $mobileHelper: Mobile.IMobileHelper) {
private $mobileHelper: Mobile.IMobileHelper,
private $projectHelper: IProjectHelper) {
}

public setShouldDispose(shouldDispose: boolean): void {
Expand Down Expand Up @@ -81,12 +82,7 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
await this.initAnalyticsStatuses();

if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === AnalyticsStatus.enabled) {
gaSettings.customDimensions = gaSettings.customDimensions || {};
gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown);

const googleAnalyticsData: IGoogleAnalyticsTrackingInformation = _.merge({ type: TrackingTypes.GoogleAnalyticsData, category: AnalyticsClients.Cli }, gaSettings);
this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
return this.sendMessageToBroker(googleAnalyticsData);
return this.forcefullyTrackInGoogleAnalytics(gaSettings);
}
}

Expand Down Expand Up @@ -115,12 +111,7 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
}

const customDimensions: IStringDictionary = {};
if (data.projectDir) {
const projectData = this.$projectDataService.getProjectData(data.projectDir);
customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectData.projectType;
const isShared = projectData.nsConfig ? (projectData.nsConfig.shared || false) : false;
customDimensions[GoogleAnalyticsCustomDimensions.isShared] = isShared.toString();
}
this.setProjectRelatedCustomDimensions(customDimensions, data.projectDir);

const googleAnalyticsEventData: IGoogleAnalyticsEventData = {
googleAnalyticsDataType: GoogleAnalyticsDataType.Event,
Expand All @@ -132,6 +123,36 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
await this.trackInGoogleAnalytics(googleAnalyticsEventData);
}

private forcefullyTrackInGoogleAnalytics(gaSettings: IGoogleAnalyticsData): Promise<void> {
gaSettings.customDimensions = gaSettings.customDimensions || {};
gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown);
this.setProjectRelatedCustomDimensions(gaSettings.customDimensions);

const googleAnalyticsData: IGoogleAnalyticsTrackingInformation = _.merge({ type: TrackingTypes.GoogleAnalyticsData, category: AnalyticsClients.Cli }, gaSettings);
this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
return this.sendMessageToBroker(googleAnalyticsData);
}

private setProjectRelatedCustomDimensions(customDimensions: IStringDictionary, projectDir?: string): IStringDictionary {
if (!projectDir) {
try {
projectDir = this.$projectHelper.projectDir;
} catch (err) {
// In case there's no project dir here, the above getter will fail.
this.$logger.trace("Unable to get the projectDir from projectHelper", err);
}
}

if (projectDir) {
const projectData = this.$projectDataService.getProjectData(projectDir);
customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectData.projectType;
const isShared = !!(projectData.nsConfig && projectData.nsConfig.shared);
customDimensions[GoogleAnalyticsCustomDimensions.isShared] = isShared.toString();
}

return customDimensions;
}

public dispose(): void {
if (this.brokerProcess && this.shouldDisposeInstance) {
this.brokerProcess.disconnect();
Expand Down
74 changes: 61 additions & 13 deletions test/services/analytics/analytics-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const helpers = require("../../../lib/common/helpers");
const originalIsInteractive = helpers.isInteractive;

const trackFeatureUsage = "TrackFeatureUsage";
const createTestInjector = (): IInjector => {
const sampleProjectType = "SampleProjectType";

const createTestInjector = (opts?: { projectHelperErrorMsg?: string, projectDir?: string }): IInjector => {
const testInjector = new Yok();
testInjector.register("options", {});
testInjector.register("logger", stubs.LoggerStub);
Expand Down Expand Up @@ -37,8 +39,15 @@ const createTestInjector = (): IInjector => {
testInjector.register("processService", {
attachToProcessExitSignals: (context: any, callback: () => void): void => undefined
});
testInjector.register("projectDataService", {});
testInjector.register("projectDataService", {
getProjectData: (projectDir?: string): IProjectData => {
return <any>{
projectType: sampleProjectType
};
}
});
testInjector.register("mobileHelper", {});
testInjector.register("projectHelper", new stubs.ProjectHelperStub(opts && opts.projectHelperErrorMsg, opts && opts.projectDir));

return testInjector;
};
Expand Down Expand Up @@ -133,11 +142,11 @@ describe("analyticsService", () => {

assert.isTrue(opts.isChildProcessSpawned);
const logger = testInjector.resolve<stubs.LoggerStub>("logger");
assert.isTrue(logger.traceOutput.indexOf(opts.expectedErrorMessage) !== -1);
assert.isTrue(logger.traceOutput.indexOf(opts.expectedErrorMessage) !== -1, `Tried to find error '${opts.expectedErrorMessage}', but couldn't. logger's trace output is: ${logger.traceOutput}`);
};

const setupTest = (expectedErrorMessage: string): any => {
const testInjector = createTestInjector();
const setupTest = (expectedErrorMessage: string, projectHelperErrorMsg?: string): any => {
const testInjector = createTestInjector({ projectHelperErrorMsg });
const opts = {
isChildProcessSpawned: false,
expectedErrorMessage
Expand Down Expand Up @@ -209,13 +218,33 @@ describe("analyticsService", () => {

await assertExpectedError(testInjector, opts);
});

it("when trying to get projectDir from projectHelper fails", async () => {
const projectHelperErrorMsg = "Failed to find project directory.";
const { testInjector, childProcess, opts } = setupTest(`Unable to get the projectDir from projectHelper Error: ${projectHelperErrorMsg}`, projectHelperErrorMsg);
childProcess.spawn = (command: string, args?: string[], options?: any): any => {
opts.isChildProcessSpawned = true;
const spawnedProcess: any = getSpawnedProcess();

spawnedProcess.connected = true;
spawnedProcess.send = (msg: any, action: () => void): void => {
opts.messageSent = msg;
action();
};

setTimeout(() => spawnedProcess.emit("message", AnalyticsMessages.BrokerReadyToReceive), 1);
return spawnedProcess;
};

await assertExpectedError(testInjector, opts);
});
});

describe("sends correct message to broker", () => {
const setupTest = (expectedResult: any, dataToSend: any, terminalOpts?: { isInteractive: boolean }): { testInjector: IInjector, opts: any } => {
const setupTest = (expectedResult: any, dataToSend: any, terminalOpts?: { isInteractive: boolean }, projectHelperOpts?: { projectDir: string }): { testInjector: IInjector, opts: any } => {
helpers.isInteractive = () => terminalOpts ? terminalOpts.isInteractive : true;

const testInjector = createTestInjector();
const testInjector = createTestInjector(projectHelperOpts);
const opts = {
isChildProcessSpawned: false,
expectedResult,
Expand Down Expand Up @@ -260,19 +289,38 @@ describe("analyticsService", () => {
}
});

const getExpectedResult = (gaDataType: string, analyticsClient?: string): any => ({
type: "googleAnalyticsData",
category: "CLI",
googleAnalyticsDataType: gaDataType,
customDimensions: { customDimension1: "value1", cd5: analyticsClient || "CLI" }
});
const getExpectedResult = (gaDataType: string, analyticsClient?: string, projectType?: string): any => {
const expectedResult: any = {
type: "googleAnalyticsData",
category: "CLI",
googleAnalyticsDataType: gaDataType,
customDimensions: { customDimension1: "value1", cd5: analyticsClient || "CLI" }
};

if (projectType) {
expectedResult.customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectType;
expectedResult.customDimensions[GoogleAnalyticsCustomDimensions.isShared] = false.toString();
}

return expectedResult;
};

_.each([GoogleAnalyticsDataType.Page, GoogleAnalyticsDataType.Event], (googleAnalyticsDataType: string) => {
it(`when data is ${googleAnalyticsDataType}`, async () => {
const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType), getDataToSend(googleAnalyticsDataType));
await assertExpectedResult(testInjector, opts);
});

it(`when data is ${googleAnalyticsDataType} and command is executed from projectDir`, async () => {
const { testInjector, opts } = setupTest(
getExpectedResult(googleAnalyticsDataType, null, sampleProjectType),
getDataToSend(googleAnalyticsDataType),
null,
{ projectDir: "/some-dir" }
);
await assertExpectedResult(testInjector, opts);
});

it(`when data is ${googleAnalyticsDataType} and terminal is not interactive`, async () => {
const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType, AnalyticsClients.Unknown), getDataToSend(googleAnalyticsDataType), { isInteractive: false });
await assertExpectedResult(testInjector, opts);
Expand Down
11 changes: 9 additions & 2 deletions test/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,15 @@ export class ProjectDataService implements IProjectDataService {
}

export class ProjectHelperStub implements IProjectHelper {
get projectDir(): string {
return "";
constructor(public projectHelperErrorMsg?: string, public customProjectDir?: string) {
}

public get projectDir(): string {
if (this.projectHelperErrorMsg) {
throw new Error(this.projectHelperErrorMsg);
}

return this.customProjectDir || "";
}

generateDefaultAppId(appName: string, baseAppId: string): string {
Expand Down