Skip to content

Commit 23136f1

Browse files
fix(GDPR): Do not track local project paths
Do not track paths to projects when local template is used. Instead track `localTemplate_<name from template's package.json or dirname>`. Also fix the prompter for emails as we need to show information how to unsubscribe from the newsletter. Add tests for the behavior.
1 parent 7ce030c commit 23136f1

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export const RESERVED_TEMPLATE_NAMES: IStringDictionary = {
7878
"angular": "tns-template-hello-world-ng"
7979
};
8080

81+
export const ANALYTICS_LOCAL_TEMPLATE_PREFIX = "localTemplate_";
82+
8183
export class ITMSConstants {
8284
static ApplicationMetadataFile = "metadata.xml";
8385
static VerboseLoggingLevels = {

lib/services/project-templates-service.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
1818

1919
const templateName = constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()] || name;
2020

21+
const realTemplatePath = await this.prepareNativeScriptTemplate(templateName, version, projectDir);
22+
2123
await this.$analyticsService.track("Template used for project creation", templateName);
2224

2325
await this.$analyticsService.trackEventActionInGoogleAnalytics({
2426
action: constants.TrackActionNames.CreateProject,
2527
isForDevice: null,
26-
additionalData: templateName
28+
additionalData: this.getTemplateNameToBeTracked(templateName, realTemplatePath)
2729
});
2830

29-
const realTemplatePath = await this.prepareNativeScriptTemplate(templateName, version, projectDir);
30-
3131
// this removes dependencies from templates so they are not copied to app folder
3232
this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME));
3333

@@ -46,5 +46,21 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
4646
this.$logger.trace(`Using NativeScript verified template: ${templateName} with version ${version}.`);
4747
return this.$npmInstallationManager.install(templateName, projectDir, { version: version, dependencyType: "save" });
4848
}
49+
50+
private getTemplateNameToBeTracked(templateName: string, realTemplatePath: string): string {
51+
if (this.$fs.exists(templateName)) {
52+
// local template is used
53+
const pathToPackageJson = path.join(realTemplatePath, constants.PACKAGE_JSON_FILE_NAME);
54+
let templateNameToTrack = path.basename(templateName);
55+
if (this.$fs.exists(pathToPackageJson)) {
56+
const templatePackageJsonContent = this.$fs.readJson(pathToPackageJson);
57+
templateNameToTrack = templatePackageJsonContent.name;
58+
}
59+
60+
return `${constants.ANALYTICS_LOCAL_TEMPLATE_PREFIX}${templateNameToTrack}`;
61+
}
62+
63+
return templateName;
64+
}
4965
}
5066
$injector.register("projectTemplatesService", ProjectTemplatesService);

lib/services/subscription-service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export class SubscriptionService implements ISubscriptionService {
1111

1212
public async subscribeForNewsletter(): Promise<void> {
1313
if (await this.shouldAskForEmail()) {
14-
this.$logger.out("Enter your e-mail address to subscribe to the NativeScript Newsletter and hear about product updates, tips & tricks, and community happenings:");
15-
const email = await this.getEmail("(press Enter for blank)");
14+
this.$logger.printMarkdown("I agree to receive email communications from Progress Software or its Partners (`https://www.progress.com/partners/partner-directory`), containing information about Progress Software's products. Consent may be withdrawn at any time. ");
15+
this.$logger.printMarkdown("You can review the Progress Software Privacy Policy at `https://www.progress.com/legal/privacy-policy`");
16+
17+
const email = await this.getEmail("Input your e-mail address to agree".green + " or " + "leave empty to decline".red.bold + ":");
1618
await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true);
1719
await this.sendEmail(email);
1820
}

test/project-templates-service.ts

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as stubs from "./stubs";
33
import { ProjectTemplatesService } from "../lib/services/project-templates-service";
44
import { assert } from "chai";
55
import * as path from "path";
6-
import temp = require("temp");
76
import * as constants from "../lib/constants";
87

98
let isDeleteDirectoryCalledForNodeModulesDir = false;
@@ -25,9 +24,12 @@ function createTestInjector(configuration?: { shouldNpmInstallThrow: boolean, np
2524
if (directory.indexOf("node_modules") !== -1) {
2625
isDeleteDirectoryCalledForNodeModulesDir = true;
2726
}
28-
}
27+
},
28+
29+
exists: (filePath: string): boolean => false
2930

3031
});
32+
3133
injector.register("npm", {
3234
install: (packageName: string, pathToSave: string, config?: any) => {
3335
if (configuration.shouldNpmInstallThrow) {
@@ -70,38 +72,85 @@ describe("project-templates-service", () => {
7072
it("when npm install fails", async () => {
7173
testInjector = createTestInjector({ shouldNpmInstallThrow: true, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null });
7274
projectTemplatesService = testInjector.resolve("projectTemplatesService");
73-
const tempFolder = temp.mkdirSync("preparetemplate");
74-
await assert.isRejected(projectTemplatesService.prepareTemplate("invalidName", tempFolder));
75+
await assert.isRejected(projectTemplatesService.prepareTemplate("invalidName", "tempFolder"));
7576
});
7677
});
7778

7879
describe("returns correct path to template", () => {
7980
it("when reserved template name is used", async () => {
8081
testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] });
8182
projectTemplatesService = testInjector.resolve("projectTemplatesService");
82-
const tempFolder = temp.mkdirSync("preparetemplate");
83-
const actualPathToTemplate = await projectTemplatesService.prepareTemplate("typescript", tempFolder);
83+
const actualPathToTemplate = await projectTemplatesService.prepareTemplate("typescript", "tempFolder");
8484
assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
8585
assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
8686
});
8787

8888
it("when reserved template name is used (case-insensitive test)", async () => {
8989
testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] });
9090
projectTemplatesService = testInjector.resolve("projectTemplatesService");
91-
const tempFolder = temp.mkdirSync("preparetemplate");
92-
const actualPathToTemplate = await projectTemplatesService.prepareTemplate("tYpEsCriPT", tempFolder);
91+
const actualPathToTemplate = await projectTemplatesService.prepareTemplate("tYpEsCriPT", "tempFolder");
9392
assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
9493
assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
9594
});
9695

