Skip to content

Commit 0a7adfa

Browse files
feat: add tracking for companies
Add tracking with information about the company from which CLI is being used. Use the insights endpoint to get information about the company and track it as custom dimensions on each hit. >NOTE: Information will be tracked only when CLI's usage-reporting is enabled. In case you do not want to have this information tracked, execute `tns usage-reporting disable`.
1 parent 602f450 commit 0a7adfa

9 files changed

+289
-2
lines changed

config/config.json

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"DISABLE_HOOKS": false,
77
"UPLOAD_PLAYGROUND_FILES_ENDPOINT": "https://play.nativescript.org/api/files",
88
"SHORTEN_URL_ENDPOINT": "https://play.nativescript.org/api/shortenurl?longUrl=%s",
9+
"INSIGHTS_URL_ENDPOINT": "https://play.nativescript.org/api/insights",
910
"PREVIEW_APP_ENVIRONMENT": "live",
1011
"GA_TRACKING_ID": "UA-111455-51"
1112
}

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ $injector.require("userSettingsService", "./services/user-settings-service");
6767
$injector.requirePublic("analyticsSettingsService", "./services/analytics-settings-service");
6868
$injector.require("analyticsService", "./services/analytics/analytics-service");
6969
$injector.require("googleAnalyticsProvider", "./services/analytics/google-analytics-provider");
70+
$injector.requirePublicClass("companyInsightsService", "./services/company-insights-service");
7071

7172
$injector.require("platformCommandParameter", "./platform-command-param");
7273
$injector.requireCommand("create", "./commands/create-project");

lib/common/services/analytics/google-analytics-custom-dimensions.d.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@ declare const enum GoogleAnalyticsCustomDimensions {
77
nodeVersion = "cd6",
88
playgroundId = "cd7",
99
usedTutorial = "cd8",
10-
isShared = "cd9"
10+
isShared = "cd9",
11+
companyName = "cd10",
12+
companyCountry = "cd11",
13+
companyRevenue = "cd12",
14+
companyIndustries = "cd13",
15+
companyEmployeeCount = "cd14",
1116
}

lib/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export class Configuration implements IConfiguration { // User specific config
88
USE_POD_SANDBOX: boolean = false;
99
UPLOAD_PLAYGROUND_FILES_ENDPOINT: string = null;
1010
SHORTEN_URL_ENDPOINT: string = null;
11+
INSIGHTS_URL_ENDPOINT: string = null;
1112
PREVIEW_APP_ENVIRONMENT: string = null;
1213
GA_TRACKING_ID: string = null;
1314
DISABLE_HOOKS: boolean = false;

lib/declarations.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ interface IConfiguration extends Config.IConfig {
431431
USE_POD_SANDBOX: boolean;
432432
UPLOAD_PLAYGROUND_FILES_ENDPOINT: string;
433433
SHORTEN_URL_ENDPOINT: string;
434+
INSIGHTS_URL_ENDPOINT: string;
434435
PREVIEW_APP_ENVIRONMENT: string;
435436
GA_TRACKING_ID: string;
436437
}
@@ -617,7 +618,6 @@ interface IITMSData {
617618
user: IApplePortalUserDetail;
618619

619620
applicationSpecificPassword: string;
620-
621621
/**
622622
* Path to a .ipa file which will be uploaded.
623623
* @type {string}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Describes the information for a company.
3+
*/
4+
interface ICompanyData {
5+
/**
6+
* The name of the company.
7+
*/
8+
name: string;
9+
10+
/**
11+
* The country where the company is located.
12+
*/
13+
country: string;
14+
15+
/**
16+
* The revenue (stringified) of the company.
17+
*/
18+
revenue: string;
19+
20+
/**
21+
* The industries in which the company is determined to work.
22+
* NOTE: The single value contains multiple industries separated with __
23+
*/
24+
industries: string;
25+
26+
/**
27+
* Number of employees in the company (stringified).
28+
*/
29+
employeeCount: string;
30+
}
31+
32+
/**
33+
* Describes information about the company returned by the Playground's /api/insights endpoint.
34+
*/
35+
interface IPlaygroundInsightsCompanyData {
36+
name: string;
37+
country: string;
38+
revenue: string;
39+
industries: string[];
40+
employeeCount: string;
41+
}
42+
43+
/**
44+
* Describes the information returned by the Playground's /api/insights endpoint.
45+
*/
46+
interface IPlaygroundInsightsEndpointData {
47+
company: IPlaygroundInsightsCompanyData;
48+
}
49+
50+
/**
51+
* Describes the service that can be used to get insights about the company using the CLI.
52+
*/
53+
interface ICompanyInsightsService {
54+
/**
55+
* Describes information about the company.
56+
* @returns {Promise<ICompanyData>}
57+
*/
58+
getCompanyData(): Promise<ICompanyData>;
59+
}

lib/services/analytics/google-analytics-provider.ts

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
1212
private $logger: ILogger,
1313
private $proxyService: IProxyService,
1414
private $config: IConfiguration,
15+
private $companyInsightsService: ICompanyInsightsService,
1516
private analyticsLoggingService: IFileLogService) {
1617
}
1718

@@ -80,6 +81,15 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
8081
defaultValues[GoogleAnalyticsCustomDimensions.usedTutorial] = playgrounInfo.usedTutorial.toString();
8182
}
8283

