Skip to content

Commit dc345f4

Browse files
Merge pull request #3396 from NativeScript/fatme/playground
feat(Analytics): Respect playground key from package.json file
2 parents 4b5c98c + 76e29f8 commit dc345f4

11 files changed

+309
-13
lines changed

PublicAPI.md

+23
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,29 @@ getUserAgentString(identifier: string): string;
898898
const userAgentString = tns.analyticsSettingsService.getUserAgentString("tns/3.3.0");
899899
```
900900
901+
### getPlaygroundInfo
902+
The `getPlaygroundInfo` method allows retrieving information for projects that are exported from playground
903+
904+
* Definition:
905+
```TypeScript
906+
/**
907+
* Gets information for projects that are exported from playground.
908+
* Returns null in case when project does not have playground key in package.json file (e.g is not exported from playground) and no playground info is saved in userSettings file
909+
* @param {string} projectDir The project directory.
910+
* @returns {Promise<IPlaygroundInfo>} Playground info. { id: string, usedTutorial: boolean }
911+
*/
912+
getPlaygroundInfo(projectDir: string): Promise<IPlaygroundInfo>;
913+
```
914+
915+
* Usage:
916+
```JavaScript
917+
tns.analyticsSettingsService.getPlaygroundInfo("/my/project/path")
918+
.then(playgroundInfo => {
919+
console.log(playgroundInfo.id);
920+
console.log(playgroundInfo.usedTutorial);
921+
});
922+
```
923+
901924
## How to add a new method to Public API
902925
CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification.
903926
For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`.

lib/bootstrap.ts

