Skip to content

Commit 8ab8ed3

Browse files
fix: project properties are not tracked on every analytics hit
Currently projectType and isShared properties are tracked only when we send events to Google Analytics and when the caller sends the projectDir to the method. In fact we need to track them(based on the projectDir) for every hit in Google Analytics. To achieve this, use the projectHelper, which tries to determine the projectDir. Track the projectType on every page and event send to Google Analytics.
1 parent 0dd8819 commit 8ab8ed3

File tree

4 files changed

+107
-28
lines changed

4 files changed

+107
-28
lines changed

lib/common/test/unit-tests/analytics-service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ function createTestInjector(testScenario: ITestScenario): IInjector {
9696
testInjector.register("childProcess", {});
9797
testInjector.register("projectDataService", {});
9898
testInjector.register("mobileHelper", {});
99+
testInjector.register("projectHelper", {
100+
projectDir: ""
101+
});
99102

100103
return testInjector;
101104
}

lib/services/analytics/analytics-service.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
1919
private $analyticsSettingsService: IAnalyticsSettingsService,
2020
private $childProcess: IChildProcess,
2121
private $projectDataService: IProjectDataService,
22-
private $mobileHelper: Mobile.IMobileHelper) {
22+
private $mobileHelper: Mobile.IMobileHelper,
23+
private $projectHelper: IProjectHelper) {
2324
}
2425

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

8384
if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === AnalyticsStatus.enabled) {
84-
gaSettings.customDimensions = gaSettings.customDimensions || {};
85-
gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown);
86-
87-
const googleAnalyticsData: IGoogleAnalyticsTrackingInformation = _.merge({ type: TrackingTypes.GoogleAnalyticsData, category: AnalyticsClients.Cli }, gaSettings);
88-
this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
89-
return this.sendMessageToBroker(googleAnalyticsData);
85+
return this.forcefullyTrackInGoogleAnalytics(gaSettings);
9086
}
9187
}
9288

@@ -115,12 +111,7 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
115111
}
116112

117113
const customDimensions: IStringDictionary = {};
118-
if (data.projectDir) {
119-
const projectData = this.$projectDataService.getProjectData(data.projectDir);
120-
customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectData.projectType;
121-
const isShared = projectData.nsConfig ? (projectData.nsConfig.shared || false) : false;
122-
customDimensions[GoogleAnalyticsCustomDimensions.isShared] = isShared.toString();
123-
}
114+
this.setProjectRelatedCustomDimensions(customDimensions, data.projectDir);
124115

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