84+
const companyData = await this.$companyInsightsService.getCompanyData();
85+
if (companyData) {
86+
defaultValues[GoogleAnalyticsCustomDimensions.companyName] = companyData.name;
87+
defaultValues[GoogleAnalyticsCustomDimensions.companyCountry] = companyData.country;
88+
defaultValues[GoogleAnalyticsCustomDimensions.companyRevenue] = companyData.revenue;
89+
defaultValues[GoogleAnalyticsCustomDimensions.companyIndustries] = companyData.industries;
90+
defaultValues[GoogleAnalyticsCustomDimensions.companyEmployeeCount] = companyData.employeeCount;
91+
}
92+
8393
customDimensions = _.merge(defaultValues, customDimensions);
8494

8595
_.each(customDimensions, (value, key) => {
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AnalyticsEventLabelDelimiter } from "../constants";
2+
import { cache } from "../common/decorators";
3+
4+
export class CompanyInsightsService implements ICompanyInsightsService {
5+
constructor(private $config: IConfiguration,
6+
private $httpClient: Server.IHttpClient,
7+
private $logger: ILogger) { }
8+
9+
@cache()
10+
public async getCompanyData(): Promise<ICompanyData> {
11+
let companyData: ICompanyData = null;
12+
try {
13+
const response = await this.$httpClient.httpRequest(this.$config.INSIGHTS_URL_ENDPOINT);
14+
const data = <IPlaygroundInsightsEndpointData>(JSON.parse(response.body));
15+
if (data.company) {
16+
const industries = _.isArray(data.company.industries) ? data.company.industries.join(AnalyticsEventLabelDelimiter) : null;
17+
companyData = {
18+
name: data.company.name,
19+
country: data.company.country,
20+
revenue: data.company.revenue,
21+
employeeCount: data.company.employeeCount,
22+
industries
23+
};
24+
}
25+
} catch (err) {
26+
this.$logger.trace(`Unable to get data for company. Error is: ${err}`);
27+
}
28+
29+
return companyData;
30+
}
31+
}
32+
33+
$injector.register("companyInsightsService", CompanyInsightsService);
+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { assert } from "chai";
2+
import { Yok } from "../../lib/common/yok";
3+
import { LoggerStub } from "../stubs";
4+
import { CompanyInsightsService } from "../../lib/services/company-insights-service";
5+
6+
describe("companyInsightsService", () => {
7+
const insightsUrlEndpoint = "/api/insights";
8+
const createTestInjector = (): IInjector => {
9+
const testInjector = new Yok();
10+
testInjector.register("config", {
11+
INSIGHTS_URL_ENDPOINT: insightsUrlEndpoint
12+
});
13+
testInjector.register("httpClient", {});
14+
testInjector.register("logger", LoggerStub);
15+
testInjector.register("companyInsightsService", CompanyInsightsService);
16+
return testInjector;
17+
};
18+
19+
describe("getCompanyData", () => {
20+
describe("returns null when", () => {
21+
it("the http client fails to get data", async () => {
22+
const testInjector = createTestInjector();
23+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
24+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
25+
const errMsg = "custom error";
26+
httpClient.httpRequest = async () => {
27+
throw new Error(errMsg);
28+
};
29+
30+
const companyData = await companyInsightsService.getCompanyData();
31+
assert.isNull(companyData);
32+
const logger = testInjector.resolve<LoggerStub>("logger");
33+
assert.isTrue(logger.traceOutput.indexOf(errMsg) !== -1);
34+
});
35+
36+
it("the body of the response is not a valid JSON", async () => {
37+
const testInjector = createTestInjector();
38+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
39+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
40+
httpClient.httpRequest = async (): Promise<any> => {
41+
return {
42+
body: "invalid JSON"
43+
};
44+
};
45+
46+
const companyData = await companyInsightsService.getCompanyData();
47+
assert.isNull(companyData);
48+
const logger = testInjector.resolve<LoggerStub>("logger");
49+
assert.isTrue(logger.traceOutput.indexOf("SyntaxError: Unexpected token") !== -1);
50+
});
51+
52+
it("response does not contain company property", async () => {
53+
const httpResultData = {
54+
foo: "bar"
55+
};
56+
57+
const testInjector = createTestInjector();
58+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
59+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
60+
httpClient.httpRequest = async (): Promise<any> => {
61+
return {
62+
body: JSON.stringify(httpResultData)
63+
};
64+
};
65+
66+
const companyData = await companyInsightsService.getCompanyData();
67+
assert.deepEqual(companyData, null);
68+
});
69+
});
70+
71+
describe("returns correct data when", () => {
72+
it("response contains company property", async () => {
73+
const httpResultData = {
74+
company: {
75+
name: "Progress",
76+
country: "Bulgaria",
77+
revenue: "123131",
78+
industries: [
79+
"Software",
80+
"Software 2"
81+
],
82+
employeeCount: "500"
83+
}
84+
};
85+
86+
const testInjector = createTestInjector();
87+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
88+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
89+
httpClient.httpRequest = async (): Promise<any> => {
90+
return {
91+
body: JSON.stringify(httpResultData)
92+
};
93+
};
94+
95+
const companyData = await companyInsightsService.getCompanyData();
96+
assert.deepEqual(companyData, {
97+
name: "Progress",
98+
country: "Bulgaria",
99+
revenue: "123131",
100+
industries: "Software__Software 2",
101+
employeeCount: "500"
102+
});
103+
});
104+
105+
it("response contains company property and industries in it are not populated", async () => {
106+
const httpResultData = {
107+
company: {
108+
name: "Progress",
109+
country: "Bulgaria",
110+
revenue: "123131",
111+
employeeCount: "500"
112+
}
113+
};
114+
115+
const testInjector = createTestInjector();
116+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
117+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
118+
httpClient.httpRequest = async (): Promise<any> => {
119+
return {
120+
body: JSON.stringify(httpResultData)
121+
};
122+
};
123+
124+
const companyData = await companyInsightsService.getCompanyData();
125+
assert.deepEqual(companyData, {
126+
name: "Progress",
127+
country: "Bulgaria",
128+
revenue: "123131",
129+
industries: null,
130+
employeeCount: "500"
131+
});
132+
});
133+
});
134+
135+
it("is called only once per process", async () => {
136+
const httpResultData = {
137+
company: {
138+
name: "Progress",
139+
country: "Bulgaria",
140+
revenue: "123131",
141+
industries: [
142+
"Software",
143+
"Software 2"
144+
],
145+
employeeCount: "500"
146+
}
147+
};
148+
149+
const testInjector = createTestInjector();
150+
const companyInsightsService = testInjector.resolve<ICompanyInsightsService>("companyInsightsService");
151+
const httpClient = testInjector.resolve<Server.IHttpClient>("httpClient");
152+
let httpRequestCounter = 0;
153+
httpClient.httpRequest = async (): Promise<any> => {
154+
httpRequestCounter++;
155+
return {
156+
body: JSON.stringify(httpResultData)
157+
};
158+
};
159+
160+
const expectedData = {
161+
name: "Progress",
162+
country: "Bulgaria",
163+
revenue: "123131",
164+
industries: "Software__Software 2",
165+
employeeCount: "500"
166+
};
167+
const companyData = await companyInsightsService.getCompanyData();
168+
assert.deepEqual(companyData, expectedData);
169+
assert.equal(httpRequestCounter, 1);
170+
171+
const companyDataSecondCall = await companyInsightsService.getCompanyData();
172+
assert.deepEqual(companyDataSecondCall, expectedData);
173+
174+
assert.equal(httpRequestCounter, 1);
175+
});
176+
});
177+
});

0 commit comments

Comments
 (0)