+2
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,5 @@ $injector.requirePublic("extensibilityService", "./services/extensibility-servic
144144

145145
$injector.require("nodeModulesDependenciesBuilder", "./tools/node-modules/node-modules-dependencies-builder");
146146
$injector.require("subscriptionService", "./services/subscription-service");
147+
148+
$injector.require('playgroundService', './services/playground-service');

lib/definitions/project.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ interface IProjectDataService {
105105
*/
106106
removeDependency(projectDir: string, dependencyName: string): void;
107107

108-
getProjectData(projectDir: string): IProjectData;
108+
getProjectData(projectDir?: string): IProjectData;
109109
}
110110

111111
/**

lib/services/analytics-settings-service.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService {
88
private $staticConfig: IStaticConfig,
99
private $hostInfo: IHostInfo,
1010
private $osInfo: IOsInfo,
11-
private $logger: ILogger) { }
11+
private $logger: ILogger,
12+
private $playgroundService: IPlaygroundService) { }
1213

1314
public async canDoRequest(): Promise<boolean> {
1415
return true;
@@ -23,6 +24,11 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService {
2324
return this.getSettingValueOrDefault(this.$staticConfig.ANALYTICS_INSTALLATION_ID_SETTING_NAME);
2425
}
2526

27+
@exported("analyticsSettingsService")
28+
public async getPlaygroundInfo(projectDir: string): Promise<IPlaygroundInfo> {
29+
return this.$playgroundService.getPlaygroundInfo(projectDir);
30+
}
31+
2632
public getClientName(): string {
2733
return "" + this.$staticConfig.CLIENT_NAME_ALIAS.cyan.bold;
2834
}

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

-8
This file was deleted.

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
4646
this.setCrossClientCustomDimensions(visitor, sessionId);
4747
break;
4848
default:
49-
this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId);
49+
await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId);
5050
break;
5151
}
5252

@@ -60,7 +60,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
6060
}
6161
}
6262

63-
private setCustomDimensions(visitor: ua.Visitor, customDimensions: IStringDictionary, sessionId: string): void {
63+
private async setCustomDimensions(visitor: ua.Visitor, customDimensions: IStringDictionary, sessionId: string): Promise<void> {
6464
const defaultValues: IStringDictionary = {
6565
[GoogleAnalyticsCustomDimensions.cliVersion]: this.$staticConfig.version,
6666
[GoogleAnalyticsCustomDimensions.nodeVersion]: process.version,
@@ -70,6 +70,12 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider {
7070
[GoogleAnalyticsCustomDimensions.client]: AnalyticsClients.Unknown
7171
};
7272

73+
const playgrounInfo = await this.$analyticsSettingsService.getPlaygroundInfo();
74+
if (playgrounInfo && playgrounInfo.id) {
75+
defaultValues[GoogleAnalyticsCustomDimensions.playgroundId] = playgrounInfo.id;
76+
defaultValues[GoogleAnalyticsCustomDimensions.usedTutorial] = playgrounInfo.usedTutorial.toString();
77+
}
78+
7379
customDimensions = _.merge(defaultValues, customDimensions);
7480

7581
_.each(customDimensions, (value, key) => {

lib/services/playground-service.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export class PlaygroundService implements IPlaygroundService {
2+
constructor(private $fs: IFileSystem,
3+
private $projectDataService: IProjectDataService,
4+
private $userSettingsService: IUserSettingsService) { }
5+
6+
public async getPlaygroundInfo(projectDir?: string): Promise<IPlaygroundInfo> {
7+
const projectData = this.getProjectData(projectDir);
8+
if (projectData) {
9+
const projectFileContent = this.$fs.readJson(projectData.projectFilePath);
10+
if (this.hasPlaygroundKey(projectFileContent)) {
11+
const id = projectFileContent.nativescript.playground.id;
12+
let usedTutorial = projectFileContent.nativescript.playground.usedTutorial || false;
13+
14+
// In case when usedTutorial=true is already saved in userSettings file, we shouldn't overwrite it
15+
const playgroundInfo = await this.getPlaygroundInfoFromUserSettingsFile();
16+
if (playgroundInfo && playgroundInfo.usedTutorial) {
17+
usedTutorial = true;
18+
}
19+
20+
delete projectFileContent.nativescript.playground;
21+
this.$fs.writeJson(projectData.projectFilePath, projectFileContent);
22+
23+
const result = { id , usedTutorial };
24+
await this.$userSettingsService.saveSettings(<any>{playground: result});
25+
return result;
26+
}
27+
}
28+
29+
return this.getPlaygroundInfoFromUserSettingsFile();
30+
}
31+
32+
private getProjectData(projectDir: string): IProjectData {
33+
try {
34+
return this.$projectDataService.getProjectData(projectDir);
35+
} catch (e) {
36+
// in case command is executed in non-project folder
37+
return null;
38+
}
39+
}
40+
41+
private hasPlaygroundKey(projectFileContent: any): boolean {
42+
return projectFileContent && projectFileContent.nativescript && projectFileContent.nativescript.playground && projectFileContent.nativescript.playground.id;
43+
}
44+
45+
private async getPlaygroundInfoFromUserSettingsFile(): Promise<IPlaygroundInfo> {
46+
return this.$userSettingsService.getSettingValue<IPlaygroundInfo>("playground");
47+
}
48+
}
49+
$injector.register('playgroundService', PlaygroundService);

test/platform-commands.ts

+3
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ function createTestInjector() {
160160
message: (): void => undefined
161161
})
162162
});
163+
testInjector.register("analyticsSettingsService", {
164+
getPlaygroundInfo: () => Promise.resolve(null)
165+
});
163166

164167
return testInjector;
165168
}

test/plugins-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ function createTestInjector() {
110110
message: (): void => undefined
111111
})
112112
});
113+
testInjector.register("analyticsSettingsService", {
114+
getPlaygroundInfo: () => Promise.resolve(null)
115+
});
113116

114117
return testInjector;
115118
}

test/services/playground-service.ts

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import { assert } from "chai";
2+
import { FileSystemStub } from "../stubs";
3+
import { PlaygroundService } from "../../lib/services/playground-service";
4+
import { Yok } from "../../lib/common/yok";
5+
6+
let userSettings: any = null;
7+
8+
function createTestInjector(): IInjector {
9+
const testInjector = new Yok();
10+
11+
testInjector.register("playgroundService", PlaygroundService);
12+
testInjector.register("fs", FileSystemStub);
13+
testInjector.register("projectDataService", {});
14+
testInjector.register("userSettingsService", {});
15+
testInjector.register("injector", testInjector);
16+
17+
return testInjector;
18+
}
19+
20+
function mockPlaygroundService(testInjector: IInjector, data?: { projectData?: any, nativescriptKey?: any, userSettingsData?: any}) {
21+
const projectDataService = testInjector.resolve<IProjectDataService>("projectDataService");
22+
projectDataService.getProjectData = () => (data && data.projectData) || <IProjectData>{};
23+
24+
const userSettingsService = testInjector.resolve("userSettingsService");
25+
userSettingsService.getSettingValue = async (keyName: string) => {
26+
return data && data.userSettingsData ? data.userSettingsData[keyName] : null;
27+
};
28+
userSettingsService.saveSettings = async (settings: any) => { userSettings = settings; };
29+
30+
const fs = testInjector.resolve<IFileSystem>("fs");
31+
fs.readJson = () => (data && data.nativescriptKey) || {};
32+
}
33+
34+
describe("PlaygroundService", () => {
35+
let testInjector: IInjector = null;
36+
let playgroundService: IPlaygroundService = null;
37+
38+
beforeEach(() => {
39+
testInjector = createTestInjector();
40+
playgroundService = testInjector.resolve("playgroundService");
41+
});
42+
43+
describe("getPlaygroundInfo", () => {
44+
it("should return null when projectDir is not specified and no playground data is saved in userSettings file", async () => {
45+
mockPlaygroundService(testInjector, { userSettingsData: null });
46+
const result = await playgroundService.getPlaygroundInfo();
47+
assert.equal(result, null);
48+
});
49+
it("should return saved playgroundData from userSettings file when projectDir is not specified and playground data is already saved in userSettings file", async () => {
50+
mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: false}}});
51+
const actualResult = await playgroundService.getPlaygroundInfo();
52+
const expectedResult = {id: "test-playground-identifier", usedTutorial: false};
53+
assert.deepEqual(actualResult, expectedResult);
54+
});
55+
it("should return null when projectFile has no nativescript key in package.json file and no playground data is saved in userSettings file", async () => {
56+
mockPlaygroundService(testInjector, { userSettingsData: null });
57+
const result = await playgroundService.getPlaygroundInfo();
58+
assert.equal(result, null);
59+
});
60+
it("should return saved playgroundData from userSettings file when projectFile has no nativescript key in package.json and some playground data is already saved in userSettings file", async () => {
61+
mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: true}}});
62+
const actualResult = await playgroundService.getPlaygroundInfo();
63+
const expectedResult = {id: "test-playground-identifier", usedTutorial: true};
64+
assert.deepEqual(actualResult, expectedResult);
65+
});
66+
67+
describe("should return playgroundInfo when project has playground key in package.json", () => {
68+
it("and no usedTutorial", async () => {
69+
const nativescriptKey = {
70+
nativescript: {
71+
playground: {
72+
id: "test-guid"
73+
}
74+
}
75+
};
76+
mockPlaygroundService(testInjector, { nativescriptKey });
77+
const actualResult = await playgroundService.getPlaygroundInfo();
78+
const expectedResult = { id: "test-guid", usedTutorial: false };
79+
assert.deepEqual(actualResult, expectedResult);
80+
});
81+
it("and usedTutorial is true", async() => {
82+
const nativescriptKey = {
83+
nativescript: {
84+
playground: {
85+
id: "test-guid",
86+
usedTutorial: true
87+
}
88+
}
89+
};
90+
mockPlaygroundService(testInjector, { nativescriptKey });
91+
const actualResult = await playgroundService.getPlaygroundInfo();
92+
const expectedResult = { id: 'test-guid', usedTutorial: true };
93+
assert.deepEqual(actualResult, expectedResult);
94+
});
95+
it("and usedTutorial is false", async () => {
96+
const nativescriptKey = {
97+
nativescript: {
98+
playground: {
99+
id: "playground-test-guid",
100+
usedTutorial: false
101+
}
102+
}
103+
};
104+
mockPlaygroundService(testInjector, { nativescriptKey });
105+
const actualResult = await playgroundService.getPlaygroundInfo();
106+
const expectedResult = { id: "playground-test-guid", usedTutorial: false };
107+
assert.deepEqual(actualResult, expectedResult);
108+
});
109+
});
110+
111+
describe("should return playgroundInfo from userSettings file", () => {
112+
it("when usedTutorial is true", async () => {
113+
mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: true}}});
114+
const actualResult = await playgroundService.getPlaygroundInfo();
115+
const expectedResult = { id: 'test-playground-identifier', usedTutorial: true };
116+
assert.deepEqual(actualResult, expectedResult);
117+
});
118+
it("when usedTutorial is false", async () => {
119+
mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: false}}});
120+
const actualResult = await playgroundService.getPlaygroundInfo();
121+
const expectedResult = { id: 'test-playground-identifier', usedTutorial: false };
122+
assert.deepEqual(actualResult, expectedResult);
123+
});
124+
});
125+
126+
it("should return undefined when userSettings file does not have playground key", async () => {
127+
mockPlaygroundService(testInjector, { userSettingsData: {}});
128+
const actualResult = await playgroundService.getPlaygroundInfo();
129+
assert.deepEqual(actualResult, undefined);
130+
});
131+
it("should replace playgroundId when another id is already saved in userSettings file", async () => {
132+
const nativescriptKey = {
133+
nativescript: {
134+
playground: {
135+
id: "test-guid"
136+
}
137+
}
138+
};
139+
mockPlaygroundService(testInjector, { nativescriptKey });
140+
let actualResult = await playgroundService.getPlaygroundInfo();
141+
let expectedResult = { id: "test-guid", usedTutorial: false };
142+
assert.deepEqual(actualResult, expectedResult);
143+
144+
const secondNativescriptKey = {
145+
nativescript: {
146+
playground: {
147+
id: "another-test-guid"
148+
}
149+
}
150+
};
151+
mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey });
152+
actualResult = await playgroundService.getPlaygroundInfo();
153+
expectedResult = { id: 'another-test-guid', usedTutorial: false };
154+
assert.deepEqual(actualResult, expectedResult);
155+
assert.deepEqual(userSettings, { playground: { id: 'another-test-guid', usedTutorial: false }});
156+
});
157+
it("should replace usedTutorial when false value is already saved in userSettings file", async () => {
158+
const nativescriptKey = {
159+
nativescript: {
160+
playground: {
161+
id: "test-guid",
162+
usedTutorial: false
163+
}
164+
}
165+
};
166+
mockPlaygroundService(testInjector, { nativescriptKey });
167+
let actualResult = await playgroundService.getPlaygroundInfo();
168+
let expectedResult = { id: "test-guid", usedTutorial: false };
169+
assert.deepEqual(actualResult, expectedResult);
170+
171+
const secondNativescriptKey = {
172+
nativescript: {
173+
playground: {
174+
id: "another-test-guid",
175+
usedTutorial: true
176+
}
177+
}
178+
};
179+
mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey, userSettingsData: {playground: { id: "test-guid", usedTutorial: false }} });
180+
actualResult = await playgroundService.getPlaygroundInfo();
181+
expectedResult = { id: 'another-test-guid', usedTutorial: true };
182+
assert.deepEqual(actualResult, expectedResult);
183+
});
184+
it("shouldn't replace usedTutorial when true value is already saved in userSettings file", async () => {
185+
const nativescriptKey = {
186+
nativescript: {
187+
playground: {
188+
id: "test-guid",
189+
usedTutorial: true
190+
}
191+
}
192+
};
193+
mockPlaygroundService(testInjector, { nativescriptKey });
194+
let actualResult = await playgroundService.getPlaygroundInfo();
195+
let expectedResult = { id: "test-guid", usedTutorial: true };
196+
assert.deepEqual(actualResult, expectedResult);
197+
198+
const secondNativescriptKey = {
199+
nativescript: {
200+
playground: {
201+
id: "another-test-guid",
202+
usedTutorial: false
203+
}
204+
}
205+
};
206+
mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey, userSettingsData: {playground: { id: "test-guid", usedTutorial: true }} });
207+
actualResult = await playgroundService.getPlaygroundInfo();
208+
expectedResult = { id: 'another-test-guid', usedTutorial: true };
209+
assert.deepEqual(actualResult, expectedResult);
210+
});
211+
});
212+
});

0 commit comments

Comments
 (0)