Skip to content

Commit 88e2b10

Browse files
fix: projectType is not tracked on every analytics hit
Currently projectType is 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 the projectType (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 cdd6b0b commit 88e2b10

File tree

3 files changed

+101
-26
lines changed

3 files changed

+101
-26
lines changed

lib/services/analytics/analytics-service.ts

+32-11
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,10 +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-
}
114+
this.setProjectTypeCustomDimension(customDimensions, data.projectDir);
122115

123116
const googleAnalyticsEventData: IGoogleAnalyticsEventData = {
124117
googleAnalyticsDataType: GoogleAnalyticsDataType.Event,
@@ -130,6 +123,34 @@ export class AnalyticsService implements IAnalyticsService, IDisposable {
130123
await this.trackInGoogleAnalytics(googleAnalyticsEventData);
131124
}
132125

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.setProjectTypeCustomDimension(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 setProjectTypeCustomDimension(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+
}
150+
151+
return customDimensions;
152+
}
153+
133154
public dispose(): void {
134155
if (this.brokerProcess && this.shouldDisposeInstance) {
135156
this.brokerProcess.disconnect();

test/services/analytics/analytics-service.ts

+60-13
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,37 @@ 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+
}
303+
304+
return expectedResult;
305+
};
269306

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

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

test/stubs.ts

+9-2
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)