126+
private forcefullyTrackInGoogleAnalytics(gaSettings: IGoogleAnalyticsData): Promise<void> {
127+
gaSettings.customDimensions = gaSettings.customDimensions || {};
128+
gaSettings.customDimensions[GoogleAnalyticsCustomDimensions.client] = this.$options.analyticsClient || (isInteractive() ? AnalyticsClients.Cli : AnalyticsClients.Unknown);
129+
this.setProjectRelatedCustomDimensions(gaSettings.customDimensions);
130+
131+
const googleAnalyticsData: IGoogleAnalyticsTrackingInformation = _.merge({ type: TrackingTypes.GoogleAnalyticsData, category: AnalyticsClients.Cli }, gaSettings);
132+
this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
133+
return this.sendMessageToBroker(googleAnalyticsData);
134+
}
135+
136+
private setProjectRelatedCustomDimensions(customDimensions: IStringDictionary, projectDir?: string): IStringDictionary {
137+
if (!projectDir) {
138+
try {
139+
projectDir = this.$projectHelper.projectDir;
140+
} catch (err) {
141+
// In case there's no project dir here, the above getter will fail.
142+
this.$logger.trace("Unable to get the projectDir from projectHelper", err);
143+
}
144+
}
145+
146+
if (projectDir) {
147+
const projectData = this.$projectDataService.getProjectData(projectDir);
148+
customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectData.projectType;
149+
const isShared = !!(projectData.nsConfig && projectData.nsConfig.shared);
150+
customDimensions[GoogleAnalyticsCustomDimensions.isShared] = isShared.toString();
151+
}
152+
153+
return customDimensions;
154+
}
155+
135156
public dispose(): void {
136157
if (this.brokerProcess && this.shouldDisposeInstance) {
137158
this.brokerProcess.disconnect();

test/services/analytics/analytics-service.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const helpers = require("../../../lib/common/helpers");
99
const originalIsInteractive = helpers.isInteractive;
1010

1111
const trackFeatureUsage = "TrackFeatureUsage";
12-
const createTestInjector = (): IInjector => {
12+
const sampleProjectType = "SampleProjectType";
13+
14+
const createTestInjector = (opts?: { projectHelperErrorMsg?: string, projectDir?: string }): IInjector => {
1315
const testInjector = new Yok();
1416
testInjector.register("options", {});
1517
testInjector.register("logger", stubs.LoggerStub);
@@ -37,8 +39,15 @@ const createTestInjector = (): IInjector => {
3739
testInjector.register("processService", {
3840
attachToProcessExitSignals: (context: any, callback: () => void): void => undefined
3941
});
40-
testInjector.register("projectDataService", {});
42+
testInjector.register("projectDataService", {
43+
getProjectData: (projectDir?: string): IProjectData => {
44+
return <any>{
45+
projectType: sampleProjectType
46+
};
47+
}
48+
});
4149
testInjector.register("mobileHelper", {});
50+
testInjector.register("projectHelper", new stubs.ProjectHelperStub(opts && opts.projectHelperErrorMsg, opts && opts.projectDir));
4251

4352
return testInjector;
4453
};
@@ -133,11 +142,11 @@ describe("analyticsService", () => {
133142

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

139-
const setupTest = (expectedErrorMessage: string): any => {
140-
const testInjector = createTestInjector();
148+
const setupTest = (expectedErrorMessage: string, projectHelperErrorMsg?: string): any => {
149+
const testInjector = createTestInjector({ projectHelperErrorMsg });
141150
const opts = {
142151
isChildProcessSpawned: false,
143152
expectedErrorMessage
@@ -209,13 +218,33 @@ describe("analyticsService", () => {
209218

210219
await assertExpectedError(testInjector, opts);
211220
});
221+
222+
it("when trying to get projectDir from projectHelper fails", async () => {
223+
const projectHelperErrorMsg = "Failed to find project directory.";
224+
const { testInjector, childProcess, opts } = setupTest(`Unable to get the projectDir from projectHelper Error: ${projectHelperErrorMsg}`, projectHelperErrorMsg);
225+
childProcess.spawn = (command: string, args?: string[], options?: any): any => {
226+
opts.isChildProcessSpawned = true;
227+
const spawnedProcess: any = getSpawnedProcess();
228+
229+
spawnedProcess.connected = true;
230+
spawnedProcess.send = (msg: any, action: () => void): void => {
231+
opts.messageSent = msg;
232+
action();
233+
};
234+
235+
setTimeout(() => spawnedProcess.emit("message", AnalyticsMessages.BrokerReadyToReceive), 1);
236+
return spawnedProcess;
237+
};
238+
239+
await assertExpectedError(testInjector, opts);
240+
});
212241
});
213242

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

218-
const testInjector = createTestInjector();
247+
const testInjector = createTestInjector(projectHelperOpts);
219248
const opts = {
220249
isChildProcessSpawned: false,
221250
expectedResult,
@@ -260,19 +289,38 @@ describe("analyticsService", () => {
260289
}
261290
});
262291

263-
const getExpectedResult = (gaDataType: string, analyticsClient?: string): any => ({
264-
type: "googleAnalyticsData",
265-
category: "CLI",
266-
googleAnalyticsDataType: gaDataType,
267-
customDimensions: { customDimension1: "value1", cd5: analyticsClient || "CLI" }
268-
});
292+
const getExpectedResult = (gaDataType: string, analyticsClient?: string, projectType?: string): any => {
293+
const expectedResult: any = {
294+
type: "googleAnalyticsData",
295+
category: "CLI",
296+
googleAnalyticsDataType: gaDataType,
297+
customDimensions: { customDimension1: "value1", cd5: analyticsClient || "CLI" }
298+
};
299+
300+
if (projectType) {
301+
expectedResult.customDimensions[GoogleAnalyticsCustomDimensions.projectType] = projectType;
302+
expectedResult.customDimensions[GoogleAnalyticsCustomDimensions.isShared] = false.toString();
303+
}
304+
305+
return expectedResult;
306+
};
269307

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

314+
it(`when data is ${googleAnalyticsDataType} and command is executed from projectDir`, async () => {
315+
const { testInjector, opts } = setupTest(
316+
getExpectedResult(googleAnalyticsDataType, null, sampleProjectType),
317+
getDataToSend(googleAnalyticsDataType),
318+
null,
319+
{ projectDir: "/some-dir" }
320+
);
321+
await assertExpectedResult(testInjector, opts);
322+
});
323+
276324
it(`when data is ${googleAnalyticsDataType} and terminal is not interactive`, async () => {
277325
const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType, AnalyticsClients.Unknown), getDataToSend(googleAnalyticsDataType), { isInteractive: false });
278326
await assertExpectedResult(testInjector, opts);

test/stubs.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,15 @@ export class ProjectDataService implements IProjectDataService {
519519
}
520520

521521
export class ProjectHelperStub implements IProjectHelper {
522-
get projectDir(): string {
523-
return "";
522+
constructor(public projectHelperErrorMsg?: string, public customProjectDir?: string) {
523+
}
524+
525+
public get projectDir(): string {
526+
if (this.projectHelperErrorMsg) {
527+
throw new Error(this.projectHelperErrorMsg);
528+
}
529+
530+
return this.customProjectDir || "";
524531
}
525532

526533
generateDefaultAppId(appName: string, baseAppId: string): string {

0 commit comments

Comments
 (0)