Skip to content

Commit 7adc487

Browse files
Merge pull request #3600 from NativeScript/vladimirov/merge-rel-master
chore: Merge release in master
2 parents f7a4594 + c794621 commit 7adc487

6 files changed

+133
-21
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
NativeScript CLI Changelog
22
================
33

4+
4.0.2 (2018, May 18)
5+
==
6+
7+
### Fixed
8+
* [Fixed #3595](https://github.com/NativeScript/nativescript-cli/issues/3595): Do not track local paths in Analytics
9+
* [Fixed #3597](https://github.com/NativeScript/nativescript-cli/issues/3597): Users who subscribe to Progess Newsletter are not informed for the privacy policy
10+
411
4.0.1 (2018, May 11)
512
==
613

lib/constants.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require("colors");
2+
13
export const APP_FOLDER_NAME = "app";
24
export const APP_RESOURCES_FOLDER_NAME = "App_Resources";
35
export const PROJECT_FRAMEWORK_FOLDER_NAME = "framework";
@@ -78,6 +80,8 @@ export const RESERVED_TEMPLATE_NAMES: IStringDictionary = {
7880
"angular": "tns-template-hello-world-ng"
7981
};
8082

83+
export const ANALYTICS_LOCAL_TEMPLATE_PREFIX = "localTemplate_";
84+
8185
export class ITMSConstants {
8286
static ApplicationMetadataFile = "metadata.xml";
8387
static VerboseLoggingLevels = {
@@ -178,3 +182,10 @@ export class MacOSVersions {
178182
}
179183

180184
export const MacOSDeprecationStringFormat = "Support for macOS %s is deprecated and will be removed in one of the next releases of NativeScript. Please, upgrade to the latest macOS version.";
185+
export const PROGRESS_PRIVACY_POLICY_URL = "https://www.progress.com/legal/privacy-policy";
186+
export class SubscribeForNewsletterMessages {
187+
public static AgreeToReceiveEmailMsg = "I agree to receive email communications from Progress Software or its Partners (`https://www.progress.com/partners/partner-directory`)," +
188+
"containing information about Progress Software's products. Consent may be withdrawn at any time.";
189+
public static ReviewPrivacyPolicyMsg = `You can review the Progress Software Privacy Policy at \`${PROGRESS_PRIVACY_POLICY_URL}\``;
190+
public static PromptMsg = "Input your e-mail address to agree".green + " or " + "leave empty to decline".red.bold + ":";
191+
}

lib/services/project-templates-service.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
1818

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

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

23-
await this.$analyticsService.trackEventActionInGoogleAnalytics({
24-
action: constants.TrackActionNames.CreateProject,
25-
isForDevice: null,
26-
additionalData: templateName
27-
});
23+
await this.$analyticsService.track("Template used for project creation", templateName);
2824

29-
const realTemplatePath = await this.prepareNativeScriptTemplate(templateName, version, projectDir);
25+
const templateNameToBeTracked = this.getTemplateNameToBeTracked(templateName, realTemplatePath);
26+
if (templateNameToBeTracked) {
27+
await this.$analyticsService.trackEventActionInGoogleAnalytics({
28+
action: constants.TrackActionNames.CreateProject,
29+
isForDevice: null,
30+
additionalData: templateNameToBeTracked
31+
});
32+
}
3033

3134
// this removes dependencies from templates so they are not copied to app folder
3235
this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME));
@@ -46,5 +49,25 @@ export class ProjectTemplatesService implements IProjectTemplatesService {
4649
this.$logger.trace(`Using NativeScript verified template: ${templateName} with version ${version}.`);
4750
return this.$npmInstallationManager.install(templateName, projectDir, { version: version, dependencyType: "save" });
4851
}
52+
53+
private getTemplateNameToBeTracked(templateName: string, realTemplatePath: string): string {
54+
try {
55+
if (this.$fs.exists(templateName)) {
56+
// local template is used
57+
const pathToPackageJson = path.join(realTemplatePath, constants.PACKAGE_JSON_FILE_NAME);
58+
let templateNameToTrack = path.basename(templateName);
59+
if (this.$fs.exists(pathToPackageJson)) {
60+
const templatePackageJsonContent = this.$fs.readJson(pathToPackageJson);
61+
templateNameToTrack = templatePackageJsonContent.name;
62+
}
63+
64+
return `${constants.ANALYTICS_LOCAL_TEMPLATE_PREFIX}${templateNameToTrack}`;
65+
}
66+
67+
return templateName;
68+
} catch (err) {
69+
this.$logger.trace(`Unable to get template name to be tracked, error is: ${err}`);
70+
}
71+
}
4972
}
5073
$injector.register("projectTemplatesService", ProjectTemplatesService);

lib/services/subscription-service.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as emailValidator from "email-validator";
22
import * as queryString from "querystring";
33
import * as helpers from "../common/helpers";
4+
import { SubscribeForNewsletterMessages } from "../constants";
45

56
export class SubscriptionService implements ISubscriptionService {
67
constructor(private $httpClient: Server.IHttpClient,
@@ -11,8 +12,10 @@ export class SubscriptionService implements ISubscriptionService {
1112

1213
public async subscribeForNewsletter(): Promise<void> {
1314
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)");
15+
this.$logger.printMarkdown(SubscribeForNewsletterMessages.AgreeToReceiveEmailMsg);
16+
this.$logger.printMarkdown(SubscribeForNewsletterMessages.ReviewPrivacyPolicyMsg);
17+
18+
const email = await this.getEmail(SubscribeForNewsletterMessages.PromptMsg);
1619
await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true);
1720
await this.sendEmail(email);
1821
}

test/project-templates-service.ts

+73-10
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,99 @@ 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+
155+
it("does not send anything when trying to get template name fails", async () => {
156+
const templateName = "localtemplate";
157+
const localTemplatePath = `/Users/username/${templateName}`;
158+
const fs = testInjector.resolve<IFileSystem>("fs");
159+
fs.exists = (localPath: string): boolean => true;
160+
fs.readJson = (filename: string, encoding?: string): any => {
161+
throw new Error("Unable to read json");
162+
};
163+
164+
await projectTemplatesService.prepareTemplate(localTemplatePath, "tempFolder");
165+
166+
assert.deepEqual(dataSentToGoogleAnalytics, null);
167+
});
168+
});
106169
});
107170
});

