diff --git a/backend/.nycrc.json b/backend/.nycrc.json index 2bd1fc818..525f4f14b 100644 --- a/backend/.nycrc.json +++ b/backend/.nycrc.json @@ -1,14 +1,15 @@ { "require": ["ts-node/register/transpile-only"], "include": ["src/**/*.ts"], + "exclude": ["src/logger/*.ts"], "reporter": ["lcov", "text"], "extension": [".ts"], "all": true, "temp-dir": "./reports/.nyc_output", "report-dir": "./reports/coverage", "check-coverage": true, - "branches": 27.9, - "lines": 39.9, - "functions": 31.7, - "statements": 40 + "branches": 30.1, + "lines": 43.5, + "functions": 33, + "statements": 43.5 } diff --git a/backend/package.json b/backend/package.json index 23397038a..1848ce215 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "license": "Apache 2.0", "description": "Provide rich user experience for Yeoman generators using VSCode extension or the browser", "repository": "https://github.com/SAP/yeoman-ui", - "version": "0.0.35", + "version": "0.0.36", "engines": { "vscode": "^1.39.2" }, @@ -24,8 +24,8 @@ "title": "Yeoman UI Generators" }, { - "command": "yeomanUI.toggleLog", - "title": "Toggle Log", + "command": "yeomanUI.toggleOutput", + "title": "Toggle Output", "icon": { "light": "./resources/images/icons/console_light.svg", "dark": "./resources/images/icons/console_dark.svg" @@ -35,17 +35,44 @@ "menus": { "commandPalette": [ { - "command": "yeomanUI.toggleLog", + "command": "yeomanUI.toggleOutput", "when": "false" } ], "editor/title": [ { - "command": "yeomanUI.toggleLog", + "command": "yeomanUI.toggleOutput", "group": "navigation", "when": "yeomanUI.Focused" } ] + }, + "configuration": { + "type": "object", + "title": "Yeoman UI", + "properties": { + "logger.loggingLevel": { + "type": "string", + "enum": [ + "off", + "fatal", + "error", + "warn", + "info", + "debug", + "trace" + ], + "default": "warn", + "description": "The verbosity of logging according to the following order: trace > debug > info > warn > error > fatal > off.", + "scope": "resource" + }, + "logger.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" + } + } } }, "scripts": { @@ -75,7 +102,8 @@ "titleize": "^1.0.1", "humanize-string": "^1.0.2", "fs-extra": "^8.1.0", - "chalk": "^3.0.0" + "chalk": "^3.0.0", + "@vscode-logging/logger": "^0.1.1" }, "devDependencies": { "@types/chai": "^4.2.5", diff --git a/backend/src/extension.ts b/backend/src/extension.ts index 32fe0034d..7fc3e70b4 100644 --- a/backend/src/extension.ts +++ b/backend/src/extension.ts @@ -9,9 +9,20 @@ import { OutputChannelLog } from './output-channel-log'; import { GeneratorFilter } from './filter'; import backendMessages from "./messages"; import { Theia } from './theia'; +import { createExtensionLoggerAndSubscribeToLogSettingsChanges } from "./logger/logger-wrapper"; +import { getClassLogger } from "./logger/logger-wrapper"; +import { IChildLogger } from "@vscode-logging/logger"; +const ERROR_ACTIVATION_FAILED_LOGGER_CONFIG = 'Extension activation failed due to Logger configuration failure:'; export function activate(context: vscode.ExtensionContext) { + try { + createExtensionLoggerAndSubscribeToLogSettingsChanges(context); + } catch (error) { + console.error(ERROR_ACTIVATION_FAILED_LOGGER_CONFIG, error.message); + return; + } + context.subscriptions.push( vscode.commands.registerCommand('loadYeomanUI', (options?: any) => { const genFilter = _.get(options, "filter"); @@ -19,10 +30,10 @@ export function activate(context: vscode.ExtensionContext) { YeomanUIPanel.createOrShow(context.extensionPath, GeneratorFilter.create(genFilter), messages); })); context.subscriptions.push( - vscode.commands.registerCommand('yeomanUI.toggleLog', () => { + vscode.commands.registerCommand('yeomanUI.toggleOutput', () => { const yeomanUi = _.get(YeomanUIPanel, "currentPanel.yeomanui"); if (yeomanUi) { - yeomanUi.toggleLog(); + yeomanUi.toggleOutput(); } })); @@ -42,6 +53,7 @@ export function activate(context: vscode.ExtensionContext) { * Manages webview panels */ export class YeomanUIPanel { + private readonly logger: IChildLogger = getClassLogger(YeomanUI.name); /** * Track the currently panel. Only allow a single panel to exist at a time. */ @@ -97,10 +109,10 @@ export class YeomanUIPanel { this.panel = panel; this.extensionPath = extensionPath; this.rpc = new RpcExtension(this.panel.webview); - const logger: YouiLog = new OutputChannelLog(); + const outputChannel: YouiLog = new OutputChannelLog(); this.theia = new Theia(); - this.yeomanui = new YeomanUI(this.rpc, logger, YeomanUIPanel.genFilter); + this.yeomanui = new YeomanUI(this.rpc, outputChannel, this.logger, YeomanUIPanel.genFilter); // Set the webview's initial html content this._update(); diff --git a/backend/src/logger/logger-wrapper.ts b/backend/src/logger/logger-wrapper.ts new file mode 100644 index 000000000..015175fc2 --- /dev/null +++ b/backend/src/logger/logger-wrapper.ts @@ -0,0 +1,91 @@ +import * as vscode from "vscode"; // NOSONAR +import { getExtensionLogger, getExtensionLoggerOpts, IChildLogger, IVSCodeExtLogger } from "@vscode-logging/logger"; +import { listenToLogSettingsChanges, logLoggerDetails } from "./settings-changes-handler"; +// import {resolve} from "path"; +import { getLoggingLevelSetting, getSourceLocationTrackingSetting} from "./settings"; + +// const PACKAGE_JSON = "package.json"; +const YEOMAN_UI_LOGGER_NAME = "yeomanui"; + +/** + * A Simple Wrapper to hold the state of our "singleton" (per extension) IVSCodeExtLogger + * implementation. + */ + +export const ERROR_LOGGER_NOT_INITIALIZED = 'Logger has not yet been initialized!'; + +/** + * @type {IVSCodeExtLogger} + */ +let logger: any; + +function isInitialized() :boolean { + return (logger !== undefined ) ? true : false; +} + +/** + * Note the use of a getter function so the value would be lazy resolved on each use. + * This enables concise and simple consumption of the Logger throughout our Extension. + * + * @returns { IVSCodeExtLogger } + */ +export function getLogger() : IVSCodeExtLogger { + if (isInitialized() === false) { + throw Error(ERROR_LOGGER_NOT_INITIALIZED); + } + return logger; +} + +export function getClassLogger(className: string) : IChildLogger { + return getLogger().getChildLogger({label:className}); +} + +export function getYeomanUILibraryLogger() : IChildLogger { + return getLibraryLogger(YEOMAN_UI_LOGGER_NAME); +} + +function getLibraryLogger(libraryName: string) : IChildLogger { + return getLogger().getChildLogger({label:libraryName}); +} + +export function createExtensionLoggerAndSubscribeToLogSettingsChanges(context: vscode.ExtensionContext) { + createExtensionLogger(context); + // Subscribe to Logger settings changes. + listenToLogSettingsChanges(context); +} + +/** + * This function should be invoked after the Logger has been initialized in the Extension's `activate` function. + * @param {IVSCodeExtLogger} newLogger + */ +function initLoggerWrapper(newLogger: any) { + logger = newLogger; +} + +function createExtensionLogger(context: vscode.ExtensionContext) { + const contextLogPath = context.logPath; + const logLevelSetting: string = getLoggingLevelSetting(); + const sourceLocationTrackingSettings: boolean = getSourceLocationTrackingSetting(); + + //TODO: const meta = require(resolve(context.extensionPath, PACKAGE_JSON)); + const extensionLoggerOpts: getExtensionLoggerOpts = { + extName: "yeoman-ui.logger", + level: logLevelSetting, + logPath: contextLogPath, + sourceLocationTracking: sourceLocationTrackingSettings + }; + + // The Logger must first be initialized before any logging commands may be invoked. + const extensionLogger = getExtensionLogger(extensionLoggerOpts); + // Update the logger-wrapper with a reference to the extLogger. + initLoggerWrapper(extensionLogger); + logLoggerDetails(context, logLevelSetting); +} + +module.exports = { + getLogger, + createExtensionLoggerAndSubscribeToLogSettingsChanges, + getClassLogger, + getYeomanUILibraryLogger, + ERROR_LOGGER_NOT_INITIALIZED +}; \ No newline at end of file diff --git a/backend/src/logger/settings-changes-handler.ts b/backend/src/logger/settings-changes-handler.ts new file mode 100644 index 000000000..d5d202391 --- /dev/null +++ b/backend/src/logger/settings-changes-handler.ts @@ -0,0 +1,46 @@ +import * as vscode from "vscode"; // NOSONAR +import {getLogger} from "./logger-wrapper"; +import {LOGGING_LEVEL_CONFIG_PROP, SOURCE_TRACKING_CONFIG_PROP} from "./settings"; + +export function logLoggerDetails(context: vscode.ExtensionContext, configLogLevel: string): void { + getLogger().info(`Start Logging in Log Level: <${configLogLevel}>`); + getLogger().info(`Full Logs can be found in the <${context.logPath}> folder.`); +} + +/** + * @param {vscode.ExtensionContext} context + */ +export function listenToLogSettingsChanges(context: vscode.ExtensionContext) { + // To enable dynamic logging level we must listen to VSCode configuration changes + // on our `loggingLevelConfigProp` configuration setting. + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(LOGGING_LEVEL_CONFIG_PROP)) { + const logLevel: string = vscode.workspace + .getConfiguration() + .get(LOGGING_LEVEL_CONFIG_PROP); + + getLogger().changeLevel(logLevel); + logLoggerDetails(context, logLevel); + } + }) + ); + + // Enable responding to changes in the sourceLocationTracking setting + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SOURCE_TRACKING_CONFIG_PROP)) { + const newSourceLocationTracking = vscode.workspace + .getConfiguration() + .get(SOURCE_TRACKING_CONFIG_PROP); + + getLogger().changeSourceLocationTracking(newSourceLocationTracking); + } + }) + ); +} + +module.exports = { + listenToLogSettingsChanges, + logLoggerDetails +}; \ No newline at end of file diff --git a/backend/src/logger/settings.ts b/backend/src/logger/settings.ts new file mode 100644 index 000000000..6d8a1de74 --- /dev/null +++ b/backend/src/logger/settings.ts @@ -0,0 +1,31 @@ +import * as vscode from "vscode"; // NOSONAR +import { resolve } from "dns"; + +/** + * Note that the values of these configuration properties must match those defined in the package.json + */ +export const LOGGING_LEVEL_CONFIG_PROP = "logger.loggingLevel"; +export const SOURCE_TRACKING_CONFIG_PROP = "logger.sourceLocationTracking"; + +/** + * @returns {LogLevel} + */ +export function getLoggingLevelSetting(): string { + const config = vscode.workspace.getConfiguration(); + return config.get(LOGGING_LEVEL_CONFIG_PROP); +} + +/** + * @returns {boolean} + */ +export function getSourceLocationTrackingSetting(): boolean { + const config = vscode.workspace.getConfiguration(); + return config.get(SOURCE_TRACKING_CONFIG_PROP); +} + +module.exports = { + LOGGING_LEVEL_CONFIG_PROP, + SOURCE_TRACKING_CONFIG_PROP, + getLoggingLevelSetting, + getSourceLocationTrackingSetting +}; \ No newline at end of file diff --git a/backend/src/output-channel-log.ts b/backend/src/output-channel-log.ts index 9f4e46195..7ca50e0c5 100644 --- a/backend/src/output-channel-log.ts +++ b/backend/src/output-channel-log.ts @@ -30,7 +30,7 @@ export class OutputChannelLog implements YouiLog { public skip(value: string): void { getOutputChannel().appendLine(stripAnsi(value)); } - public showLog():boolean { + public showOutput():boolean { getOutputChannel().show(); return true; } diff --git a/backend/src/theia.ts b/backend/src/theia.ts index b1c192074..734d35c80 100644 --- a/backend/src/theia.ts +++ b/backend/src/theia.ts @@ -1,5 +1,5 @@ // TODO: remove this class when those vscode commands are implemented in theia. -import * as vscode from "vscode"; +import * as vscode from "vscode"; // NOSONAR export class Theia { private isInTheiaCached: boolean; diff --git a/backend/src/webSocketServer/index.ts b/backend/src/webSocketServer/index.ts index 78517ccb5..368b394ba 100644 --- a/backend/src/webSocketServer/index.ts +++ b/backend/src/webSocketServer/index.ts @@ -4,6 +4,7 @@ import { YeomanUI } from '../yeomanui'; import { YouiLog } from "../youi-log"; import { ServerLog } from './server-log'; import backendMessages from "../messages"; +import { IChildLogger } from "@vscode-logging/logger"; class YeomanUIWebSocketServer { private rpc: RpcExtensionWebSockets | undefined; @@ -28,7 +29,7 @@ class YeomanUIWebSocketServer { this.rpc = new RpcExtensionWebSockets(ws); //TODO: Use RPC to send it to the browser log (as a collapsed pannel in Vue) const logger: YouiLog = new ServerLog(this.rpc); - this.yeomanui = new YeomanUI(this.rpc, logger); + this.yeomanui = new YeomanUI(this.rpc, logger, {debug: () => {}, error: () => {}} as IChildLogger); this.yeomanui.setMessages(backendMessages); }); } diff --git a/backend/src/webSocketServer/server-log.ts b/backend/src/webSocketServer/server-log.ts index 0aeda6d64..4e88a2c88 100644 --- a/backend/src/webSocketServer/server-log.ts +++ b/backend/src/webSocketServer/server-log.ts @@ -4,7 +4,7 @@ const stripAnsi = require("strip-ansi"); export class ServerLog implements YouiLog { private rpc: RpcCommon; - private isLogVisible: boolean = false; + private isOutputVisible: boolean = false; /** * */ @@ -32,8 +32,8 @@ export class ServerLog implements YouiLog { public skip(str: string): void { this.rpc.invoke("log", [stripAnsi(str) + '\n']); } - public showLog(): boolean { - this.isLogVisible = !this.isLogVisible; - return !this.isLogVisible; + public showOutput(): boolean { + this.isOutputVisible = !this.isOutputVisible; + return !this.isOutputVisible; } } \ No newline at end of file diff --git a/backend/src/yeomanui.ts b/backend/src/yeomanui.ts index fd82b3008..836b6a8c4 100644 --- a/backend/src/yeomanui.ts +++ b/backend/src/yeomanui.ts @@ -1,6 +1,5 @@ import * as os from "os"; import * as path from "path"; -import * as fs from "fs"; import * as fsextra from "fs-extra"; import * as _ from "lodash"; import * as Environment from "yeoman-environment"; @@ -14,6 +13,7 @@ import { YouiLog } from "./youi-log"; import { IRpc } from "@sap-devx/webview-rpc/out.ext/rpc-common"; import Generator = require("yeoman-generator"); import { GeneratorType, GeneratorFilter } from "./filter"; +import { IChildLogger } from "@vscode-logging/logger"; export interface IGeneratorChoice { name: string; @@ -48,7 +48,8 @@ export class YeomanUI { private static NODE_MODULES = 'node_modules'; private rpc: IRpc; - private logger: YouiLog; + private outputChannel: YouiLog; + private logger: IChildLogger; private genMeta: { [namespace: string]: Environment.GeneratorMeta }; private youiAdapter: YouiAdapter; private gen: Generator | undefined; @@ -56,32 +57,42 @@ export class YeomanUI { private currentQuestions: Environment.Adapter.Questions; private genFilter: GeneratorFilter; - constructor(rpc: IRpc, logger: YouiLog, genFilter?: GeneratorFilter) { + constructor(rpc: IRpc, outputChannel: YouiLog, logger: IChildLogger, genFilter?: GeneratorFilter) { this.rpc = rpc; if (!this.rpc) { throw new Error("rpc must be set"); } + this.outputChannel = outputChannel; this.logger = logger; this.rpc.setResponseTimeout(3600000); this.rpc.registerMethod({ func: this.receiveIsWebviewReady, thisArg: this }); this.rpc.registerMethod({ func: this.runGenerator, thisArg: this }); this.rpc.registerMethod({ func: this.evaluateMethod, thisArg: this }); - this.rpc.registerMethod({ func: this.toggleLog, thisArg: this }); - this.rpc.registerMethod({ func: this.logMessage, thisArg: this }); + this.rpc.registerMethod({ func: this.toggleOutput, thisArg: this }); + this.rpc.registerMethod({ func: this.logError, thisArg: this }); - this.youiAdapter = new YouiAdapter(logger); + this.youiAdapter = new YouiAdapter(outputChannel); this.youiAdapter.setYeomanUI(this); this.promptCount = 0; this.genMeta = {}; this.currentQuestions = {}; this.setGenFilter(genFilter); - } public setGenFilter(genFilter: GeneratorFilter) { this.genFilter = genFilter ? genFilter : GeneratorFilter.create(); } + public async logError(error: any, prefixMessage?: string) { + let errorMessage = this.getErrorInfo(error); + if (prefixMessage) { + errorMessage = `${prefixMessage}\n${errorMessage}`; + } + + this.logger.error(errorMessage); + return errorMessage; + } + public async getGenerators(): Promise { // optimization: looking up generators takes a long time, so if generators are already loaded don't bother // on the other hand, we never look for newly installed generators... @@ -115,19 +126,12 @@ export class YeomanUI { } public async runGenerator(generatorName: string) { - // TODO: wait for dir to be created - fs.mkdir(YeomanUI.CWD, { recursive: true }, (err) => { - if (err) { - console.error(err); - } - }); - // TODO: should create and set target dir only after user has selected a generator; // see issue: https://github.com/yeoman/environment/issues/55 // process.chdir() doesn't work after environment has been created - - const env: Environment = Environment.createEnv(undefined, {}, this.youiAdapter); try { + await fsextra.mkdirs(YeomanUI.CWD); + const env: Environment = Environment.createEnv(undefined, {}, this.youiAdapter); const meta: Environment.GeneratorMeta = this.getGenMetadata(generatorName); // TODO: support sub-generators env.register(meta.resolved); @@ -148,10 +152,10 @@ export class YeomanUI { const image: any = genGetImage(); if (image.then) { image.then((contents: string) => { - console.log(`image contents: ${contents}`); + this.logger.debug(`image contents: ${contents}`); }); } else if (image !== undefined) { - console.log(`image contents: ${image}`); + this.logger.debug(`image contents: ${image}`); } } @@ -168,19 +172,17 @@ export class YeomanUI { let message: string; let destinationRoot = this.gen.destinationRoot(); if (err) { - console.error(err); message = `${generatorName} failed: ${err}.`; + this.logError(err, message); this.doGeneratorDone(false, message, destinationRoot); + } else { + message = `The '${generatorName}' project has been generated.`; + this.logger.debug("done running yeomanui! " + message + ` You can find it at ${destinationRoot}`); + this.doGeneratorDone(true, message, destinationRoot); } - - message = `The '${generatorName}' project has been generated.`; - console.log("done running yeomanui! " + message + ` You can find it at ${destinationRoot}`); - this.doGeneratorDone(true, message, destinationRoot); }); } catch (error) { - const errorMessage = `Error info ---->\n ${this.getErrorInfo(error)}`; - this.showMessageInOutput(errorMessage); - return Promise.reject(errorMessage); + this.logError(error); } } @@ -200,17 +202,18 @@ export class YeomanUI { if (_.isString(error)) { return error; } - const name = _.get(error, "name", ""); - const message = _.get(error, "message", ""); - const stack = _.get(error, "stack", ""); - const string = error.toString(); - return `name: ${name}\n message: ${message}\n stack: ${stack}\n string: ${string}\n`; + const name = _.get(error, "name", ""); + const message = _.get(error, "message", ""); + const stack = _.get(error, "stack", ""); + const string = error.toString(); + + return `name: ${name}\n message: ${message}\n stack: ${stack}\n string: ${string}\n`; } async showMessageInOutput(errorMessage: string) { await this.logMessage(errorMessage); - this.toggleLog(); + this.toggleOutput(); } public doGeneratorInstall(): Promise { @@ -242,11 +245,10 @@ export class YeomanUI { } } catch (error) { const questionInfo = `Could not update method '${methodName}' in '${questionName}' question in generator '${this.gen.options.namespace}'`; - const errorMessage = `${questionInfo}\nError info ---->\n ${this.getErrorInfo(error)}`; - this.showMessageInOutput(errorMessage); + const errorMessage = await this.logError(error, questionInfo); return Promise.reject(errorMessage); } -} + } public async receiveIsWebviewReady() { // TODO: loading generators takes a long time; consider prefetching list of generators @@ -255,12 +257,12 @@ export class YeomanUI { await this.runGenerator(response.name); } - public toggleLog(): boolean { - return this.logger.showLog(); + public toggleOutput(): boolean { + return this.outputChannel.showOutput(); } public logMessage(message: string): void { - this.logger.log(message); + this.outputChannel.log(message); } public async showPrompt(questions: Environment.Adapter.Questions): Promise { @@ -300,6 +302,7 @@ export class YeomanUI { try { packageJson = await this.getGenPackageJson(genPackagePath); } catch (error) { + this.logError(error); return Promise.resolve(undefined); } @@ -320,6 +323,7 @@ export class YeomanUI { genImageUrl = await datauri.promise(path.join(genPackagePath, YeomanUI.YEOMAN_PNG)); } catch (error) { genImageUrl = defaultImage.default; + this.logger.debug(error); } const genMessage = _.get(packageJson, "description", YeomanUI.defaultMessage); diff --git a/backend/src/youi-log.ts b/backend/src/youi-log.ts index b384bd0f1..84b741977 100644 --- a/backend/src/youi-log.ts +++ b/backend/src/youi-log.ts @@ -6,5 +6,5 @@ export interface YouiLog { conflict(str: string): void; identical(str: string): void; skip(str: string): void; - showLog():boolean; + showOutput():boolean; } diff --git a/backend/tests/extension.spec.ts b/backend/tests/extension.spec.ts index 9b2806f2f..88a686d54 100644 --- a/backend/tests/extension.spec.ts +++ b/backend/tests/extension.spec.ts @@ -14,6 +14,7 @@ const testVscode = { }; mockVscode(testVscode, "src/extension.ts"); import * as extension from "../src/extension"; +import * as loggerWrapper from "../src/logger/logger-wrapper"; describe('extension unit test', () => { let sandbox: any; @@ -21,6 +22,7 @@ describe('extension unit test', () => { let windowMock: any; let yeomanUiPanelMock: any; let yeomanUiMock: any; + let loggerWrapperMock: any; before(() => { sandbox = sinon.createSandbox(); @@ -34,8 +36,9 @@ describe('extension unit test', () => { commandsMock = sandbox.mock(testVscode.commands); windowMock = sandbox.mock(testVscode.window); yeomanUiPanelMock = sandbox.mock(extension.YeomanUIPanel); - _.set(extension.YeomanUIPanel, "currentPanel.yeomanui", {toggleLog: () => {}}); + _.set(extension.YeomanUIPanel, "currentPanel.yeomanui", {toggleOutput: () => {}}); yeomanUiMock = sandbox.mock(extension.YeomanUIPanel.currentPanel.yeomanui); + loggerWrapperMock = sandbox.mock(loggerWrapper); }); afterEach(() => { @@ -43,12 +46,14 @@ describe('extension unit test', () => { windowMock.verify(); yeomanUiPanelMock.verify(); yeomanUiMock.verify(); + loggerWrapperMock.verify(); }); describe('activate', () => { let testContext: any; beforeEach(() => { testContext = { subscriptions: [], extensionPath: "testExtensionpath" }; + loggerWrapperMock.expects("createExtensionLoggerAndSubscribeToLogSettingsChanges"); }); it("commands registration", () => { @@ -57,7 +62,7 @@ describe('extension unit test', () => { // tslint:disable-next-line: no-unused-expression expect( _.get(oRegisteredCommands, "loadYeomanUI")).to.be.not.undefined; // tslint:disable-next-line: no-unused-expression - expect(_.get(oRegisteredCommands, "yeomanUI.toggleLog")).to.be.not.undefined; + expect(_.get(oRegisteredCommands, "yeomanUI.toggleOutput")).to.be.not.undefined; }); it("execution loadYeomanUI command", () => { @@ -67,11 +72,11 @@ describe('extension unit test', () => { loadYeomanUICommand(); }); - it("execution yeomanui.toggleLog command", () => { + it("execution yeomanui.toggleOutput command", () => { extension.activate(testContext); - const yeomanUIToggleLogCommand = _.get(oRegisteredCommands, "yeomanUI.toggleLog"); - yeomanUiMock.expects("toggleLog"); - yeomanUIToggleLogCommand(); + const yeomanUIToggleOutputCommand = _.get(oRegisteredCommands, "yeomanUI.toggleOutput"); + yeomanUiMock.expects("toggleOutput"); + yeomanUIToggleOutputCommand(); }); }); }); \ No newline at end of file diff --git a/backend/tests/yeomanui.spec.ts b/backend/tests/yeomanui.spec.ts index a58ead4b5..be82ff1f4 100644 --- a/backend/tests/yeomanui.spec.ts +++ b/backend/tests/yeomanui.spec.ts @@ -10,6 +10,7 @@ import * as yeomanEnv from "yeoman-environment"; import { YouiLog } from "../src/youi-log"; import { IMethod, IPromiseCallbacks, IRpc } from "@sap-devx/webview-rpc/out.ext/rpc-common"; import { GeneratorType, GeneratorFilter } from "../src/filter"; +import { IChildLogger } from "@vscode-logging/logger"; describe('yeomanui unit test', () => { let sandbox: any; @@ -79,14 +80,16 @@ describe('yeomanui unit test', () => { public skip(): void { return; } - public showLog(): boolean { + public showOutput(): boolean { return false; } } + const testLogger = {debug: () => {}, error: () => {}} as IChildLogger; + const rpc = new TestRpc(); const logger = new TestLog(); - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); before(() => { sandbox = sinon.createSandbox(); @@ -402,15 +405,15 @@ describe('yeomanui unit test', () => { }); }); - it("toggleLog", () => { - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); - const res = yeomanUi.toggleLog(); + it("toggleOutput", () => { + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); + const res = yeomanUi.toggleOutput(); // tslint:disable-next-line: no-unused-expression expect(res).to.be.false; }); it("logMessage", () => { - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); const res = yeomanUi.logMessage("message"); // tslint:disable-next-line: no-unused-expression expect(res).to.be.undefined; @@ -418,7 +421,7 @@ describe('yeomanui unit test', () => { describe("setGenInstall", () => { it("install method not exist", () => { - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); let gen: any = {} as Generator; yeomanUi["setGenInstall"](gen); // tslint:disable-next-line: no-unused-expression @@ -426,7 +429,7 @@ describe('yeomanui unit test', () => { }); it("install method exists", () => { - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); class GenTest { public install(): any{ return "original_install"; @@ -446,7 +449,7 @@ describe('yeomanui unit test', () => { }); describe("getEnv", () => { - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger, testLogger); const testEnv = yeomanUi["getEnv"](); const nodemodules = YeomanUI["NODE_MODULES"]; testEnv.getNpmPaths = (localOnly: boolean = false): string[] => { diff --git a/frontend/jest.config.js b/frontend/jest.config.js index afe107f73..275c974f9 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.js @@ -36,9 +36,9 @@ module.exports = { coverageThreshold: { "global": { "branches": 86.4, - "functions": 98, - "lines": 95, - "statements": 95 + "functions": 98.4, + "lines": 95.3, + "statements": 95.3 } } } diff --git a/frontend/package.json b/frontend/package.json index 1ae5f488d..27914b6f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "yeoman-ui-frontend", "displayName": "Yeoman UI Frontend", - "version": "0.3.0", + "version": "0.3.6", "publisher": "SAP", "license": "Apache 2.0", "description": "Frontend for the yeoman UI framework", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 185d86e10..d36d06f32 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -167,15 +167,15 @@ export default { const relevantQuestionsToUpdate = _.slice(questions, questionIndex) let showBusy = true - const that = this + const finished = relevantQuestionsToUpdate.reduce((p, question) => { - return p.then(() => that.updateQuestion(question)).catch(error => { + return p.then(() => this.updateQuestion(question)).catch(error => { // eslint-disable-next-line no-console console.error(error); - // TODO: add information to log in case a question failed and there is a list/rawlist question without selected value }) }, Promise.resolve()); + const that = this setTimeout(() => { if (showBusy) { that.showBusyIndicator = true @@ -248,8 +248,9 @@ export default { if (this.resolve) { try { this.resolve(this.currentPrompt.answers); - } catch (e) { - this.reject(e); + } catch (error) { + this.rpc.invoke("logError", [error]); + this.reject(error); return; } } diff --git a/frontend/src/components/Header.vue b/frontend/src/components/Header.vue index fb5f07f1b..c02bbbb43 100644 --- a/frontend/src/components/Header.vue +++ b/frontend/src/components/Header.vue @@ -3,7 +3,7 @@ {{selectedGeneratorHeader}} - + mdi-console @@ -15,8 +15,8 @@ export default { name: "Header", props: ["selectedGeneratorHeader", "stepName", "isInVsCode", "rpc"], methods: { - collapseLog() { - this.rpc.invoke("toggleLog", [{}]); + collapseOutput() { + this.rpc.invoke("toggleOutput", [{}]); this.$emit("parentShowConsole"); } } diff --git a/frontend/tests/App.spec.js b/frontend/tests/App.spec.js index ea2a0128c..05309bc0a 100644 --- a/frontend/tests/App.spec.js +++ b/frontend/tests/App.spec.js @@ -356,6 +356,9 @@ describe('App.vue', () => { const rejectSpy = jest.spyOn(wrapper.vm, 'reject') wrapper.vm.promptIndex = 1 wrapper.vm.prompts = [{}, {}] + wrapper.vm.rpc = { + invoke: () => new Promise(resolve => setTimeout(() => resolve(), 300)) + } wrapper.vm.next() diff --git a/frontend/tests/components/Header.spec.js b/frontend/tests/components/Header.spec.js index 232d79507..2b54186bf 100644 --- a/frontend/tests/components/Header.spec.js +++ b/frontend/tests/components/Header.spec.js @@ -1,7 +1,6 @@ import {initComponent, destroy} from '../Utils' import Header from '../../src/components/Header.vue' //There are issues of importing vuetify components https://github.com/vuejs/vue-cli/issues/1584 -// import { VBtn } from 'vuetify/lib' import _ from 'lodash' let wrapper @@ -29,7 +28,7 @@ describe('Header.vue', () => { expect(wrapper.find('v-icon-stub').text()).toBe("mdi-console") }) - test('click triggers collapseLog method', async () => { + test('click triggers collapseOutput method', async () => { const rpcInvokeMockFunction = jest.fn() wrapper = initComponent(Header, { rpc: {