Skip to content

Commit 4c23fc4

Browse files
committed
feat: introduce kotlin usage tracking
1 parent 6bd1fe6 commit 4c23fc4

11 files changed

+96
-27
lines changed

lib/constants.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export const AWAIT_NOTIFICATION_TIMEOUT_SECONDS = 9;
3535
export const SRC_DIR = "src";
3636
export const MAIN_DIR = "main";
3737
export const ASSETS_DIR = "assets";
38+
export const ANDROID_ANALYTICS_DATA_DIR = "analytics";
39+
export const ANDROID_ANALYTICS_DATA_FILE = "build-statistics.json";
3840
export const MANIFEST_FILE_NAME = "AndroidManifest.xml";
3941
export const APP_GRADLE_FILE_NAME = "app.gradle";
4042
export const INFO_PLIST_FILE_NAME = "Info.plist";
@@ -188,7 +190,8 @@ export const enum TrackActionNames {
188190
PreviewAppData = "Preview App Data",
189191
UninstallCLI = "Uninstall CLI",
190192
UsingRuntimeVersion = "Using Runtime Version",
191-
AddPlatform = "Add Platform"
193+
AddPlatform = "Add Platform",
194+
UsingKotlin = "Using Kotlin"
192195
}
193196

194197
export const AnalyticsEventLabelDelimiter = "__";

lib/definitions/gradle.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ interface IGradleBuildService {
1515
}
1616

1717
interface IGradleBuildArgsService {
18-
getBuildTaskArgs(buildData: IAndroidBuildData): string[];
18+
getBuildTaskArgs(buildData: IAndroidBuildData): Promise<string[]>;
1919
getCleanTaskArgs(buildData: IAndroidBuildData): string[];
2020
}

lib/services/android-project-service.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
2525
private $androidResourcesMigrationService: IAndroidResourcesMigrationService,
2626
private $filesHashService: IFilesHashService,
2727
private $gradleCommandService: IGradleCommandService,
28-
private $gradleBuildService: IGradleBuildService) {
28+
private $gradleBuildService: IGradleBuildService,
29+
private $analyticsService: IAnalyticsService) {
2930
super($fs, $projectDataService);
3031
}
3132

@@ -235,6 +236,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
235236

236237
const outputPath = platformData.getBuildOutputPath(buildData);
237238
await this.$filesHashService.saveHashesForProject(this._platformData, outputPath);
239+
await this.trackKotlinUsage(projectRoot);
238240
}
239241

240242
public async buildForDeploy(projectRoot: string, projectData: IProjectData, buildData?: IAndroidBuildData): Promise<void> {
@@ -444,6 +446,39 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
444446
});
445447
}
446448
}
449+
450+
private async trackKotlinUsage(projectRoot: string): Promise<void> {
451+
const buildStatistics = this.tryGetAndroidBuildStatistics(projectRoot);
452+
453+
try {
454+
if (buildStatistics && buildStatistics.kotlinUsage) {
455+
const analyticsDelimiter = constants.AnalyticsEventLabelDelimiter;
456+
const hasUseKotlinPropertyInAppData = `hasUseKotlinPropertyInApp${analyticsDelimiter}${buildStatistics.kotlinUsage.hasUseKotlinPropertyInApp}`;
457+
const hasKotlinRuntimeClassesData = `hasKotlinRuntimeClasses${analyticsDelimiter}${buildStatistics.kotlinUsage.hasKotlinRuntimeClasses}`;
458+
await this.$analyticsService.trackEventActionInGoogleAnalytics({
459+
action: constants.TrackActionNames.UsingKotlin,
460+
additionalData: `${hasUseKotlinPropertyInAppData}${analyticsDelimiter}${hasKotlinRuntimeClassesData}`
461+
});
462+
}
463+
} catch (e) {
464+
this.$logger.trace(`Failed to track android build statistics. Error is: ${e.message}`);
465+
}
466+
}
467+
468+
private tryGetAndroidBuildStatistics(projectRoot: string): Object {
469+
const staticsFilePath = path.join(projectRoot, constants.ANDROID_ANALYTICS_DATA_DIR, constants.ANDROID_ANALYTICS_DATA_FILE);
470+
let buildStatistics;
471+
472+
if (this.$fs.exists(staticsFilePath)) {
473+
try {
474+
buildStatistics = this.$fs.readJson(staticsFilePath);
475+
} catch (e) {
476+
this.$logger.trace(`Unable to read android build statistics file. Error is ${e.message}`);
477+
}
478+
}
479+
480+
return buildStatistics;
481+
}
447482
}
448483

449484
$injector.register("androidProjectService", AndroidProjectService);

lib/services/android/gradle-build-args-service.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ import { Configurations } from "../../common/constants";
33