test/services/subscription-service.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { assert } from "chai";
33
import { SubscriptionService } from "../../lib/services/subscription-service";
44
import { LoggerStub } from "../stubs";
55
import { stringify } from "querystring";
6+
import { SubscribeForNewsletterMessages } from "../../lib/constants";
67
const helpers = require("../../lib/common/helpers");
78

89
interface IValidateTestData {
@@ -153,12 +154,16 @@ describe("subscriptionService", () => {
153154
loggerOutput += args.join(" ");
154155
};
155156

157+
logger.printMarkdown = (message: string): void => {
158+
loggerOutput += message;
159+
};
160+
156161
await subscriptionService.subscribeForNewsletter();
157162

158-
assert.equal(loggerOutput, "Enter your e-mail address to subscribe to the NativeScript Newsletter and hear about product updates, tips & tricks, and community happenings:");
163+
assert.equal(loggerOutput, `${SubscribeForNewsletterMessages.AgreeToReceiveEmailMsg}${SubscribeForNewsletterMessages.ReviewPrivacyPolicyMsg}`);
159164
});
160165

161-
const expectedMessageInPrompter = "(press Enter for blank)";
166+
const expectedMessageInPrompter = SubscribeForNewsletterMessages.PromptMsg;
162167
it(`calls prompter with specific message - ${expectedMessageInPrompter}`, async () => {
163168
const testInjector = createTestInjector();
164169
const subscriptionService = testInjector.resolve<SubscriptionServiceTester>(SubscriptionServiceTester);

0 commit comments

Comments
 (0)