Skip to content

Commit 189f3f0

Browse files
committed
feat: add tracking for command options
1 parent cdd6b0b commit 189f3f0

File tree

5 files changed

+138
-2
lines changed

5 files changed

+138
-2
lines changed

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ $injector.require("bundleValidatorHelper", "./helpers/bundle-validator-helper");
128128
$injector.require("androidBundleValidatorHelper", "./helpers/android-bundle-validator-helper");
129129
$injector.require("liveSyncCommandHelper", "./helpers/livesync-command-helper");
130130
$injector.require("deployCommandHelper", "./helpers/deploy-command-helper");
131+
$injector.require("optionsTrackHelper", "./helpers/options-track-helper");
131132

132133
$injector.requirePublicClass("localBuildService", "./services/local-build-service");
133134
$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service");

lib/common/services/commands-service.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export class CommandsService implements ICommandsService {
3030
private $resources: IResourceLoader,
3131
private $staticConfig: Config.IStaticConfig,
3232
private $helpService: IHelpService,
33-
private $extensibilityService: IExtensibilityService) {
33+
private $extensibilityService: IExtensibilityService,
34+
private $optionsTrackHelper: IOptionsTrackHelper) {
3435
}
3536

3637
public allCommands(opts: { includeDevCommands: boolean }): string[] {
@@ -63,6 +64,7 @@ export class CommandsService implements ICommandsService {
6364
}
6465

6566
await analyticsService.trackInGoogleAnalytics(googleAnalyticsPageData);
67+
await this.$optionsTrackHelper.trackOptions(this.$options);
6668
}
6769

6870
const shouldExecuteHooks = !this.$staticConfig.disableCommandHooks && (command.enableHooks === undefined || command.enableHooks === true);

lib/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ export const enum TrackActionNames {
149149
LiveSync = "LiveSync",
150150
RunSetupScript = "Run Setup Script",
151151
CheckLocalBuildSetup = "Check Local Build Setup",
152-
CheckEnvironmentRequirements = "Check Environment Requirements"
152+
CheckEnvironmentRequirements = "Check Environment Requirements",
153+
Options = "Options"
153154
}
154155

155156
export const AnalyticsEventLabelDelimiter = "__";

lib/declarations.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,10 @@ interface IAndroidBundleValidatorHelper {
910910
validateRuntimeVersion(projectData: IProjectData): void
911911
}
912912

913+
interface IOptionsTrackHelper {
914+
trackOptions(options: IOptions): Promise<void>
915+
}
916+
913917
interface INativeScriptCloudExtensionService {
914918
/**
915919
* Installs nativescript-cloud extension

lib/helpers/options-track-helper.ts

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as path from "path";
2+
import { TrackActionNames } from "../constants";
3+
4+
export class OptionsTrackHelper {
5+
public static SINGLE_OBJECT_SIZE_LIMIT = 400;
6+
public static SINGLE_PROPERTY_SIZE_LIMIT = 300;
7+
public static PASSWORD_DETECTION_STRING = "password";
8+
public static PASSOWRD_REPLACE_VALUE = "privateData";
9+
public static PATH_REPLACE_VALUE = "_localpath";
10+
public static SIZE_EXEEDED_REPLACE_VALUE = "popertySizeExceeded";
11+
12+
13+
constructor(
14+
private $analyticsService: IAnalyticsService) {
15+
}
16+
17+
public async trackOptions(options: IOptions) {
18+
const trackObjects = this.getTrackObjects(options);
19+
const promises: Promise<void>[] = [];
20+
_.forEach(trackObjects, trackObject => {
21+
const json = JSON.stringify(trackObject);
22+
23+
const trackPromise = this.$analyticsService.trackEventActionInGoogleAnalytics({
24+
action: TrackActionNames.Options,
25+
additionalData: json
26+
});
27+
28+
promises.push(trackPromise);
29+
});
30+
31+
await Promise.all(promises);
32+
}
33+
34+
private getTrackObjects(options: IOptions): Object[] {
35+
const optionsArgvCopy = _.cloneDeep(options.argv);
36+
const data = this.sanitizeTrackObject(optionsArgvCopy, options);
37+
38+
return this.splitTrackObjects(data);
39+
}
40+
41+
private sanitizeTrackObject(data: IDictionary<any>, options?: IOptions): IDictionary<any> {
42+
const shorthands = options ? options.shorthands: [];
43+
const optionsDefinitions = options ? options.options: {};
44+
45+
_.forEach(data, (value, key) => {
46+
if(this.shouldSkipProperty(key, value, shorthands, optionsDefinitions)) {
47+
delete data[key];
48+
} else {
49+
if(key.toLowerCase().indexOf(OptionsTrackHelper.PASSWORD_DETECTION_STRING) >= 0) {
50+
value = OptionsTrackHelper.PASSOWRD_REPLACE_VALUE;
51+
} else if(typeof value === "string" && value !== path.basename(value)) {
52+
value = OptionsTrackHelper.PATH_REPLACE_VALUE;
53+
} else if(_.isObject(value) && !_.isArray(value)) {
54+
value = this.sanitizeTrackObject(value);
55+
}
56+
data[key] = value;
57+
}
58+
});
59+
60+
return data;
61+
}
62+
63+
private shouldSkipProperty(key: string, value: any, shorthands: string[] = [], options: IDictionary<IDashedOption> = {}): Boolean {
64+
if(shorthands.indexOf(key) >= 0){
65+
return true;
66+
}
67+
68+
if(key.indexOf("-") >= 0) {
69+
return true;
70+
}
71+
72+
if(key === "_") {
73+
return true;
74+
}
75+
76+
const optionDef = options[key];
77+
if(optionDef && optionDef.type === OptionType.Boolean) {
78+
if(optionDef.default !== true && value === false || optionDef.default === true && value === true) {
79+
return true;
80+
}
81+
}
82+
83+
if(_.isUndefined(value)) {
84+
return true;
85+
}
86+
}
87+
88+
private splitTrackObjects(trackData: Object): Object[] {
89+
const json = JSON.stringify(trackData);
90+
const firstObject: IDictionary<any> = {};
91+
const secondObject: IDictionary<any> = {};
92+
const bigFields: Object[] = [];
93+
94+
if(json.length > OptionsTrackHelper.SINGLE_OBJECT_SIZE_LIMIT){
95+
const keys = _.keys(trackData);
96+
97+
if(keys.length === 1) {
98+
return [trackData];
99+
}
100+
101+
for (let i = 0; i < keys.length; i++) {
102+
const key = keys[i];
103+
let value = trackData[key];
104+
const valueLength = JSON.stringify(value).length;
105+
106+
if(valueLength > OptionsTrackHelper.SINGLE_OBJECT_SIZE_LIMIT) {
107+
value = "SIZE_EXEEDED_REPLACE_VALUE";
108+
}
109+
110+
if(valueLength > OptionsTrackHelper.SINGLE_PROPERTY_SIZE_LIMIT) {
111+
bigFields.push({
112+
[key]: value
113+
});
114+
} else if(i < keys.length/2) {
115+
firstObject[key] = value;
116+
} else {
117+
secondObject[key] = value;
118+
}
119+
}
120+
121+
return bigFields.concat(this.splitTrackObjects(firstObject), this.splitTrackObjects(secondObject));
122+
} else {
123+
return [trackData];
124+
}
125+
}
126+
}
127+
128+
$injector.register("optionsTrackHelper", OptionsTrackHelper);

0 commit comments

Comments
 (0)