diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 0126054..19227ba 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -368,7 +368,14 @@ ], "configuration": { "type": "object", - "title": "%feature_previews_title%" + "title": "%feature_previews_title%", + "properties": { + "salesforcedx-vscode-core.show-cli-success-msg": { + "type": ["boolean"], + "default": true, + "description": "%show_cli_success_msg_description%" + } + } } } } diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 4f8bcf4..8201b48 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -44,5 +44,7 @@ "SFDX: Execute Anonymous Apex with Currently Selected Text", "feature_previews_title": "Salesforce Feature Previews", "force_project_create_text": "SFDX: Create Project", - "force_apex_trigger_create_text": "SFDX: Create Apex Trigger" + "force_apex_trigger_create_text": "SFDX: Create Apex Trigger", + "show_cli_success_msg_description": + "Specifies whether status messages for Salesforce CLI commands run using the VS Code command palette will appear as pop-up information messages (true) or as status bar messages (false)." } diff --git a/packages/salesforcedx-vscode-core/src/constants.ts b/packages/salesforcedx-vscode-core/src/constants.ts index 43cee1e..b68ae3f 100644 --- a/packages/salesforcedx-vscode-core/src/constants.ts +++ b/packages/salesforcedx-vscode-core/src/constants.ts @@ -13,3 +13,6 @@ export const TERMINAL_INTEGRATED_ENVS = [ 'terminal.integrated.env.linux', 'terminal.integrated.env.windows' ]; +export const SFDX_CORE_CONFIGURATION_NAME = 'salesforcedx-vscode-core'; +export const STATUS_BAR_MSG_TIMEOUT_MS = 5000; +export const SHOW_CLI_SUCCESS_INFO_MSG = 'show-cli-success-msg'; diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index b3c8071..071ff01 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -28,6 +28,7 @@ export const messages = { notification_canceled_execution_text: '%s was canceled', notification_unsuccessful_execution_text: '%s failed to run', notification_show_button_text: 'Show', + notification_show_in_status_bar_button_text: 'Show Only in Status Bar', predicates_no_folder_opened_text: 'No folder opened. Open a Salesforce DX project in VS Code.', diff --git a/packages/salesforcedx-vscode-core/src/notifications/notificationService.ts b/packages/salesforcedx-vscode-core/src/notifications/notificationService.ts index fb31535..4a2f0b7 100644 --- a/packages/salesforcedx-vscode-core/src/notifications/notificationService.ts +++ b/packages/salesforcedx-vscode-core/src/notifications/notificationService.ts @@ -9,7 +9,9 @@ import { CommandExecution } from '@salesforce/salesforcedx-utils-vscode/out/src/ import { Observable } from 'rxjs/Observable'; import * as vscode from 'vscode'; import { DEFAULT_SFDX_CHANNEL } from '../channels'; +import { STATUS_BAR_MSG_TIMEOUT_MS } from '../constants'; import { nls } from '../messages'; +import { sfdxCoreSettings } from '../settings'; /** * A centralized location for all notification functionalities. @@ -76,13 +78,28 @@ export class NotificationService { ) { observable.subscribe(async data => { if (data != undefined && data.toString() === '0') { - const showButtonText = nls.localize('notification_show_button_text'); - const selection = await this.showInformationMessage( - nls.localize('notification_successful_execution_text', executionName), - showButtonText + const message = nls.localize( + 'notification_successful_execution_text', + executionName ); - if (selection && selection === showButtonText) { - this.channel.show(); + if (sfdxCoreSettings.getShowCLISuccessMsg()) { + const showButtonText = nls.localize('notification_show_button_text'); + const showOnlyStatusBarButtonText = nls.localize( + 'notification_show_in_status_bar_button_text' + ); + const selection = await this.showInformationMessage( + message, + showButtonText, + showOnlyStatusBarButtonText + ); + if (selection && selection === showButtonText) { + this.channel.show(); + } + if (selection && selection === showOnlyStatusBarButtonText) { + sfdxCoreSettings.updateShowCLISuccessMsg(false); + } + } else { + vscode.window.setStatusBarMessage(message, STATUS_BAR_MSG_TIMEOUT_MS); } } else { if (cancellationToken && cancellationToken.isCancellationRequested) { diff --git a/packages/salesforcedx-vscode-core/src/settings/index.ts b/packages/salesforcedx-vscode-core/src/settings/index.ts new file mode 100644 index 0000000..18666d5 --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/settings/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2017, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { SfdxCoreSettings } from './sfdxCoreSettings'; + +export const sfdxCoreSettings = SfdxCoreSettings.getInstance(); diff --git a/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts new file mode 100644 index 0000000..424e54a --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import * as vscode from 'vscode'; +import { + SFDX_CORE_CONFIGURATION_NAME, + SHOW_CLI_SUCCESS_INFO_MSG +} from '../constants'; +/** + * A centralized location for interacting with sfdx-core settings. + */ +export class SfdxCoreSettings { + private static instance: SfdxCoreSettings; + + public static getInstance() { + if (!SfdxCoreSettings.instance) { + SfdxCoreSettings.instance = new SfdxCoreSettings(); + } + return SfdxCoreSettings.instance; + } + + /** + * Get the configuration for a sfdx-core + */ + public getConfiguration(): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration(SFDX_CORE_CONFIGURATION_NAME); + } + + public getShowCLISuccessMsg(): boolean { + return this.getConfigValue(SHOW_CLI_SUCCESS_INFO_MSG, true); + } + + public async updateShowCLISuccessMsg(value: boolean) { + await this.setConfigValue(SHOW_CLI_SUCCESS_INFO_MSG, value); + } + + private getConfigValue(key: string, defaultValue: T): T { + return this.getConfiguration().get(key, defaultValue); + } + + private async setConfigValue(key: string, value: any) { + await this.getConfiguration().update(key, value); + } +} diff --git a/packages/salesforcedx-vscode-core/test/notifications/index.test.ts b/packages/salesforcedx-vscode-core/test/notifications/index.test.ts index fabc514..f792885 100644 --- a/packages/salesforcedx-vscode-core/test/notifications/index.test.ts +++ b/packages/salesforcedx-vscode-core/test/notifications/index.test.ts @@ -11,8 +11,12 @@ import { CancellationTokenSource, window } from 'vscode'; import { DEFAULT_SFDX_CHANNEL } from '../../src/channels/channelService'; import { nls } from '../../src/messages'; import { NotificationService } from '../../src/notifications/notificationService'; +import { SfdxCoreSettings } from '../../src/settings/sfdxCoreSettings'; const SHOW_BUTTON_TEXT = nls.localize('notification_show_button_text'); +const SHOW_ONLY_STATUS_BAR_BUTTON_TEXT = nls.localize( + 'notification_show_in_status_bar_button_text' +); // tslint:disable:no-empty describe('Notifications', () => { @@ -20,6 +24,8 @@ describe('Notifications', () => { let mShowWarningMessage: SinonStub; let mShowErrorMessage: SinonStub; let mShow: SinonStub; + let mStatusBar: SinonStub; + let settings: SinonStub; beforeEach(() => { mShow = stub(DEFAULT_SFDX_CHANNEL, 'show'); @@ -32,6 +38,12 @@ describe('Notifications', () => { mShowErrorMessage = stub(window, 'showErrorMessage').returns( Promise.resolve(null) ); + mStatusBar = stub(window, 'setStatusBarMessage').returns( + Promise.resolve(null) + ); + settings = stub(SfdxCoreSettings.prototype, 'getShowCLISuccessMsg').returns( + true + ); }); afterEach(() => { @@ -39,6 +51,8 @@ describe('Notifications', () => { mShowInformation.restore(); mShowWarningMessage.restore(); mShowErrorMessage.restore(); + mStatusBar.restore(); + settings.restore(); }); it('Should notify successful execution', async () => { @@ -52,10 +66,12 @@ describe('Notifications', () => { assert.calledWith( mShowInformation, 'mock command successfully ran', - SHOW_BUTTON_TEXT + SHOW_BUTTON_TEXT, + SHOW_ONLY_STATUS_BAR_BUTTON_TEXT ); assert.notCalled(mShowWarningMessage); assert.notCalled(mShowErrorMessage); + assert.notCalled(mStatusBar); }); it('Should notify successful and show channel as requested', async () => { @@ -74,10 +90,61 @@ describe('Notifications', () => { assert.calledWith( mShowInformation, 'mock command successfully ran', - SHOW_BUTTON_TEXT + SHOW_BUTTON_TEXT, + SHOW_ONLY_STATUS_BAR_BUTTON_TEXT ); assert.notCalled(mShowWarningMessage); assert.notCalled(mShowErrorMessage); + assert.notCalled(mStatusBar); + }); + + it('Should notify successful in status bar based on user configuration', async () => { + // Set user configuration to show success messages in status bar. + settings.restore(); + settings = stub(SfdxCoreSettings.prototype, 'getShowCLISuccessMsg').returns( + false + ); + + const observable = new ReplaySubject(); + observable.next(0); + + const notificationService = NotificationService.getInstance(); + await notificationService.reportExecutionStatus('mock command', observable); + + assert.notCalled(mShow); + assert.notCalled(mShowInformation); + assert.notCalled(mShowWarningMessage); + assert.notCalled(mShowErrorMessage); + assert.calledOnce(mStatusBar); + }); + + it('Should update setting to hide future information messages', async () => { + // For this particular test, we need it to return a different value + mShowInformation.restore(); + mShowInformation = stub(window, 'showInformationMessage').returns( + Promise.resolve(SHOW_ONLY_STATUS_BAR_BUTTON_TEXT) + ); + const updateSetting = stub( + SfdxCoreSettings.prototype, + 'updateShowCLISuccessMsg' + ); + const observable = new ReplaySubject(); + observable.next(0); + + const notificationService = NotificationService.getInstance(); + await notificationService.reportExecutionStatus('mock command', observable); + + assert.calledWith( + mShowInformation, + 'mock command successfully ran', + SHOW_BUTTON_TEXT, + SHOW_ONLY_STATUS_BAR_BUTTON_TEXT + ); + assert.notCalled(mShow); + assert.notCalled(mShowWarningMessage); + assert.notCalled(mShowErrorMessage); + assert.notCalled(mStatusBar); + assert.calledOnce(updateSetting); }); it('Should notify cancellation', async () => {