diff --git a/lib/common/commands/preuninstall.ts b/lib/common/commands/preuninstall.ts index 41ba5c3c4e..a808f41513 100644 --- a/lib/common/commands/preuninstall.ts +++ b/lib/common/commands/preuninstall.ts @@ -1,28 +1,44 @@ import * as path from "path"; -import { doesCurrentNpmCommandMatch } from "../helpers"; +import { doesCurrentNpmCommandMatch, isInteractive } from "../helpers"; +import { TrackActionNames, AnalyticsEventLabelDelimiter } from "../../constants"; export class PreUninstallCommand implements ICommand { - public disableAnalytics = true; + private static FEEDBACK_FORM_URL = "https://www.nativescript.org/uninstall-feedback"; public allowedParameters: ICommandParameter[] = []; - constructor(private $extensibilityService: IExtensibilityService, + constructor(private $analyticsService: IAnalyticsService, + private $extensibilityService: IExtensibilityService, private $fs: IFileSystem, + private $opener: IOpener, private $packageInstallationManager: IPackageInstallationManager, private $settingsService: ISettingsService) { } public async execute(args: string[]): Promise { const isIntentionalUninstall = doesCurrentNpmCommandMatch([/^uninstall$/, /^remove$/, /^rm$/, /^r$/, /^un$/, /^unlink$/]); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.UninstallCLI, + additionalData: `isIntentionalUninstall${AnalyticsEventLabelDelimiter}${isIntentionalUninstall}${AnalyticsEventLabelDelimiter}isInteractive${AnalyticsEventLabelDelimiter}${!!isInteractive()}` + }); + if (isIntentionalUninstall) { - this.handleIntentionalUninstall(); + await this.handleIntentionalUninstall(); } this.$fs.deleteFile(path.join(this.$settingsService.getProfileDir(), "KillSwitches", "cli")); } - private handleIntentionalUninstall(): void { + private async handleIntentionalUninstall(): Promise { this.$extensibilityService.removeAllExtensions(); this.$packageInstallationManager.clearInspectorCache(); + await this.handleFeedbackForm(); + } + + private async handleFeedbackForm(): Promise { + if (isInteractive()) { + this.$opener.open(PreUninstallCommand.FEEDBACK_FORM_URL); + } } } diff --git a/lib/common/test/unit-tests/preuninstall.ts b/lib/common/test/unit-tests/preuninstall.ts new file mode 100644 index 0000000000..2f2ae3a978 --- /dev/null +++ b/lib/common/test/unit-tests/preuninstall.ts @@ -0,0 +1,145 @@ +import { assert } from "chai"; +import { Yok } from "../../yok"; +import { PreUninstallCommand } from "../../commands/preuninstall"; +import * as path from "path"; +const helpers = require("../../helpers"); + +describe("preuninstall", () => { + const profileDir = "profileDir"; + const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + + testInjector.register("extensibilityService", { + removeAllExtensions: (): void => undefined + }); + + testInjector.register("fs", { + deleteFile: (pathToFile: string): void => undefined + }); + + testInjector.register("packageInstallationManager", { + clearInspectorCache: (): void => undefined + }); + + testInjector.register("settingsService", { + getProfileDir: (): string => profileDir + }); + + testInjector.register("opener", { + open: (filename: string, appname?: string): void => undefined + }); + + testInjector.register("analyticsService", { + trackEventActionInGoogleAnalytics: async (data: IEventActionData): Promise => undefined + }); + + testInjector.registerCommand("dev-preuninstall", PreUninstallCommand); + + return testInjector; + }; + + it("cleans the KillSwitches/cli file", async () => { + helpers.doesCurrentNpmCommandMatch = () => false; + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); + const deletedFiles: string[] = []; + fs.deleteFile = (pathToFile: string) => { + deletedFiles.push(pathToFile); + }; + + const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall"); + await preUninstallCommand.execute([]); + assert.deepEqual(deletedFiles, [path.join(profileDir, "KillSwitches", "cli")]); + }); + + it("tracks correct data in analytics", async () => { + const testData: { isInteractive: boolean, isIntentionalUninstall: boolean, expecteEventLabelData: string }[] = [ + { + isIntentionalUninstall: false, + isInteractive: false, + expecteEventLabelData: `isIntentionalUninstall__false__isInteractive__false` + }, + { + isIntentionalUninstall: true, + isInteractive: false, + expecteEventLabelData: `isIntentionalUninstall__true__isInteractive__false` + }, + { + isIntentionalUninstall: false, + isInteractive: true, + expecteEventLabelData: `isIntentionalUninstall__false__isInteractive__true` + }, + { + isIntentionalUninstall: true, + isInteractive: true, + expecteEventLabelData: `isIntentionalUninstall__true__isInteractive__true` + } + ]; + + const testInjector = createTestInjector(); + const analyticsService = testInjector.resolve("analyticsService"); + let trackedData: IEventActionData[] = []; + analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise => { + trackedData.push(data); + }; + + const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall"); + for (const testCase of testData) { + helpers.isInteractive = () => testCase.isInteractive; + helpers.doesCurrentNpmCommandMatch = () => testCase.isIntentionalUninstall; + await preUninstallCommand.execute([]); + assert.deepEqual(trackedData, [{ + action: "Uninstall CLI", + additionalData: testCase.expecteEventLabelData + }]); + trackedData = []; + } + }); + + it("removes all extensions and inspector cache when uninstall command is executed", async () => { + helpers.doesCurrentNpmCommandMatch = () => true; + helpers.isInteractive = () => false; + + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); + const deletedFiles: string[] = []; + fs.deleteFile = (pathToFile: string) => { + deletedFiles.push(pathToFile); + }; + + const extensibilityService = testInjector.resolve("extensibilityService"); + let isRemoveAllExtensionsCalled = false; + extensibilityService.removeAllExtensions = () => { + isRemoveAllExtensionsCalled = true; + }; + + const packageInstallationManager = testInjector.resolve("packageInstallationManager"); + let isClearInspectorCacheCalled = false; + packageInstallationManager.clearInspectorCache = () => { + isClearInspectorCacheCalled = true; + }; + + const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall"); + await preUninstallCommand.execute([]); + assert.deepEqual(deletedFiles, [path.join(profileDir, "KillSwitches", "cli")]); + + assert.isTrue(isRemoveAllExtensionsCalled, "When uninstall is called, `removeAllExtensions` method must be called"); + assert.isTrue(isClearInspectorCacheCalled, "When uninstall is called, `clearInspectorCache` method must be called"); + }); + + it("opens the uninstall feedback form when terminal is interactive and uninstall is called", async () => { + helpers.doesCurrentNpmCommandMatch = () => true; + helpers.isInteractive = () => true; + + const testInjector = createTestInjector(); + const opener = testInjector.resolve("opener"); + const openParams: any[] = []; + opener.open = (filename: string, appname?: string) => { + openParams.push({ filename, appname }); + }; + + const preUninstallCommand: ICommand = testInjector.resolveCommand("dev-preuninstall"); + await preUninstallCommand.execute([]); + assert.deepEqual(openParams, [{ filename: "https://www.nativescript.org/uninstall-feedback", appname: undefined }]); + }); +}); diff --git a/lib/constants.ts b/lib/constants.ts index 93c3516fca..fd8f561e7f 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -185,7 +185,8 @@ export const enum TrackActionNames { Options = "Options", AcceptTracking = "Accept Tracking", Performance = "Performance", - PreviewAppData = "Preview App Data" + PreviewAppData = "Preview App Data", + UninstallCLI = "Uninstall CLI" } export const AnalyticsEventLabelDelimiter = "__";