Skip to content

Commit 7faf9b9

Browse files
Merge pull request #4973 from NativeScript/vladimirov/uninstall-form
feat: open feedback form when CLI is uninstalled
2 parents 2fc81c5 + c7efa01 commit 7faf9b9

File tree

3 files changed

+168
-6
lines changed

3 files changed

+168
-6
lines changed

lib/common/commands/preuninstall.ts

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
import * as path from "path";
2-
import { doesCurrentNpmCommandMatch } from "../helpers";
2+
import { doesCurrentNpmCommandMatch, isInteractive } from "../helpers";
3+
import { TrackActionNames, AnalyticsEventLabelDelimiter } from "../../constants";
34

45
export class PreUninstallCommand implements ICommand {
5-
public disableAnalytics = true;
6+
private static FEEDBACK_FORM_URL = "https://www.nativescript.org/uninstall-feedback";
67

78
public allowedParameters: ICommandParameter[] = [];
89

9-
constructor(private $extensibilityService: IExtensibilityService,
10+
constructor(private $analyticsService: IAnalyticsService,
11+
private $extensibilityService: IExtensibilityService,
1012
private $fs: IFileSystem,
13+
private $opener: IOpener,
1114
private $packageInstallationManager: IPackageInstallationManager,
1215
private $settingsService: ISettingsService) { }
1316

1417
public async execute(args: string[]): Promise<void> {
1518
const isIntentionalUninstall = doesCurrentNpmCommandMatch([/^uninstall$/, /^remove$/, /^rm$/, /^r$/, /^un$/, /^unlink$/]);
19+
20+
await this.$analyticsService.trackEventActionInGoogleAnalytics({
21+
action: TrackActionNames.UninstallCLI,
22+
additionalData: `isIntentionalUninstall${AnalyticsEventLabelDelimiter}${isIntentionalUninstall}${AnalyticsEventLabelDelimiter}isInteractive${AnalyticsEventLabelDelimiter}${!!isInteractive()}`
23+
});
24+
1625
if (isIntentionalUninstall) {
17-
this.handleIntentionalUninstall();
26+
await this.handleIntentionalUninstall();
1827
}
1928

2029
this.$fs.deleteFile(path.join(this.$settingsService.getProfileDir(), "KillSwitches", "cli"));
2130
}
2231

23-
private handleIntentionalUninstall(): void {
32+
private async handleIntentionalUninstall(): Promise<void> {
2433
this.$extensibilityService.removeAllExtensions();
2534
this.$packageInstallationManager.clearInspectorCache();
35+
await this.handleFeedbackForm();
36+
}
37+
38+
private async handleFeedbackForm(): Promise<void> {
39+
if (isInteractive()) {
40+
this.$opener.open(PreUninstallCommand.FEEDBACK_FORM_URL);
41+
}
2642
}
2743
}
2844

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { assert } from "chai";
2+
import { Yok } from "../../yok";
3+
import { PreUninstallCommand } from "../../commands/preuninstall";
4+
import * as path from "path";
5+
const helpers = require("../../helpers");
6+
7+
describe("preuninstall", () => {
8+
const profileDir = "profileDir";
9+
const createTestInjector = (): IInjector => {
10+
const testInjector = new Yok();
11+
12+
testInjector.register("extensibilityService", {
13+
removeAllExtensions: (): void => undefined
14+
});
15+
16+
testInjector.register("fs", {
17+
deleteFile: (pathToFile: string): void => undefined
18+
});
19+
20+
testInjector.register("packageInstallationManager", {
21+
clearInspectorCache: (): void => undefined
22+
});
23+
24+
testInjector.register("settingsService", {
25+
getProfileDir: (): string => profileDir
26+
});
27+
28+
testInjector.register("opener", {
29+
open: (filename: string, appname?: string): void => undefined
30+
});
31+
32+
testInjector.register("analyticsService", {
33+
trackEventActionInGoogleAnalytics: async (data: IEventActionData): Promise<void> => undefined
34+
});
35+
36+
testInjector.registerCommand("dev-preuninstall", PreUninstallCommand);
37+
38+
return testInjector;
39+
};
40+
41+
it("cleans the KillSwitches/cli file", async () => {
42+
helpers.doesCurrentNpmCommandMatch = () => false;
43+
const testInjector = createTestInjector();
44+
const fs = testInjector.resolve<IFileSystem>("fs");
45+
const deletedFiles: string[] = [];
46+
fs.deleteFile = (pathToFile: string) => {
47+
deletedFiles.push(pathToFile);
48+
};
49+
50+
const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall");
51+
await preUninstallCommand.execute([]);
52+
assert.deepEqual(deletedFiles, [path.join(profileDir, "KillSwitches", "cli")]);
53+
});
54+
55+
it("tracks correct data in analytics", async () => {
56+
const testData: { isInteractive: boolean, isIntentionalUninstall: boolean, expecteEventLabelData: string }[] = [
57+
{
58+
isIntentionalUninstall: false,
59+
isInteractive: false,
60+
expecteEventLabelData: `isIntentionalUninstall__false__isInteractive__false`
61+
},
62+
{
63+
isIntentionalUninstall: true,
64+
isInteractive: false,
65+
expecteEventLabelData: `isIntentionalUninstall__true__isInteractive__false`
66+
},
67+
{
68+
isIntentionalUninstall: false,
69+
isInteractive: true,
70+
expecteEventLabelData: `isIntentionalUninstall__false__isInteractive__true`
71+
},
72+
{
73+
isIntentionalUninstall: true,
74+
isInteractive: true,
75+
expecteEventLabelData: `isIntentionalUninstall__true__isInteractive__true`
76+
}
77+
];
78+
79+
const testInjector = createTestInjector();
80+
const analyticsService = testInjector.resolve<IAnalyticsService>("analyticsService");
81+
let trackedData: IEventActionData[] = [];
82+
analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise<void> => {
83+
trackedData.push(data);
84+
};
85+
86+
const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall");
87+
for (const testCase of testData) {
88+
helpers.isInteractive = () => testCase.isInteractive;
89+
helpers.doesCurrentNpmCommandMatch = () => testCase.isIntentionalUninstall;
90+
await preUninstallCommand.execute([]);
91+
assert.deepEqual(trackedData, [{
92+
action: "Uninstall CLI",
93+
additionalData: testCase.expecteEventLabelData
94+
}]);
95+
trackedData = [];
96+
}
97+
});
98+
99+
it("removes all extensions and inspector cache when uninstall command is executed", async () => {
100+
helpers.doesCurrentNpmCommandMatch = () => true;
101+
helpers.isInteractive = () => false;
102+
103+
const testInjector = createTestInjector();
104+
const fs = testInjector.resolve<IFileSystem>("fs");
105+
const deletedFiles: string[] = [];
106+
fs.deleteFile = (pathToFile: string) => {
107+
deletedFiles.push(pathToFile);
108+
};
109+
110+
const extensibilityService = testInjector.resolve<IExtensibilityService>("extensibilityService");
111+
let isRemoveAllExtensionsCalled = false;
112+
extensibilityService.removeAllExtensions = () => {
113+
isRemoveAllExtensionsCalled = true;
114+
};
115+
116+
const packageInstallationManager = testInjector.resolve<IPackageInstallationManager>("packageInstallationManager");
117+
let isClearInspectorCacheCalled = false;
118+
packageInstallationManager.clearInspectorCache = () => {
119+
isClearInspectorCacheCalled = true;
120+
};
121+
122+
const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall");
123+
await preUninstallCommand.execute([]);
124+
assert.deepEqual(deletedFiles, [path.join(profileDir, "KillSwitches", "cli")]);
125+
126+
assert.isTrue(isRemoveAllExtensionsCalled, "When uninstall is called, `removeAllExtensions` method must be called");
127+
assert.isTrue(isClearInspectorCacheCalled, "When uninstall is called, `clearInspectorCache` method must be called");
128+
});
129+
130+
it("opens the uninstall feedback form when terminal is interactive and uninstall is called", async () => {
131+
helpers.doesCurrentNpmCommandMatch = () => true;
132+
helpers.isInteractive = () => true;
133+
134+
const testInjector = createTestInjector();
135+
const opener = testInjector.resolve<IOpener>("opener");
136+
const openParams: any[] = [];
137+
opener.open = (filename: string, appname?: string) => {
138+
openParams.push({ filename, appname });
139+
};
140+
141+
const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall");
142+
await preUninstallCommand.execute([]);
143+
assert.deepEqual(openParams, [{ filename: "https://www.nativescript.org/uninstall-feedback", appname: undefined }]);
144+
});
145+
});

lib/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ export const enum TrackActionNames {
185185
Options = "Options",
186186
AcceptTracking = "Accept Tracking",
187187
Performance = "Performance",
188-
PreviewAppData = "Preview App Data"
188+
PreviewAppData = "Preview App Data",
189+
UninstallCLI = "Uninstall CLI"
189190
}
190191

191192
export const AnalyticsEventLabelDelimiter = "__";

0 commit comments

Comments
 (0)