diff --git a/backend/package.json b/backend/package.json index a644b7042..bbb8d73d7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "yeoman-ui", - "version": "1.1.3", + "version": "1.1.4", "displayName": "Application Wizard", "publisher": "SAPOS", "author": { @@ -75,7 +75,7 @@ "type": "object", "title": "Application Wizard", "properties": { - "loggingLevel": { + "ApplicationWizard.loggingLevel": { "type": "string", "enum": [ "off", @@ -90,22 +90,22 @@ "description": "The verbosity of logging according to the following order: trace > debug > info > warn > error > fatal > off.", "scope": "resource" }, - "sourceLocationTracking": { + "ApplicationWizard.sourceLocationTracking": { "type": "boolean", "default": false, "description": "If chosen, the location of the source code is added to log entries. Warning – this action may slow your extension. We recommend you use it only for debugging.", "scope": "resource" }, - "installationLocation": { + "ApplicationWizard.installationLocation": { "type": "string", "markdownDescription": "Install generators in a specified location. If not set, global location is used." }, - "autoUpdate": { + "ApplicationWizard.autoUpdate": { "type": "boolean", "default": true, - "description": "Auto update installed generators." + "description": "Automatically update installed generators." }, - "searchQuery": { + "ApplicationWizard.searchQuery": { "type": "array", "default": [ "SAP", diff --git a/backend/src/exploreGensMessages.ts b/backend/src/exploreGensMessages.ts index 0f6a08991..aba8fadec 100644 --- a/backend/src/exploreGensMessages.ts +++ b/backend/src/exploreGensMessages.ts @@ -3,9 +3,12 @@ export default { auto_update_finished: "Installed generators have been updated.", failed_to_install: (genName: string) => `Could not install ${genName}`, failed_to_uninstall: (genName: string) => `Could not uninstall ${genName}`, + failed_to_update: (genName: string) => `Could not update ${genName}`, uninstalling: (genName: string) => `Uninstalling ${genName} ...`, uninstalled: (genName: string) => `${genName} has been uninstalled.`, installing: (genName: string) => `Installing the latest version of ${genName}...`, installed: (genName: string) => `${genName} has been installed.`, + updating: (genName: string) => `Updating to the latest version of ${genName}...`, + updated: (genName: string) => `${genName} has been updated.`, failed_to_get: (gensQueryUrl: string) => `Could not get generators with the queryUrl ${gensQueryUrl}` }; \ No newline at end of file diff --git a/backend/src/exploregens.ts b/backend/src/exploregens.ts index bc7e50528..bb6e7798f 100644 --- a/backend/src/exploregens.ts +++ b/backend/src/exploregens.ts @@ -8,45 +8,55 @@ import * as path from "path"; import messages from "./exploreGensMessages"; import Environment = require("yeoman-environment"); +export enum GenState { + uninstalling = "uninstalling", + updating = "updating", + installing = "installing", + notInstalled = "notInstalled", + installed = "installed" +}; export class ExploreGens { public static getInstallationLocation(wsConfig: any) { return _.trim(wsConfig.get(ExploreGens.INSTALLATION_LOCATION)); } - private static readonly INSTALLATION_LOCATION = "installationLocation"; + private static readonly INSTALLATION_LOCATION = "ApplicationWizard.installationLocation"; private logger: IChildLogger; private rpc: IRpc; - private gensBeingHandled: string[]; + private gensBeingHandled: any[]; private cachedInstalledGeneratorsPromise: Promise; private context: any; private vscode: any; private isInTheiaCached: boolean; + private npmGlobalPathPromise: Promise; private readonly theiaCommands: string[] = ["theia.open", "preferences:open", "keymaps:open", "workspace:openRecent"]; private readonly GLOBAL_ACCEPT_LEGAL_NOTE = "global.exploreGens.acceptlegalNote"; - private readonly LAST_AUTO_UPDATE_DATE = "lastAutoUpdateDate"; - private readonly SEARCH_QUERY = "searchQuery"; - private readonly AUTO_UPDATE = "autoUpdate" + private readonly LAST_AUTO_UPDATE_DATE = "global.exploreGens.lastAutoUpdateDate"; + private readonly SEARCH_QUERY = "ApplicationWizard.searchQuery"; + private readonly AUTO_UPDATE = "ApplicationWizard.autoUpdate" private readonly NPM = (process.platform === "win32" ? "npm.cmd" : "npm"); private readonly EMPTY = ""; + private readonly SLASH = "/"; private readonly NODE_MODULES = "node_modules"; + private readonly GENERATOR = "generator-"; private readonly ONE_DAY = 1000 * 60 * 60 * 24; private readonly NPM_REGISTRY_HOST = _.get(process, "env.NPM_CFG_REGISTRY", "http://registry.npmjs.com/"); private readonly SEARCH_QUERY_PREFIX = `${this.NPM_REGISTRY_HOST}-/v1/search?text=`; private readonly SEARCH_QUERY_SUFFIX = "keywords:yeoman-generator &size=25&ranking=popularity"; - constructor(rpc: IRpc, logger: IChildLogger, context?: any, vscode?: any) { + constructor(logger: IChildLogger, context?: any, vscode?: any) { this.context = context; this.vscode = vscode; this.logger = logger; this.gensBeingHandled = []; - this.init(rpc); + this.npmGlobalPathPromise = this.getNpmGlobalPath(); this.doGeneratorsUpdate(); } - private init(rpc: IRpc) { + public init(rpc: IRpc) { this.initRpc(rpc); this.setInstalledGens(); } @@ -79,23 +89,18 @@ export class ExploreGens { return true; } - private doGeneratorsUpdate() { + private async doGeneratorsUpdate() { const lastUpdateDate = this.context.globalState.get(this.LAST_AUTO_UPDATE_DATE, 0); const currentDate = Date.now(); if ((currentDate - lastUpdateDate) > this.ONE_DAY) { this.context.globalState.update(this.LAST_AUTO_UPDATE_DATE, currentDate); - this.updateAllInstalledGenerators(); + const autoUpdateEnabled = this.getWsConfig().get(this.AUTO_UPDATE, true); + if (autoUpdateEnabled) { + await this.updateAllInstalledGenerators(); + } } } - private async getAllInstalledGenerators(): Promise { - return new Promise(resolve => { - const yoEnv: Environment.Options = Environment.createEnv(); - const npmPaths = this.getNpmPaths(yoEnv); - yoEnv.lookup({ npmPaths }, async () => this.onEnvLookup(yoEnv, resolve)); - }); - } - private initRpc(rpc: IRpc) { this.rpc = rpc; this.rpc.registerMethod({ func: this.getFilteredGenerators, thisArg: this }); @@ -109,21 +114,19 @@ export class ExploreGens { } private async updateAllInstalledGenerators() { - const autoUpdateEnabled = this.getWsConfig().get(this.AUTO_UPDATE, true); - if (autoUpdateEnabled) { - const installedGenerators: string[] = await this.getAllInstalledGenerators(); - if (!_.isEmpty(installedGenerators)) { - this.logger.debug(messages.auto_update_started); - const statusBarMessage = this.vscode.window.setStatusBarMessage(messages.auto_update_started); - const locationParams = this.getGeneratorsLocationParams(); - const promises = _.map(installedGenerators, genName => { - return this.installGenerator(locationParams, genName, false); - }); - - await Promise.all(promises); - statusBarMessage.dispose(); - this.vscode.window.setStatusBarMessage(messages.auto_update_finished, 10000); - } + const installedGenerators: string[] = await this.getAllInstalledGenerators(); + if (!_.isEmpty(installedGenerators)) { + this.logger.debug(messages.auto_update_started); + const statusBarMessage = this.vscode.window.setStatusBarMessage(messages.auto_update_started); + const locationParams = this.getGeneratorsLocationParams(); + const promises = _.map(installedGenerators, genName => { + return this.update(locationParams, genName); + }); + + await Promise.all(promises); + this.setInstalledGens(); + statusBarMessage.dispose(); + this.vscode.window.setStatusBarMessage(messages.auto_update_finished, 10000); } } @@ -131,20 +134,6 @@ export class ExploreGens { return this.vscode.workspace.getConfiguration(); } - private async install(gen: any) { - const locationParams = this.getGeneratorsLocationParams(); - const res = await this.installGenerator(locationParams, gen.package.name); - this.setInstalledGens(); - return res; - } - - private async uninstall(gen: any) { - const locationParams = this.getGeneratorsLocationParams(); - const res = await this.uninstallGenerator(locationParams, gen.package.name); - this.setInstalledGens(); - return !res; - } - private async getFilteredGenerators(query?: string, author?: string) { query = query || this.EMPTY; author = author || this.EMPTY; @@ -155,8 +144,13 @@ export class ExploreGens { const res: any = await npmFetch.json(gensQueryUrl); const filteredGenerators = _.map(_.get(res, "objects"), gen => { const genName = gen.package.name; - gen.disabledToHandle = _.includes(this.gensBeingHandled, genName) ? true : false; - gen.installed = _.includes(cachedGens, genName); + gen.state = _.includes(cachedGens, genName) ? GenState.installed : GenState.notInstalled; + gen.disabledToHandle = false; + const handlingState = this.getHandlingState(genName); + if (handlingState) { + gen.state = handlingState; + gen.disabledToHandle = true; + } return gen; }); @@ -190,72 +184,101 @@ export class ExploreGens { return util.promisify(cp.exec)(arg); } - private async installGenerator(locationParams: string, genName: string, isInstall = true): Promise { - this.gensBeingHandled.push(genName); + private async install(gen: any) { + const genName = gen.package.name; + this.addToHandled(genName, GenState.installing); const installingMessage = messages.installing(genName); - let statusbarMessage; - if (isInstall) { - statusbarMessage = this.vscode.window.setStatusBarMessage(installingMessage); - } + const statusbarMessage = this.vscode.window.setStatusBarMessage(installingMessage); try { this.logger.debug(installingMessage); + this.updateBeingHandledGenerator(genName, GenState.installing); + const locationParams = this.getGeneratorsLocationParams(); const installCommand = this.getNpmInstallCommand(locationParams, genName); - this.updateBeingHandledGenerator(genName, true); await this.exec(installCommand); const successMessage = messages.installed(genName); this.logger.debug(successMessage); - if (isInstall) { - this.vscode.window.showInformationMessage(successMessage); - } - return true; + this.vscode.window.showInformationMessage(successMessage); + this.updateBeingHandledGenerator(genName, GenState.installed); } catch (error) { this.showAndLogError(messages.failed_to_install(genName), error); - return false; + this.updateBeingHandledGenerator(genName, GenState.notInstalled); } finally { - this.removeFromArray(this.gensBeingHandled, genName); - this.updateBeingHandledGenerator(genName, false); - if (statusbarMessage) { - statusbarMessage.dispose(); - } + this.removeFromHandled(genName); + this.setInstalledGens(); + statusbarMessage.dispose(); } } - private async uninstallGenerator(locationParams: string, genName: string): Promise { - this.gensBeingHandled.push(genName); + private async uninstall(gen: any) { + const genName = gen.package.name; + this.addToHandled(genName, GenState.uninstalling); const uninstallingMessage = messages.uninstalling(genName); const statusbarMessage = this.vscode.window.setStatusBarMessage(uninstallingMessage); try { this.logger.debug(uninstallingMessage); + this.updateBeingHandledGenerator(genName, GenState.uninstalling); + const locationParams = this.getGeneratorsLocationParams(); const uninstallCommand = this.getNpmUninstallCommand(locationParams, genName); await this.exec(uninstallCommand); const successMessage = messages.uninstalled(genName); this.logger.debug(successMessage); this.vscode.window.showInformationMessage(successMessage); - return true; + this.updateBeingHandledGenerator(genName, GenState.notInstalled); } catch (error) { this.showAndLogError(messages.failed_to_uninstall(genName), error); - return false; + this.updateBeingHandledGenerator(genName, GenState.installed); } finally { - this.removeFromArray(this.gensBeingHandled, genName); + this.removeFromHandled(genName); + this.setInstalledGens(); statusbarMessage.dispose(); } } - private async isInstalled(gen: any) { - const installedGens: string[] = await this.getInstalledGens(); - return _.includes(installedGens, gen.package.name); + private async update(locationParams: string, genName: string) { + this.addToHandled(genName, GenState.updating); + + try { + this.logger.debug(messages.updating(genName)); + this.updateBeingHandledGenerator(genName, GenState.updating); + const installCommand = this.getNpmInstallCommand(locationParams, genName); + await this.exec(installCommand); + this.logger.debug(messages.updated(genName)); + this.updateBeingHandledGenerator(genName, GenState.installed); + } catch (error) { + this.showAndLogError(messages.failed_to_update(genName), error); + this.updateBeingHandledGenerator(genName, GenState.notInstalled); + } finally { + this.removeFromHandled(genName); + } } - private removeFromArray(array: string[], valueToRemove: string) { - _.remove(array, value => { - return value === valueToRemove; + private async updateBeingHandledGenerator(genName: string, state: GenState) { + this.rpc.invoke("updateBeingHandledGenerator", [genName, state]); + } + + private addToHandled(genName: string, state: GenState) { + this.gensBeingHandled.push({ name: genName, state }); + } + + private removeFromHandled(genName: string) { + _.remove(this.gensBeingHandled, gen => { + return gen.name === genName; }); } - private updateBeingHandledGenerator(genName: string, isBeingHandled: boolean) { - this.rpc.invoke("updateBeingHandledGenerator", [genName, isBeingHandled]); + private getHandlingState(genName: string) { + const gen = _.find(this.gensBeingHandled, gen => { + return gen.name === genName; + }); + + return _.get(gen, "state"); + } + + private async isInstalled(gen: any) { + const installedGens: string[] = await this.getInstalledGens(); + return _.includes(installedGens, gen.package.name); } private getNpmInstallCommand(locationParams: string, genName: string) { @@ -266,22 +289,35 @@ export class ExploreGens { return `${this.NPM} uninstall ${locationParams} ${genName}`; } - private getNpmPaths(env: Environment.Options) { + private async getNpmGlobalPath(): Promise { + const res = await this.exec(`${this.NPM} root -g`); + return _.trim(res.stdout); + } + + private async getAllInstalledGenerators(): Promise { + const npmPaths = await this.getNpmPaths(); + return new Promise(resolve => { + const yoEnv: Environment.Options = Environment.createEnv(); + yoEnv.lookup({ npmPaths }, async () => this.onEnvLookup(yoEnv, resolve)); + }); + } + + private async getNpmPaths() { const customLocation = ExploreGens.getInstallationLocation(this.getWsConfig()); if (_.isEmpty(customLocation)) { - return env.getNpmPaths(); + return [await this.npmGlobalPathPromise]; } return [path.join(customLocation, this.NODE_MODULES)]; } private onEnvLookup(env: Environment.Options, resolve: any) { - const gensMeta: string[] = env.getGeneratorsMeta(); - const gensFullNames = _.map(gensMeta, (genMeta: any) => { - const packagePath = genMeta.packagePath; - const nodeModulesIndex = packagePath.indexOf(this.NODE_MODULES); - return packagePath.substring(nodeModulesIndex + this.NODE_MODULES.length + 1); - }) - resolve(_.uniq(gensFullNames)); + const genNames = env.getGeneratorNames(); + const gensFullNames = _.map(genNames, genName => { + const parts = _.split(genName, this.SLASH); + return _.size(parts) === 1 ? `${this.GENERATOR}${genName}` : `${parts[0]}${this.SLASH}${this.GENERATOR}${parts[1]}`; + }); + + resolve(gensFullNames); } } diff --git a/backend/src/logger/settings.ts b/backend/src/logger/settings.ts index 86fcf249a..f96ec3f52 100644 --- a/backend/src/logger/settings.ts +++ b/backend/src/logger/settings.ts @@ -4,8 +4,8 @@ import { LogLevel } from "@vscode-logging/logger"; /** * Note that the values of these configuration properties must match those defined in the package.json */ -export const LOGGING_LEVEL_CONFIG_PROP = "loggingLevel"; -export const SOURCE_TRACKING_CONFIG_PROP = "sourceLocationTracking"; +export const LOGGING_LEVEL_CONFIG_PROP = "ApplicationWizard.loggingLevel"; +export const SOURCE_TRACKING_CONFIG_PROP = "ApplicationWizard.sourceLocationTracking"; /** * @returns {LogLevel} diff --git a/backend/src/panels/ExploreGensPanel.ts b/backend/src/panels/ExploreGensPanel.ts index 03ceee127..2ac88f307 100644 --- a/backend/src/panels/ExploreGensPanel.ts +++ b/backend/src/panels/ExploreGensPanel.ts @@ -9,7 +9,7 @@ import { RpcExtension } from "@sap-devx/webview-rpc/out.ext/rpc-extension"; export class ExploreGensPanel extends AbstractWebviewPanel { public setWebviewPanel(webviewPanel: vscode.WebviewPanel) { super.setWebviewPanel(webviewPanel); - this.exploreGens = new ExploreGens(new RpcExtension(webviewPanel.webview), this.logger, this.context, vscode); + this.exploreGens.init(new RpcExtension(webviewPanel.webview)); this.initWebviewPanel(); } @@ -20,6 +20,7 @@ export class ExploreGensPanel extends AbstractWebviewPanel { this.viewTitle = "Explore and Install Generators"; this.focusedKey = "exploreGens.Focused"; this.htmlFileName = path.join("exploregens", "index.html"); + this.exploreGens = new ExploreGens(this.logger, this.context, vscode); } public loadWebviewPanel() { @@ -32,6 +33,5 @@ export class ExploreGensPanel extends AbstractWebviewPanel { public disposeWebviewPanel() { super.disposeWebviewPanel(); - this.exploreGens = null; } } diff --git a/backend/src/webSocketServer/exploreGensIndex.ts b/backend/src/webSocketServer/exploreGensIndex.ts index 70e28dcd1..549a48722 100644 --- a/backend/src/webSocketServer/exploreGensIndex.ts +++ b/backend/src/webSocketServer/exploreGensIndex.ts @@ -47,7 +47,7 @@ class ExploreGensWebSocketServer { workspace: { getConfiguration: () => { return { - get: () => true + get: () => "" }; } }, @@ -57,7 +57,8 @@ class ExploreGensWebSocketServer { } } }; - this.exploreGens = new ExploreGens(this.rpc, childLogger as IChildLogger, context, vscode); + this.exploreGens = new ExploreGens(childLogger as IChildLogger, context, vscode); + this.exploreGens.init(this.rpc); }); } } diff --git a/backend/tests/exploregens.spec.ts b/backend/tests/exploregens.spec.ts index aded23982..b78710ddf 100644 --- a/backend/tests/exploregens.spec.ts +++ b/backend/tests/exploregens.spec.ts @@ -13,7 +13,7 @@ import Environment = require("yeoman-environment"); const testYoEnv = { lookup: () => true, getNpmPaths: (): any[] => [], - getGeneratorsMeta: () => new Error("not implemented") + getGeneratorNames: () => new Error("not implemented") }; const config = { get: () => new Error("not implemented"), @@ -53,7 +53,7 @@ const testVscode = { }; mockVscode(testVscode, "src/exploregens.ts"); -import { ExploreGens } from "../src/exploregens"; +import { ExploreGens, GenState } from "../src/exploregens"; import { fail } from "assert"; describe('exploregens unit test', () => { @@ -109,7 +109,8 @@ describe('exploregens unit test', () => { } const rpc = new TestRpc(); const childLogger = { debug: () => true, error: () => true, fatal: () => true, warn: () => true, info: () => true, trace: () => true, getChildLogger: () => { return {} as IChildLogger; } }; - const exploregens = new ExploreGens(rpc, childLogger as IChildLogger, testVscode.context, testVscode); + const exploregens = new ExploreGens(childLogger as IChildLogger, testVscode.context, testVscode); + exploregens.init(rpc); before(() => { sandbox = sinon.createSandbox(); @@ -153,7 +154,7 @@ describe('exploregens unit test', () => { it("acceptLegalNote", async () => { globalStateMock.expects("update").withExactArgs(exploregens["GLOBAL_ACCEPT_LEGAL_NOTE"], true).resolves(); - const res = await exploregens["acceptLegalNote"](); + const res = await exploregens["acceptLegalNote"](); expect(res).to.be.true; }); @@ -161,21 +162,21 @@ describe('exploregens unit test', () => { it("is in theia, legal note is accepted", async () => { exploregens["isInTheiaCached"] = true; globalStateMock.expects("get").withExactArgs(exploregens["GLOBAL_ACCEPT_LEGAL_NOTE"], false).returns(true); - const res = await exploregens["isLegalNoteAccepted"](); + const res = await exploregens["isLegalNoteAccepted"](); expect(res).to.be.true; }); it("is in theia, legal note is not accepted", async () => { exploregens["isInTheiaCached"] = true; globalStateMock.expects("get").withExactArgs(exploregens["GLOBAL_ACCEPT_LEGAL_NOTE"], false).returns(false); - const res = await exploregens["isLegalNoteAccepted"](); + const res = await exploregens["isLegalNoteAccepted"](); expect(res).to.be.false; }); it("is not in theia", async () => { exploregens["isInTheiaCached"] = false; globalStateMock.expects("get").never(); - const res = await exploregens["isLegalNoteAccepted"](); + const res = await exploregens["isLegalNoteAccepted"](); expect(res).to.be.true; }); }); @@ -184,21 +185,21 @@ describe('exploregens unit test', () => { it("use cached value", async () => { exploregens["isInTheiaCached"] = true; vscodeCommandsMock.expects("getCommands").never(); - const res = await exploregens["isInTheia"](); + const res = await exploregens["isInTheia"](); expect(res).to.be.true; }); it("returns true", async () => { exploregens["isInTheiaCached"] = undefined; vscodeCommandsMock.expects("getCommands").withExactArgs(true).resolves(["theia.open", "preferences:open"]); - const res = await exploregens["isInTheia"](); + const res = await exploregens["isInTheia"](); expect(res).to.be.true; }); it("returns false", async () => { exploregens["isInTheiaCached"] = undefined; vscodeCommandsMock.expects("getCommands").withExactArgs(true).resolves(["workbench.action.openGlobalKeybindings"]); - const res = await exploregens["isInTheia"](); + const res = await exploregens["isInTheia"](); expect(res).to.be.false; }); }); @@ -206,7 +207,7 @@ describe('exploregens unit test', () => { describe("NPM", () => { it("win32 platform", () => { const stub = sinon.stub(process, 'platform').value("win32"); - const exploregens1 = new ExploreGens(rpc, null, testVscode.context, testVscode); + const exploregens1 = new ExploreGens(null, testVscode.context, testVscode); const res = exploregens1["NPM"]; expect(res).to.be.equal("npm.cmd"); stub.restore(); @@ -214,7 +215,7 @@ describe('exploregens unit test', () => { it("linux platfrom", () => { const stub = sinon.stub(process, 'platform').value("linux"); - const exploregens2 = new ExploreGens(rpc, null, testVscode.context, testVscode); + const exploregens2 = new ExploreGens(null, testVscode.context, testVscode); const res = exploregens2["NPM"]; expect(res).to.be.equal("npm"); stub.restore(); @@ -235,7 +236,7 @@ describe('exploregens unit test', () => { const customLocation = path.join("home", "user", "projects"); workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(customLocation); yoEnvMock.expects("createEnv").returns(testYoEnv); - testYoEnvMock.expects("lookup").withArgs({npmPaths: [path.join(customLocation, exploregens["NODE_MODULES"])]}); + testYoEnvMock.expects("lookup").withArgs({ npmPaths: [path.join(customLocation, exploregens["NODE_MODULES"])] }); exploregens["init"](rpc); }); @@ -249,9 +250,11 @@ describe('exploregens unit test', () => { rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["isLegalNoteAccepted"], thisArg: exploregens }); rpcMock.expects("registerMethod").withExactArgs({ func: exploregens["acceptLegalNote"], thisArg: exploregens }); - workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(""); + const globalLocation = "testGlobalPath"; + workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(); + exploregens["npmGlobalPathPromise"] = Promise.resolve(globalLocation); yoEnvMock.expects("createEnv").returns(testYoEnv); - testYoEnvMock.expects("lookup").withArgs({npmPaths: []}); + testYoEnvMock.expects("lookup").withArgs({ npmPaths: [globalLocation] }); exploregens["init"](rpc); }); }); @@ -264,19 +267,18 @@ describe('exploregens unit test', () => { }; const genName = gen.package.name; - it("update generator", async () => { + it("successfully installed", async () => { workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(""); + vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.installing(genName)).returns(statusBarMessage); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.installed(genName)); loggerMock.expects("debug").withExactArgs(messages.installing(genName)); loggerMock.expects("debug").withExactArgs(messages.installed(genName)); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.installing]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.installed]); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", genName)); exploreGensMock.expects("setInstalledGens"); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.installed(genName)); - vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.installing(genName)).returns(statusBarMessage); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, false]); - const res = await exploregens["install"](gen); - expect(res).to.be.true; + await exploregens["install"](gen); }); it("an error is thrown", async () => { @@ -287,14 +289,13 @@ describe('exploregens unit test', () => { vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.failed_to_install(genName) + `: ${errorMessage}`).resolves(); loggerMock.expects("debug").withExactArgs(messages.installing(genName)); loggerMock.expects("error").withExactArgs(errorMessage); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, false]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.installing]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.notInstalled]); statusBarMessageMock.expects("dispose"); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", genName)).throws(errorMessage); exploreGensMock.expects("setInstalledGens"); - const res = await exploregens["install"](gen); - expect(res).to.be.false; + await exploregens["install"](gen); }); }); @@ -311,24 +312,24 @@ describe('exploregens unit test', () => { expect(res).to.be.deep.equal([expectedResult.objects, expectedResult.total]); expect(res[0][0].disabledToHandle).to.be.false; expect(res[0][1].disabledToHandle).to.be.false; - expect(res[0][0].installed).to.be.false; - expect(res[0][1].installed).to.be.true; + expect(res[0][0].state).to.be.equal(GenState.notInstalled); + expect(res[0][1].state).to.be.equal(GenState.installed); }); - it("query parameter is some words", async () => { + it("a generator is updating", async () => { const expectedResult = { objects: [{ package: { name: "generator-aa" } }], total: 1 } const url = exploregens["getGensQueryURL"]("test of query", ""); npmFetchMock.expects("json").withExactArgs(url).resolves(expectedResult); - exploregens["gensBeingHandled"] = ["generator-aa"]; + exploregens["gensBeingHandled"] = [{name: "generator-aa", state: GenState.updating}]; exploregens["cachedInstalledGeneratorsPromise"] = Promise.resolve(["generator-aa"]); const res = await exploregens["getFilteredGenerators"]("test of query"); expect(res[0]).to.be.deep.equal(expectedResult.objects); expect(res[1]).to.be.equal(expectedResult.total); expect(res[0][0].disabledToHandle).to.be.true; - expect(res[0][0].installed).to.be.true; + expect(res[0][0].state).to.be.equal(GenState.updating); }); it("npmFetch.json throws error", async () => { @@ -412,7 +413,7 @@ describe('exploregens unit test', () => { it("updateAllInstalledGenerators doesn't called", async () => { globalStateMock.expects("get").withExactArgs(exploregens["LAST_AUTO_UPDATE_DATE"], 0).returns(Date.now()); workspaceConfigMock.expects("get").never(); - + await exploregens["doGeneratorsUpdate"](); }); @@ -420,11 +421,11 @@ describe('exploregens unit test', () => { globalStateMock.expects("get").withExactArgs(exploregens["LAST_AUTO_UPDATE_DATE"], 0).returns(100); workspaceConfigMock.expects("get").withExactArgs(exploregens["AUTO_UPDATE"], true).returns(false); workspaceConfigMock.expects("get").never(); - + await exploregens["doGeneratorsUpdate"](); }); - it("generators auto update is true and downloadedGenerators returns undefined", async () => { + it("generators auto update is true and getAllInstalledGenerators returns undefined", async () => { globalStateMock.expects("get").withExactArgs(exploregens["LAST_AUTO_UPDATE_DATE"], 0).returns(100); workspaceConfigMock.expects("get").withExactArgs(exploregens["AUTO_UPDATE"], true).returns(true); exploreGensMock.expects("getAllInstalledGenerators").resolves(); @@ -433,24 +434,42 @@ describe('exploregens unit test', () => { await exploregens["doGeneratorsUpdate"](); }); - it("generators auto update is true and downloadedGenerators returns a generators list", async () => { + it("generators auto update is true and getAllInstalledGenerators returns a generators list", async () => { globalStateMock.expects("get").withExactArgs(exploregens["LAST_AUTO_UPDATE_DATE"], 0).returns(100); workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(""); workspaceConfigMock.expects("get").withExactArgs(exploregens["AUTO_UPDATE"], true).returns(true); vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.auto_update_started).returns(statusBarMessage); vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.auto_update_finished, 10000); loggerMock.expects("debug").withExactArgs(messages.auto_update_started); - loggerMock.expects("debug").withExactArgs(messages.installing("generator-aa")); - loggerMock.expects("debug").withExactArgs(messages.installing("@sap/generator-bb")); - loggerMock.expects("debug").withExactArgs(messages.installed("generator-aa")); - loggerMock.expects("debug").withExactArgs(messages.installed("@sap/generator-bb")); + loggerMock.expects("debug").withExactArgs(messages.updating("generator-aa")); + loggerMock.expects("debug").withExactArgs(messages.updating("@sap/generator-bb")); + loggerMock.expects("debug").withExactArgs(messages.updated("generator-aa")); + loggerMock.expects("debug").withExactArgs(messages.updated("@sap/generator-bb")); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", "generator-aa")).resolves(); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", "@sap/generator-bb")).resolves(); - exploreGensMock.expects("getAllInstalledGenerators").resolves(["generator-aa", "@sap/generator-bb"]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["@sap/generator-bb", true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", false]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["@sap/generator-bb", false]); + exploreGensMock.expects("getAllInstalledGenerators").twice().resolves(["generator-aa", "@sap/generator-bb"]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", GenState.updating]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["@sap/generator-bb", GenState.updating]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", GenState.installed]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["@sap/generator-bb", GenState.installed]); + + await exploregens["doGeneratorsUpdate"](); + }); + + it("generators auto update is true and getAllInstalledGenerators returns a generators list, update fails", async () => { + const errorMessage = `update failure.`; + globalStateMock.expects("get").withExactArgs(exploregens["LAST_AUTO_UPDATE_DATE"], 0).returns(100); + workspaceConfigMock.expects("get").withExactArgs(ExploreGens["INSTALLATION_LOCATION"]).returns(""); + workspaceConfigMock.expects("get").withExactArgs(exploregens["AUTO_UPDATE"], true).returns(true); + vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.auto_update_started).returns(statusBarMessage); + vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(messages.auto_update_finished, 10000); + loggerMock.expects("debug").withExactArgs(messages.auto_update_started); + loggerMock.expects("debug").withExactArgs(messages.updating("generator-aa")); + exploreGensMock.expects("getAllInstalledGenerators").twice().resolves(["generator-aa"]); + exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", "generator-aa")).throws(errorMessage); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", GenState.updating]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", GenState.notInstalled]); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.failed_to_update("generator-aa") + `: ${errorMessage}`).resolves(); await exploregens["doGeneratorsUpdate"](); }); @@ -458,7 +477,7 @@ describe('exploregens unit test', () => { describe("onEnvLookup", () => { it("there are no installed generators", async () => { - testYoEnvMock.expects("getGeneratorsMeta").returns([]); + testYoEnvMock.expects("getGeneratorNames").returns([]); const res = await new Promise(resolve => { exploregens["onEnvLookup"](testYoEnv, resolve); }); @@ -466,32 +485,20 @@ describe('exploregens unit test', () => { }); it("there are installed generators", async () => { - testYoEnvMock.expects("getGeneratorsMeta").returns([{ - packagePath: path.join("path1", "node_modules", "generator-aa") - }, { - packagePath: path.join("path2", "node_modules", "generator-bb") - }, { - packagePath: path.join("path3", "node_modules", "generator-aa") - }]); + testYoEnvMock.expects("getGeneratorNames").returns(["aa", "bb", "@sap/cc"]); const res = await new Promise(resolve => { exploregens["onEnvLookup"](testYoEnv, resolve); }); - expect(res).to.have.lengthOf(2); + expect(res).to.have.lengthOf(3); expect(res).includes("generator-bb"); expect(res).includes("generator-aa"); + expect(res).includes("@sap/generator-cc"); }); }); - describe("updateBeingHandledGenerator", () => { - it("invoke method with isBeingHandled param equal true", () => { - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", true]); - exploregens["updateBeingHandledGenerator"]("generator-aa", true); - }); - - it("invoke method with isBeingHandled param equel false", () => { - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", false]); - exploregens["updateBeingHandledGenerator"]("generator-aa", false); - }); + it("updateBeingHandledGenerator", () => { + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", ["generator-aa", GenState.installed]); + exploregens["updateBeingHandledGenerator"]("generator-aa", GenState.installed); }); it("isInstalled", async () => { @@ -527,12 +534,13 @@ describe('exploregens unit test', () => { vscodeWindowMock.expects("showInformationMessage").withExactArgs(successMessage); loggerMock.expects("debug").withExactArgs(uninstallingMessage); loggerMock.expects("debug").withExactArgs(successMessage); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.uninstalling]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.notInstalled]); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmUninstallCommand"]("-g", genName)).resolves(); exploreGensMock.expects("setInstalledGens"); statusBarMessageMock.expects("dispose"); - const res = await exploregens["uninstall"](gen); - expect(res).to.be.false; + await exploregens["uninstall"](gen); }); it("uninstall fails on exec method", async () => { @@ -545,61 +553,13 @@ describe('exploregens unit test', () => { vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.failed_to_uninstall(genName) + `: ${errorMessage}`).resolves(); loggerMock.expects("debug").withExactArgs(uninstallingMessage); loggerMock.expects("error").withExactArgs(errorMessage); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.uninstalling]); + rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, GenState.installed]); exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmUninstallCommand"]("-g", genName)).throws(errorMessage); exploreGensMock.expects("setInstalledGens"); statusBarMessageMock.expects("dispose"); - const res = await exploregens["uninstall"](gen); - expect(res).to.be.true; - }); - }); - - describe("installGenerator", () => { - const gen: any = { - package: { - name: "generator-aa" - } - }; - const genName = gen.package.name; - - it("generator is already installed", async () => { - const installingMessage = messages.installing(genName); - const successMessage = messages.installed(genName); - - vscodeWindowMock.expects("setStatusBarMessage").withExactArgs(installingMessage); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(successMessage); - loggerMock.expects("debug").withExactArgs(installingMessage); - loggerMock.expects("debug").withExactArgs(successMessage); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, false]); - exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", genName)).resolves(); - - expect(exploregens["installGenerator"]("-g", genName)); - }); - - it("generator doesn't installed", async () => { - const successMessage = messages.installed(genName); - - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, false]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, true]); - exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", genName)).resolves(); - loggerMock.expects("debug").withExactArgs(messages.installing(genName)); - loggerMock.expects("debug").withExactArgs(successMessage); - - await exploregens["installGenerator"]("-g", genName, false); - }); - - it("install fails on exec method", async () => { - const errorMessage = `install failure.`; - - loggerMock.expects("error").withExactArgs(errorMessage); - loggerMock.expects("debug").withExactArgs(messages.installing(genName)); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, true]); - rpcMock.expects("invoke").withExactArgs("updateBeingHandledGenerator", [genName, false]); - exploreGensMock.expects("exec").withExactArgs(exploregens["getNpmInstallCommand"]("-g", genName)).throws(errorMessage); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.failed_to_install(genName) + `: ${errorMessage}`).resolves(); - - await exploregens["installGenerator"]("-g", genName, false); + await exploregens["uninstall"](gen); }); }); @@ -607,4 +567,4 @@ describe('exploregens unit test', () => { const res = exploregens["getGensQueryURL"](" test ", " "); expect(res).that.does.not.include(" "); }); -}); +}); \ No newline at end of file diff --git a/frontend/src/ExploreGensApp.vue b/frontend/src/ExploreGensApp.vue index 241aed0e1..f64e64696 100644 --- a/frontend/src/ExploreGensApp.vue +++ b/frontend/src/ExploreGensApp.vue @@ -154,43 +154,26 @@ export default { onDisclaimer() { this.disclaimerOpened = !this.disclaimerOpened; }, - genDisplayName(gen) { - return `${gen.package.name} ${gen.package.version}`; - }, actionName(gen) { - if (gen.disabledToHandle) { - return gen.installed - ? this.messages.uninstalling - : this.messages.installing; + if (gen.state === "installed") { + return messages.uninstall; + } else if (gen.state === "notInstalled") { + return messages.install; + } else { // installing, uninstalling, updating + return messages[gen.state]; } - - return gen.installed ? this.messages.uninstall : this.messages.install; }, actionColor(gen) { if (gen.disabledToHandle) { return "primary"; } - return gen.installed ? "#585858" : "primary"; + return gen.state === "installed" ? "#585858" : "primary"; }, - async onAction(gen) { + onAction(gen) { if (!gen.disabledToHandle) { - gen.disabledToHandle = true; - gen.action = this.actionName(gen); - const action = gen.installed ? "uninstall" : "install"; - gen.color = this.actionColor(gen); - gen.installed = await this.rpc.invoke(action, [gen]); - - const currentGen = _.find(this.gens, currentGen => { - return gen.package.name === currentGen.package.name; - }); - - if (currentGen) { - currentGen.disabledToHandle = false; - currentGen.installed = gen.installed; - currentGen.action = this.actionName(currentGen); - currentGen.color = this.actionColor(currentGen); - } + const action = (gen.state === "installed" ? "uninstall" : "install"); + this.rpc.invoke(action, [gen]); } }, onQueryChange() { @@ -225,13 +208,16 @@ export default { async onAcceptLegalNote() { this.isLegalNoteAccepted = await this.rpc.invoke("acceptLegalNote"); }, - async updateBeingHandledGenerator(genName, isBeingHandled) { + async updateBeingHandledGenerator(genName, genState) { const gen = _.find(this.gens, gen => { return gen.package.name === genName; }); if (gen) { - gen.disabledToHandle = isBeingHandled; + gen.disabledToHandle = _.includes(["uninstalling", "installing", "updating"], genState); + gen.state = genState; + gen.action = this.actionName(gen); + gen.color = this.actionColor(gen); } }, getVscodeApi() { @@ -245,10 +231,15 @@ export default { }, initRpc(rpc) { this.rpc = rpc; - rpc.registerMethod({ - func: this.updateBeingHandledGenerator, - thisArg: this, - name: this.updateBeingHandledGenerator.name + const functions = [ + "updateBeingHandledGenerator" + ]; + _.forEach(functions, funcName => { + this.rpc.registerMethod({ + func: this[funcName], + thisArg: this, + name: funcName + }); }); }, async setupRpc() { diff --git a/frontend/src/exploreGensMessages.js b/frontend/src/exploreGensMessages.js index 47e2156bc..63ce8d99e 100644 --- a/frontend/src/exploreGensMessages.js +++ b/frontend/src/exploreGensMessages.js @@ -17,6 +17,7 @@ export default { uninstall: "Uninstall", installing: "Installing...", uninstalling: "Uninstalling...", + updating: "Updating...", more_info: "More information", accept: "OK", view_disclaimer: "View Disclaimer",