9796
it("uses defaultTemplate when undefined is passed as parameter", async () => {
9897
testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] });
9998
projectTemplatesService = testInjector.resolve("projectTemplatesService");
100-
const tempFolder = temp.mkdirSync("preparetemplate");
101-
const actualPathToTemplate = await projectTemplatesService.prepareTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], tempFolder);
99+
const actualPathToTemplate = await projectTemplatesService.prepareTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], "tempFolder");
102100
assert.strictEqual(path.basename(actualPathToTemplate), nativeScriptValidatedTemplatePath);
103101
assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted.");
104102
});
105103
});
104+
105+
describe("sends correct information to Google Analytics", () => {
106+
let analyticsService: IAnalyticsService;
107+
let dataSentToGoogleAnalytics: IEventActionData;
108+
beforeEach(() => {
109+
testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] });
110+
analyticsService = testInjector.resolve<IAnalyticsService>("analyticsService");
111+
dataSentToGoogleAnalytics = null;
112+
analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise<void> => {
113+
dataSentToGoogleAnalytics = data;
114+
};
115+
projectTemplatesService = testInjector.resolve("projectTemplatesService");
116+
});
117+
118+
it("sends template name when the template is used from npm", async () => {
119+
const templateName = "template-from-npm";
120+
await projectTemplatesService.prepareTemplate(templateName, "tempFolder");
121+
assert.deepEqual(dataSentToGoogleAnalytics, {
122+
action: constants.TrackActionNames.CreateProject,
123+
isForDevice: null,
124+
additionalData: templateName
125+
});
126+
});
127+
128+
it("sends template name (from template's package.json) when the template is used from local path", async () => {
129+
const templateName = "my-own-local-template";
130+
const localTemplatePath = "/Users/username/localtemplate";
131+
const fs = testInjector.resolve<IFileSystem>("fs");
132+
fs.exists = (path: string): boolean => true;
133+
fs.readJson = (filename: string, encoding?: string): any => ({ name: templateName });
134+
await projectTemplatesService.prepareTemplate(localTemplatePath, "tempFolder");
135+
assert.deepEqual(dataSentToGoogleAnalytics, {
136+
action: constants.TrackActionNames.CreateProject,
137+
isForDevice: null,
138+
additionalData: `${constants.ANALYTICS_LOCAL_TEMPLATE_PREFIX}${templateName}`
139+
});
140+
});
141+
142+
it("sends the template name (path to dirname) when the template is used from local path but there's no package.json at the root", async () => {
143+
const templateName = "localtemplate";
144+
const localTemplatePath = `/Users/username/${templateName}`;
145+
const fs = testInjector.resolve<IFileSystem>("fs");
146+
fs.exists = (localPath: string): boolean => path.basename(localPath) !== constants.PACKAGE_JSON_FILE_NAME;
147+
await projectTemplatesService.prepareTemplate(localTemplatePath, "tempFolder");
148+
assert.deepEqual(dataSentToGoogleAnalytics, {
149+
action: constants.TrackActionNames.CreateProject,
150+
isForDevice: null,
151+
additionalData: `${constants.ANALYTICS_LOCAL_TEMPLATE_PREFIX}${templateName}`
152+
});
153+
});
154+
});
106155
});
107156
});

0 commit comments

Comments
 (0)