44
export class GradleBuildArgsService implements IGradleBuildArgsService {
55
constructor(private $androidToolsInfo: IAndroidToolsInfo,
6+
private $analyticsService: IAnalyticsService,
7+
private $staticConfig: Config.IStaticConfig,
68
private $logger: ILogger) { }
79

8-
public getBuildTaskArgs(buildData: IAndroidBuildData): string[] {
10+
public async getBuildTaskArgs(buildData: IAndroidBuildData): Promise<string[]> {
911
const args = this.getBaseTaskArgs(buildData);
1012
args.unshift(this.getBuildTaskName(buildData));
1113

14+
if (await this.$analyticsService.isEnabled(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME)) {
15+
args.push("-PgatherAnalyticsData=true");
16+
}
17+
1218
return args;
1319
}
1420

lib/services/android/gradle-build-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class GradleBuildService extends EventEmitter implements IGradleBuildServ
1010
) { super(); }
1111

1212
public async buildProject(projectRoot: string, buildData: IAndroidBuildData): Promise<void> {
13-
const buildTaskArgs = this.$gradleBuildArgsService.getBuildTaskArgs(buildData);
13+
const buildTaskArgs = await this.$gradleBuildArgsService.getBuildTaskArgs(buildData);
1414
const spawnOptions = { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true };
1515
const gradleCommandOptions = { cwd: projectRoot, message: "Gradle build...", stdio: buildData.buildOutputStdio, spawnOptions };
1616

test/cocoapods-service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ function changeNewLineCharacter(input: string): string {
4040
}
4141

4242
describe("Cocoapods service", () => {
43+
if (require("os").platform() === "win32") {
44+
console.log("Skipping 'Cocoapods service' tests. They can work only on macOS and Linux");
45+
return;
46+
}
4347
const nativeProjectPath = "nativeProjectPath";
4448
const mockPluginData: any = {
4549
name: "plugin1",

test/services/android-project-service.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ const createTestInjector = (): IInjector => {
4141
testInjector.register("gradleCommandService", GradleCommandService);
4242
testInjector.register("gradleBuildService", GradleBuildService);
4343
testInjector.register("gradleBuildArgsService", GradleBuildArgsService);
44-
44+
testInjector.register("analyticsService", stubs.AnalyticsService);
45+
testInjector.register("staticConfig", {TRACK_FEATURE_USAGE_SETTING_NAME: "TrackFeatureUsage"});
4546
return testInjector;
4647
};
4748

test/services/android/gradle-build-args-service.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Yok } from "../../../lib/common/yok";
22
import { GradleBuildArgsService } from "../../../lib/services/android/gradle-build-args-service";
3+
import * as stubs from "../../stubs";
34
import { assert } from "chai";
5+
import * as temp from "temp";
6+
temp.track();
47

58
function createTestInjector(): IInjector {
69
const injector = new Yok();
@@ -14,43 +17,45 @@ function createTestInjector(): IInjector {
1417
});
1518
injector.register("logger", {});
1619
injector.register("gradleBuildArgsService", GradleBuildArgsService);
20+
injector.register("analyticsService", stubs.AnalyticsService);
21+
injector.register("staticConfig", {TRACK_FEATURE_USAGE_SETTING_NAME: "TrackFeatureUsage"});
1722

1823
return injector;
1924
}
2025

21-
function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => string[]) {
22-
_.each(testCases, testCase => {
23-
it(testCase.name, () => {
26+
async function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => Promise<string[]>) {
27+
for (const testCase of testCases) {
28+
it(testCase.name, async () => {
2429
const injector = createTestInjector();
2530
if (testCase.logLevel) {
2631
const logger = injector.resolve("logger");
2732
logger.getLevel = () => testCase.logLevel;
2833
}
2934

3035
const gradleBuildArgsService = injector.resolve("gradleBuildArgsService");
31-
const args = testFunction(gradleBuildArgsService, testCase.buildConfig);
36+
const args = await testFunction(gradleBuildArgsService, testCase.buildConfig);
3237

3338
assert.deepEqual(args, testCase.expectedResult);
3439
});
35-
});
40+
}
3641
}
37-
42+
const ksPath = temp.path({ prefix: "ksPath" });
3843
const expectedInfoLoggingArgs = ["--quiet"];
3944
const expectedTraceLoggingArgs = ["--stacktrace", "--debug"];
4045
const expectedDebugBuildArgs = ["-PcompileSdk=android-28", "-PtargetSdk=26", "-PbuildToolsVersion=my-build-tools-version", "-PgenerateTypings=true"];
41-
const expectedReleaseBuildArgs = expectedDebugBuildArgs.concat(["-Prelease", "-PksPath=/my/key/store/path",
46+
const expectedReleaseBuildArgs = expectedDebugBuildArgs.concat(["-Prelease", `-PksPath=${ksPath}`,
4247
"-Palias=keyStoreAlias", "-Ppassword=keyStoreAliasPassword", "-PksPassword=keyStorePassword"]);
4348

4449
const releaseBuildConfig = {
4550
release: true,
46-
keyStorePath: "/my/key/store/path",
51+
keyStorePath: ksPath,
4752
keyStoreAlias: "keyStoreAlias",
4853
keyStoreAliasPassword: "keyStoreAliasPassword",
4954
keyStorePassword: "keyStorePassword"
5055
};
5156

5257
describe("GradleBuildArgsService", () => {
53-
describe("getBuildTaskArgs", () => {
58+
describe("getBuildTaskArgs", async () => {
5459
const testCases = [
5560
{
5661
name: "should return correct args for debug build with info log",
@@ -102,10 +107,10 @@ describe("GradleBuildArgsService", () => {
102107
}
103108
];
104109

105-
executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => gradleBuildArgsService.getBuildTaskArgs(buildData));
110+
await executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => gradleBuildArgsService.getBuildTaskArgs(buildData));
106111
});
107112

108-
describe("getCleanTaskArgs", () => {
113+
describe("getCleanTaskArgs", async () => {
109114
const testCases = [
110115
{
111116
name: "should return correct args for debug clean build with info log",
@@ -157,6 +162,6 @@ describe("GradleBuildArgsService", () => {
157162
}
158163
];
159164

160-
executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => gradleBuildArgsService.getCleanTaskArgs(buildData));
165+
await executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => Promise.resolve(gradleBuildArgsService.getCleanTaskArgs(buildData)));
161166
});
162167
});

test/services/ios/export-options-plist-service.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Yok } from "../../../lib/common/yok";
22
import { ExportOptionsPlistService } from "../../../lib/services/ios/export-options-plist-service";
33
import { assert } from "chai";
4-
import { EOL } from "os";
54

65
let actualPlistTemplate: string = null;
76
const projectName = "myProjectName";
@@ -54,7 +53,7 @@ describe("ExportOptionsPlistService", () => {
5453
const projectData = { projectName, projectIdentifiers: { ios: "org.nativescript.myTestApp" }};
5554
exportOptionsPlistService.createDevelopmentExportOptionsPlist(archivePath, projectData, testCase.buildConfig);
5655

57-
const template = actualPlistTemplate.split(EOL).join(" ");
56+
const template = actualPlistTemplate.split("\n").join(" ");
5857
assert.isTrue(template.indexOf(`<key>method</key> <string>${provisionType}</string>`) > 0);
5958
assert.isTrue(template.indexOf("<key>uploadBitcode</key> <false/>") > 0);
6059
assert.isTrue(template.indexOf("<key>compileBitcode</key> <false/>") > 0);
@@ -97,7 +96,7 @@ describe("ExportOptionsPlistService", () => {
9796
const projectData = { projectName, projectIdentifiers: { ios: "org.nativescript.myTestApp" }};
9897
exportOptionsPlistService.createDistributionExportOptionsPlist(projectRoot, projectData, testCase.buildConfig);
9998

100-
const template = actualPlistTemplate.split(EOL).join(" ");
99+
const template = actualPlistTemplate.split("\n").join(" ");
101100
assert.isTrue(template.indexOf("<key>method</key> <string>app-store</string>") > 0);
102101
assert.isTrue(template.indexOf("<key>uploadBitcode</key> <false/>") > 0);
103102
assert.isTrue(template.indexOf("<key>compileBitcode</key> <false/>") > 0);

test/services/playground/preview-app-livesync-service.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ const deviceMockData = <Device>{
5959
platform: normalizedPlatformName
6060
};
6161
const defaultProjectFiles = [
62-
"my/test/file1.js",
63-
"my/test/file2.js",
64-
"my/test/file3.js",
65-
"my/test/nested/file1.js",
66-
"my/test/nested/file2.js",
67-
"my/test/nested/file3.js"
62+
path.join("my", "test", "file1.js"),
63+
path.join("my", "test", "file2.js"),
64+
path.join("my", "test", "file3.js"),
65+
path.join("my", "test", "nested", "file1.js"),
66+
path.join("my", "test", "nested", "file2.js"),
67+
path.join("my", "test", "nested", "file3.js")
6868
];
6969
const syncFilesMockData = {
7070
projectDir: projectDirPath,

test/stubs.ts

+16
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,22 @@ export class MarkingModeServiceStub implements IMarkingModeService {
859859
}
860860
}
861861

862+
export class AnalyticsService implements IAnalyticsService {
863+
async checkConsent(): Promise<void> { return ; }
864+
async trackFeature(featureName: string): Promise<void> { return ; }
865+
async trackException(exception: any, message: string): Promise<void> { return ; }
866+
async setStatus(settingName: string, enabled: boolean): Promise<void> { return ; }
867+
async getStatusMessage(settingName: string, jsonFormat: boolean, readableSettingName: string): Promise<string> { return "Fake message"; }
868+
async isEnabled(settingName: string): Promise<boolean> { return false; }
869+
async track(featureName: string, featureValue: string): Promise<void> { return ; }
870+
async trackEventActionInGoogleAnalytics(data: IEventActionData) { return Promise.resolve(); }
871+
async trackInGoogleAnalytics(data: IGoogleAnalyticsData) { return Promise.resolve(); }
872+
async trackAcceptFeatureUsage(settings: { acceptTrackFeatureUsage: boolean }) { return Promise.resolve(); }
873+
async trackPreviewAppData() { return Promise.resolve(); }
874+
async finishTracking() { return Promise.resolve(); }
875+
setShouldDispose() {}
876+
}
877+
862878
export class InjectorStub extends Yok implements IInjector {
863879
constructor() {
864880
super();

0 commit comments

Comments
 (0)