Skip to content

Commit 7813962

Browse files
Track project type when deploy/run/livesync is executed
Track the project type (Angular, Pure TypeScript, Pure JavaScript) when any of the commands is executed: * prepare * deploy * run * livesync This will allow us to better understand the type of projects that the users are building.
1 parent a2cbfaf commit 7813962

11 files changed

+185
-14
lines changed

lib/definitions/platform.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ interface IPlatformService {
142142
* @returns {string} The contents of the file or null when there is no such file.
143143
*/
144144
readFile(device: Mobile.IDevice, deviceFilePath: string): IFuture<string>;
145+
146+
/**
147+
* Sends information to analytics for current project type.
148+
* The information is sent once per process for each project.
149+
* In long living process, where the project may change, each of the projects will be tracked after it's being opened.
150+
* @returns {IFuture<void>}
151+
*/
152+
trackProjectType(): IFuture<void>;
145153
}
146154

147155
interface IPlatformData {
@@ -183,4 +191,4 @@ interface INodeModulesDependenciesBuilder {
183191
interface IBuildInfo {
184192
prepareTime: string;
185193
buildTime: string;
186-
}
194+
}

lib/definitions/project.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ interface IProjectData {
1010
projectFilePath: string;
1111
projectId?: string;
1212
dependencies: any;
13+
devDependencies: IStringDictionary;
1314
appDirectoryPath: string;
1415
appResourcesDirectoryPath: string;
16+
projectType: string;
1517
}
1618

1719
interface IProjectDataService {

lib/project-data.ts

+43
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,33 @@ import * as constants from "./constants";
22
import * as path from "path";
33
import { EOL } from "os";
44

5+
interface IProjectType {
6+
type: string;
7+
requiredDependencies?: string[];
8+
isDefaultProjectType?: boolean;
9+
}
10+
511
export class ProjectData implements IProjectData {
612
private static OLD_PROJECT_FILE_NAME = ".tnsproject";
713

14+
/**
15+
* NOTE: Order of the elements is important as the TypeScript dependencies are commonly included in Angular project as well.
16+
*/
17+
private static PROJECT_TYPES: IProjectType[] = [
18+
{
19+
type: "Pure JavaScript",
20+
isDefaultProjectType: true
21+
},
22+
{
23+
type: "Angular",
24+
requiredDependencies: ["@angular/core", "nativescript-angular"]
25+
},
26+
{
27+
type: "Pure TypeScript",
28+
requiredDependencies: ["typescript", "nativescript-dev-typescript"]
29+
}
30+
];
31+
832
public projectDir: string;
933
public platformsDir: string;
1034
public projectFilePath: string;
@@ -13,6 +37,8 @@ export class ProjectData implements IProjectData {
1337
public appDirectoryPath: string;
1438
public appResourcesDirectoryPath: string;
1539
public dependencies: any;
40+
public devDependencies: IStringDictionary;
41+
public projectType: string;
1642

1743
constructor(private $fs: IFileSystem,
1844
private $errors: IErrors,
@@ -48,6 +74,8 @@ export class ProjectData implements IProjectData {
4874
if (data) {
4975
this.projectId = data.id;
5076
this.dependencies = fileContent.dependencies;
77+
this.devDependencies = fileContent.devDependencies;
78+
this.projectType = this.getProjectType();
5179
} else { // This is the case when we have package.json file but nativescipt key is not presented in it
5280
this.tryToUpgradeProject();
5381
}
@@ -57,6 +85,21 @@ export class ProjectData implements IProjectData {
5785
}
5886
}
5987

88+
private getProjectType(): string {
89+
let detectedProjectType = _.find(ProjectData.PROJECT_TYPES, (projectType) => projectType.isDefaultProjectType).type;
90+
91+
const deps: string[] = _.keys(this.dependencies).concat(_.keys(this.devDependencies));
92+
93+
_.each(ProjectData.PROJECT_TYPES, projectType => {
94+
if (_.some(projectType.requiredDependencies, requiredDependency => deps.indexOf(requiredDependency) !== -1)) {
95+
detectedProjectType = projectType.type;
96+
return false;
97+
}
98+
});
99+
100+
return detectedProjectType;
101+
}
102+
60103
private throwNoProjectFoundError(): void {
61104
this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", this.$options.path || path.resolve("."));
62105
}

lib/services/livesync/livesync-service.ts

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class LiveSyncService implements ILiveSyncService {
9999
@helpers.hook('livesync')
100100
private liveSyncCore(liveSyncData: ILiveSyncData[], applicationReloadAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture<void>): IFuture<void> {
101101
return (() => {
102+
this.$platformService.trackProjectType().wait();
103+
102104
let watchForChangeActions: ((event: string, filePath: string, dispatcher: IFutureDispatcher) => void)[] = [];
103105
_.each(liveSyncData, (dataItem) => {
104106
let service: IPlatformLiveSyncService = this.$injector.resolve("platformLiveSyncService", { _liveSyncData: dataItem });

lib/services/platform-service.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ let clui = require("clui");
1212
const buildInfoFileName = ".nsbuildinfo";
1313

1414
export class PlatformService implements IPlatformService {
15+
private _trackedProjectFilePath: string = null;
1516

1617
constructor(private $devicesService: Mobile.IDevicesService,
1718
private $errors: IErrors,
@@ -38,7 +39,8 @@ export class PlatformService implements IPlatformService {
3839
private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory,
3940
private $projectChangesService: IProjectChangesService,
4041
private $emulatorPlatformService: IEmulatorPlatformService,
41-
private $childProcess: IChildProcess) { }
42+
private $childProcess: IChildProcess,
43+
private $analyticsService: IAnalyticsService) { }
4244

4345
public addPlatforms(platforms: string[]): IFuture<void> {
4446
return (() => {
@@ -196,6 +198,8 @@ export class PlatformService implements IPlatformService {
196198
return (() => {
197199
this.validatePlatform(platform);
198200

201+
this.trackProjectType().wait();
202+
199203
//We need dev-dependencies here, so before-prepare hooks will be executed correctly.
200204
try {
201205
this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
@@ -348,7 +352,7 @@ export class PlatformService implements IPlatformService {
348352
}
349353
let platformData = this.$platformsData.getPlatformData(platform);
350354
let forDevice = !buildConfig || buildConfig.buildForDevice;
351-
let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath;
355+
let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath;
352356
if (!this.$fs.exists(outputPath)) {
353357
return true;
354358
}
@@ -372,9 +376,24 @@ export class PlatformService implements IPlatformService {
372376
}).future<boolean>()();
373377
}
374378

379+
public trackProjectType(): IFuture<void> {
380+
return (() => {
381+
// Track each project once per process.
382+
// In long living process, where we may work with multiple projects, we would like to track the information for each of them.
383+
if (this.$projectData && (this.$projectData.projectFilePath !== this._trackedProjectFilePath)) {
384+
this._trackedProjectFilePath = this.$projectData.projectFilePath;
385+
386+
this.$analyticsService.track("Working with project type", this.$projectData.projectType).wait();
387+
}
388+
}).future<void>()();
389+
}
390+
375391
public buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void> {
376392
return (() => {
377393
this.$logger.out("Building project...");
394+
395+
this.trackProjectType().wait();
396+
378397
let platformData = this.$platformsData.getPlatformData(platform);
379398
platformData.platformProjectService.buildProject(platformData.projectRoot, buildConfig).wait();
380399
let prepareInfo = this.$projectChangesService.getPrepareInfo(platform);
@@ -449,6 +468,8 @@ export class PlatformService implements IPlatformService {
449468

450469
public runPlatform(platform: string): IFuture<void> {
451470
return (() => {
471+
this.trackProjectType().wait();
472+
452473
if (this.$options.justlaunch) {
453474
this.$options.watch = false;
454475
}
@@ -484,7 +505,7 @@ export class PlatformService implements IPlatformService {
484505
this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
485506
let found: Mobile.IDeviceInfo[] = [];
486507
if (this.$devicesService.hasDevices) {
487-
found = this.$devicesService.getDevices().filter((device:Mobile.IDeviceInfo) => device.identifier === this.$options.device);
508+
found = this.$devicesService.getDevices().filter((device: Mobile.IDeviceInfo) => device.identifier === this.$options.device);
488509
}
489510
if (found.length === 0) {
490511
this.$errors.fail("Cannot find device with name: %s", this.$options.device);
@@ -514,7 +535,7 @@ export class PlatformService implements IPlatformService {
514535
let deviceFilePath = this.getDeviceBuildInfoFilePath(device);
515536
try {
516537
return JSON.parse(this.readFile(device, deviceFilePath).wait());
517-
} catch(e) {
538+
} catch (e) {
518539
return null;
519540
};
520541
}).future<IBuildInfo>()();
@@ -527,7 +548,7 @@ export class PlatformService implements IPlatformService {
527548
try {
528549
let buildInfoTime = this.$fs.readJson(buildInfoFile);
529550
return buildInfoTime;
530-
} catch(e) {
551+
} catch (e) {
531552
return null;
532553
}
533554
}

test/npm-support.ts

+4
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ function createTestInjector(): IInjector {
8080
testInjector.register("config", StaticConfigLib.Configuration);
8181
testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService);
8282
testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService);
83+
testInjector.register("analyticsService", {
84+
track: () => Future.fromResult()
85+
});
86+
8387
return testInjector;
8488
}
8589

test/platform-commands.ts

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { XmlValidator } from "../lib/xml-validator";
2121
import * as ChildProcessLib from "../lib/common/child-process";
2222
import {CleanCommand} from "../lib/commands/platform-clean";
2323
import ProjectChangesLib = require("../lib/services/project-changes-service");
24+
import Future = require("fibers/future");
2425

2526
let isCommandExecuted = true;
2627

@@ -142,6 +143,10 @@ function createTestInjector() {
142143
testInjector.register("childProcess", ChildProcessLib.ChildProcess);
143144
testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService);
144145
testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService);
146+
testInjector.register("analyticsService", {
147+
track: () => Future.fromResult()
148+
});
149+
145150
return testInjector;
146151
}
147152

test/platform-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ function createTestInjector() {
8181
testInjector.register("childProcess", ChildProcessLib.ChildProcess);
8282
testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService);
8383
testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService);
84+
testInjector.register("analyticsService", {
85+
track: () => Future.fromResult()
86+
});
8487

8588
return testInjector;
8689
}

test/project-data.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ProjectData } from "../lib/project-data";
2+
import { Yok } from "../lib/common/yok";
3+
import { assert } from "chai";
4+
import * as stubs from "./stubs";
5+
import * as path from "path";
6+
7+
describe("projectData", () => {
8+
const createTestInjector = (): IInjector => {
9+
const testInjector = new Yok();
10+
11+
testInjector.register("projectHelper", {
12+
projectDir: null,
13+
sanitizeName: (name: string) => name
14+
});
15+
16+
testInjector.register("fs", {
17+
exists: () => true,
18+
readJson: (): any => null
19+
});
20+
21+
testInjector.register("staticConfig", {
22+
CLIENT_NAME_KEY_IN_PROJECT_FILE: "nativescript",
23+
PROJECT_FILE_NAME: "package.json"
24+
});
25+
26+
testInjector.register("errors", stubs.ErrorsStub);
27+
28+
testInjector.register("logger", stubs.LoggerStub);
29+
30+
testInjector.register("options", {});
31+
32+
testInjector.register("projectData", ProjectData);
33+
34+
return testInjector;
35+
};
36+
37+
describe("projectType", () => {
38+
39+
const assertProjectType = (dependencies: any, devDependencies: any, expectedProjecType: string) => {
40+
const testInjector = createTestInjector();
41+
const fs = testInjector.resolve("fs");
42+
fs.exists = (filePath: string) => filePath && path.basename(filePath) === "package.json";
43+
44+
fs.readJson = () => ({
45+
nativescript: {},
46+
dependencies: dependencies,
47+
devDependencies: devDependencies
48+
});
49+
50+
const projectHelper: IProjectHelper = testInjector.resolve("projectHelper");
51+
projectHelper.projectDir = "projectDir";
52+
53+
const projectData: IProjectData = testInjector.resolve("projectData");
54+
assert.deepEqual(projectData.projectType, expectedProjecType);
55+
};
56+
57+
it("detects project as Angular when @angular/core exists as dependency", () => {
58+
assertProjectType({ "@angular/core": "*" }, null, "Angular");
59+
});
60+
61+
it("detects project as Angular when nativescript-angular exists as dependency", () => {
62+
assertProjectType({ "nativescript-angular": "*" }, null, "Angular");
63+
});
64+
65+
it("detects project as TypeScript when nativescript-dev-typescript exists as dependency", () => {
66+
assertProjectType(null, { "nativescript-dev-typescript": "*" }, "Pure TypeScript");
67+
});
68+
69+
it("detects project as TypeScript when typescript exists as dependency", () => {
70+
assertProjectType(null, { "typescript": "*" }, "Pure TypeScript");
71+
});
72+
73+
it("detects project as JavaScript when no other project type is detected", () => {
74+
assertProjectType(null, null, "Pure JavaScript");
75+
});
76+
});
77+
});

test/project-service.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ describe("Project Service Tests", () => {
154154
"readme": "dummy",
155155
"repository": "dummy"
156156
});
157-
npmInstallationManager.install("tns-template-hello-world", defaultTemplateDir, { dependencyType: "save" }).wait();
158-
defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", "tns-template-hello-world");
157+
npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultTemplateDir, { dependencyType: "save" }).wait();
158+
defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]);
159159
fs.deleteDirectory(path.join(defaultTemplatePath, "node_modules"));
160160

161161
let defaultSpecificVersionTemplateDir = temp.mkdirSync("defaultTemplateSpeciffic");
@@ -167,8 +167,8 @@ describe("Project Service Tests", () => {
167167
"readme": "dummy",
168168
"repository": "dummy"
169169
});
170-
npmInstallationManager.install("tns-template-hello-world", defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }).wait();
171-
defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", "tns-template-hello-world");
170+
npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }).wait();
171+
defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]);
172172
fs.deleteDirectory(path.join(defaultSpecificVersionTemplatePath, "node_modules"));
173173

174174
let angularTemplateDir = temp.mkdirSync("angularTemplate");
@@ -180,8 +180,8 @@ describe("Project Service Tests", () => {
180180
"readme": "dummy",
181181
"repository": "dummy"
182182
});
183-
npmInstallationManager.install("tns-template-hello-world-ng", angularTemplateDir, { dependencyType: "save" }).wait();
184-
angularTemplatePath = path.join(angularTemplateDir, "node_modules", "tns-template-hello-world-ng");
183+
npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["angular"], angularTemplateDir, { dependencyType: "save" }).wait();
184+
angularTemplatePath = path.join(angularTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["angular"]);
185185
fs.deleteDirectory(path.join(angularTemplatePath, "node_modules"));
186186

187187
let typescriptTemplateDir = temp.mkdirSync("typescriptTemplate");
@@ -193,8 +193,8 @@ describe("Project Service Tests", () => {
193193
"readme": "dummy",
194194
"repository": "dummy"
195195
});
196-
npmInstallationManager.install("tns-template-hello-world-ts", typescriptTemplateDir, { dependencyType: "save" }).wait();
197-
typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", "tns-template-hello-world-ts");
196+
npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["typescript"], typescriptTemplateDir, { dependencyType: "save" }).wait();
197+
typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["typescript"]);
198198
fs.deleteDirectory(path.join(typescriptTemplatePath, "node_modules"));
199199
});
200200

test/stubs.ts

+6
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ export class ProjectDataStub implements IProjectData {
236236
dependencies: any;
237237
appDirectoryPath: string;
238238
appResourcesDirectoryPath: string;
239+
devDependencies: IStringDictionary;
240+
projectType: string;
239241
}
240242

241243
export class PlatformsDataStub implements IPlatformsData {
@@ -669,6 +671,10 @@ export class PlatformServiceStub implements IPlatformService {
669671
public readFile(device: Mobile.IDevice, deviceFilePath: string): IFuture<string> {
670672
return Future.fromResult("");
671673
}
674+
675+
public trackProjectType(): IFuture<void> {
676+
return Future.fromResult();
677+
}
672678
}
673679

674680
export class EmulatorPlatformService implements IEmulatorPlatformService {

0 commit comments

Comments
 (0)