From 165d4999be645d681ce6aa62fde310e3a6a8a7dc Mon Sep 17 00:00:00 2001 From: Pierre Vanobbergen <37369168+PierreVanobbergen@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:12:58 +0100 Subject: [PATCH 1/2] feat: change status bar color when in production org setting Create a setting to allow the status bar to change color when the default org is a production one fix #5517 --- .../salesforcedx-vscode-core/package.json | 10 ++ .../package.nls.ja.json | 2 + .../salesforcedx-vscode-core/package.nls.json | 2 + .../salesforcedx-vscode-core/src/constants.ts | 3 + .../salesforcedx-vscode-core/src/index.ts | 17 ++- .../src/settings/colorWarningWhenProdOrg.ts | 70 ++++++++++++ .../src/settings/sfdxCoreSettings.ts | 15 ++- .../src/util/authInfo.ts | 17 ++- .../settings/colorWarningWhenProdOrg.test.ts | 108 ++++++++++++++++++ scripts/setup-jest.ts | 13 ++- 10 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts create mode 100644 packages/salesforcedx-vscode-core/test/jest/settings/colorWarningWhenProdOrg.test.ts diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 9a23e1909c..a87015867d 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -983,6 +983,16 @@ "default": "fatal", "description": "%sf_log_level_description%" }, + "salesforcedx-vscode-core.color-warning-production-org": { + "type": "boolean", + "default": false, + "description": "%color_warning_production_org_description%" + }, + "salesforcedx-vscode-core.color-warning-production-org-color": { + "type": "string", + "default": "#FF0000", + "description": "%color_warning_production_org_color_description%" + }, "salesforcedx-vscode-core.telemetry-tag": { "type": "string", "default": null, diff --git a/packages/salesforcedx-vscode-core/package.nls.ja.json b/packages/salesforcedx-vscode-core/package.nls.ja.json index e73f47df6b..93a7393d3b 100644 --- a/packages/salesforcedx-vscode-core/package.nls.ja.json +++ b/packages/salesforcedx-vscode-core/package.nls.ja.json @@ -4,6 +4,8 @@ "apex_generate_class_text": "SFDX: Apex クラスを作成", "apex_generate_trigger_text": "SFDX: Apex トリガを作成", "cancel_sfdx_command_text": "SFDX: アクティブなコマンドをキャンセル", + "color_warning_production_org_color_description": "本番環境で作業しているときのステータスバーの色を指定します。Color-warning-production-org」がfalseに設定されている場合、この設定は無視されます。", + "color_warning_production_org_description": "ステータスバーの色を変えて、本番環境で作業していることを示す。", "config_list_text": "SFDX: すべての設定変数を一覧表示", "config_set_org_text": "SFDX: デフォルトの組織を設定", "conflict_detect_open_file": "ファイルを開く", diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 14d2d79335..e0e925971e 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -5,6 +5,8 @@ "apex_generate_trigger_text": "SFDX: Create Apex Trigger", "apex_generate_unit_test_class_text": "SFDX: Create Apex Unit Test Class", "cancel_sfdx_command_text": "SFDX: Cancel Active Command", + "color_warning_production_org_color_description": "Specifies the color of the status bar when you're working in a production org. This setting is ignored if \"Color-warning-production-org\" is set to false.", + "color_warning_production_org_description": "Change the status bar color to indicate that you're working in a production org", "config_list_text": "SFDX: List All Config Variables", "config_set_org_text": "SFDX: Set a Default Org", "conflict_detect_open_file": "Open File", diff --git a/packages/salesforcedx-vscode-core/src/constants.ts b/packages/salesforcedx-vscode-core/src/constants.ts index 5812495b70..0151ef1ffd 100644 --- a/packages/salesforcedx-vscode-core/src/constants.ts +++ b/packages/salesforcedx-vscode-core/src/constants.ts @@ -41,6 +41,9 @@ export const PREFER_DEPLOY_ON_SAVE_ENABLED = 'push-or-deploy-on-save.preferDeployOnSave'; export const PUSH_OR_DEPLOY_ON_SAVE_IGNORE_CONFLICTS = 'push-or-deploy-on-save.ignoreConflictsOnPush'; +export const COLOR_WARNING_WHEN_PRODUCTION_ORG = 'color-warning-production-org'; +export const COLOR_WARNING_WHEN_PRODUCTION_ORG_COLOR = + 'color-warning-production-org-color'; export const RETRIEVE_TEST_CODE_COVERAGE = 'retrieve-test-code-coverage'; export const SHOW_CLI_SUCCESS_INFO_MSG = 'show-cli-success-msg'; export const TELEMETRY_ENABLED = 'telemetry.enabled'; diff --git a/packages/salesforcedx-vscode-core/src/index.ts b/packages/salesforcedx-vscode-core/src/index.ts index dce6f79daf..caaa6fa78a 100644 --- a/packages/salesforcedx-vscode-core/src/index.ts +++ b/packages/salesforcedx-vscode-core/src/index.ts @@ -115,6 +115,7 @@ import { OrgList } from './orgPicker'; import { isSalesforceProjectOpened } from './predicates'; import { SalesforceProjectConfig } from './salesforceProject'; import { registerPushOrDeployOnSave, sfdxCoreSettings } from './settings'; +import { colorWhenProductionOrg } from './settings/colorWarningWhenProdOrg'; import { taskViewService } from './statuses'; import { showTelemetryMessage, telemetryService } from './telemetry'; import { MetricsReporter } from './telemetry/MetricsReporter'; @@ -489,7 +490,9 @@ const registerInternalDevCommands = ( ); }; -const registerOrgPickerCommands = (orgListParam: OrgList): vscode.Disposable => { +const registerOrgPickerCommands = ( + orgListParam: OrgList +): vscode.Disposable => { const setDefaultOrgCmd = vscode.commands.registerCommand( 'sfdx.set.default.org', () => orgListParam.setDefaultOrg() @@ -537,7 +540,10 @@ const setupOrgBrowser = async ( }; export const activate = async (extensionContext: vscode.ExtensionContext) => { - const activateTracker = new ActivationTracker(extensionContext, telemetryService); + const activateTracker = new ActivationTracker( + extensionContext, + telemetryService + ); const rootWorkspacePath = getRootWorkspacePath(); // Switch to the project directory so that the main @salesforce // node libraries work correctly. @salesforce/core, @@ -592,7 +598,9 @@ export const activate = async (extensionContext: vscode.ExtensionContext) => { telemetryService }; - telemetryService.sendExtensionActivationEvent(activateTracker.activationInfo.startActivateHrTime); + telemetryService.sendExtensionActivationEvent( + activateTracker.activationInfo.startActivateHrTime + ); MetricsReporter.extensionPackStatus(); console.log('SFDX CLI Extension Activated (internal dev mode)'); return internalApi; @@ -701,6 +709,7 @@ const initializeProject = async (extensionContext: vscode.ExtensionContext) => { // Register file watcher for push or deploy on save await registerPushOrDeployOnSave(); + await colorWhenProductionOrg(); await decorators.showOrg(); await setUpOrgExpirationWatcher(newOrgList); @@ -711,7 +720,7 @@ const initializeProject = async (extensionContext: vscode.ExtensionContext) => { } }; -export const deactivate = async (): Promise => { +export const deactivate = async (): Promise => { console.log('SFDX CLI Extension Deactivated'); // Send metric data. diff --git a/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts b/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts new file mode 100644 index 0000000000..0a718fc209 --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, 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 { WorkspaceContext } from '../context'; +import { getDefaultUsernameOrAlias } from '../context/workspaceOrgType'; +import { OrgAuthInfo } from '../util'; +import { SfdxCoreSettings } from './sfdxCoreSettings'; + +/** + * Change the color of the status bar when the default org is a production org + * @returns {Promise} - returns true if the color was changed + */ +export const colorWhenProductionOrg = async () => { + const baseColorStatusBar = new vscode.ThemeColor('statusBar.background'); + + const colorWHenProductionOrgHandler = async () => { + const usernameOrAlias = await getDefaultUsernameOrAlias(); + const settings = SfdxCoreSettings.getInstance(); + + const activated = settings.getColorWarningWhenProductionOrg(); + const colorForProdOrg = settings.getColorWarningWhenProductionOrgColor(); + + if (!usernameOrAlias || !activated) { + return false; + } + const isProdOrg = await OrgAuthInfo.isAProductionOrg( + await OrgAuthInfo.getUsername(usernameOrAlias) + ); + const colorCustomizations = { + 'statusBar.background': isProdOrg ? colorForProdOrg : baseColorStatusBar + }; + + // Save the configuration to the global settings file + await vscode.workspace + .getConfiguration() + .update( + 'workbench.colorCustomizations', + colorCustomizations, + vscode.ConfigurationTarget.Global + ); + return true; + }; + + WorkspaceContext.getInstance().onOrgChange(() => + colorWHenProductionOrgHandler() + ); + + /** + * Change the color of the status bar when the window state changes, avoiding the status bar color on others vscode windows + */ + vscode.window.onDidChangeWindowState(async e => { + if (e.focused) { + await colorWHenProductionOrgHandler(); + } else { + await vscode.workspace + .getConfiguration() + .update( + 'workbench.colorCustomizations', + {}, + vscode.ConfigurationTarget.Global + ); + } + }); + return await colorWHenProductionOrgHandler(); +}; diff --git a/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts index cdcde2e8a5..048a6e5eb4 100644 --- a/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts +++ b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts @@ -21,7 +21,9 @@ import { PUSH_OR_DEPLOY_ON_SAVE_IGNORE_CONFLICTS, RETRIEVE_TEST_CODE_COVERAGE, SHOW_CLI_SUCCESS_INFO_MSG, - TELEMETRY_ENABLED + TELEMETRY_ENABLED, + COLOR_WARNING_WHEN_PRODUCTION_ORG, + COLOR_WARNING_WHEN_PRODUCTION_ORG_COLOR } from '../constants'; /** * A centralized location for interacting with sfdx-core settings. @@ -113,6 +115,17 @@ export class SfdxCoreSettings { ); } + public getColorWarningWhenProductionOrg(): boolean { + return this.getConfigValue(COLOR_WARNING_WHEN_PRODUCTION_ORG, false); + } + + public getColorWarningWhenProductionOrgColor(): string { + return this.getConfigValue( + COLOR_WARNING_WHEN_PRODUCTION_ORG_COLOR, + '#ff0000' + ); + } + private getConfigValue(key: string, defaultValue: T): T { return this.getConfiguration().get(key, defaultValue); } diff --git a/packages/salesforcedx-vscode-core/src/util/authInfo.ts b/packages/salesforcedx-vscode-core/src/util/authInfo.ts index 098c36a8f6..02c8618d4a 100644 --- a/packages/salesforcedx-vscode-core/src/util/authInfo.ts +++ b/packages/salesforcedx-vscode-core/src/util/authInfo.ts @@ -18,9 +18,8 @@ import { telemetryService } from '../telemetry'; export class OrgAuthInfo { public static async getDevHubUsername() { - const defaultDevHubUsernameOrAlias = await OrgAuthInfo.getDefaultDevHubUsernameOrAlias( - false - ); + const defaultDevHubUsernameOrAlias = + await OrgAuthInfo.getDefaultDevHubUsernameOrAlias(false); let defaultDevHubUsername: string | undefined; if (defaultDevHubUsernameOrAlias) { defaultDevHubUsername = await OrgAuthInfo.getUsername( @@ -34,7 +33,8 @@ export class OrgAuthInfo { enableWarning: boolean ): Promise { try { - const defaultUsernameOrAlias = await ConfigUtil.getDefaultUsernameOrAlias(); + const defaultUsernameOrAlias = + await ConfigUtil.getDefaultUsernameOrAlias(); if (!defaultUsernameOrAlias) { displayMessage( nls.localize('error_no_default_username'), @@ -114,6 +114,15 @@ export class OrgAuthInfo { ); } + public static async isAProductionOrg(username: string): Promise { + const authInfo = await AuthInfo.create({ username }); + const authInfoFields = authInfo.getFields(); + return Promise.resolve( + !authInfoFields.isSandbox && + !authInfoFields.instanceUrl?.includes('sandbox.my.salesforce.com') + ); + } + public static async getConnection( usernameOrAlias?: string ): Promise { diff --git a/packages/salesforcedx-vscode-core/test/jest/settings/colorWarningWhenProdOrg.test.ts b/packages/salesforcedx-vscode-core/test/jest/settings/colorWarningWhenProdOrg.test.ts new file mode 100644 index 0000000000..5ea71d1c7d --- /dev/null +++ b/packages/salesforcedx-vscode-core/test/jest/settings/colorWarningWhenProdOrg.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, 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 { WorkspaceContext } from '../../../src/context'; +import { getDefaultUsernameOrAlias } from '../../../src/context/workspaceOrgType'; +import { colorWhenProductionOrg } from '../../../src/settings/colorWarningWhenProdOrg'; +import { SfdxCoreSettings } from '../../../src/settings/sfdxCoreSettings'; +import { OrgAuthInfo } from '../../../src/util'; + +// Mocking getDefaultUsernameOrAlias function +jest.mock('../../../src/context/workspaceOrgType', () => ({ + getDefaultUsernameOrAlias: jest.fn() +})); + +// Mocking OrgAuthInfo class +jest.mock('../../../src/util', () => ({ + OrgAuthInfo: { + getUsername: jest.fn(), + isAProductionOrg: jest.fn() + } +})); + +// Mocking SfdxCoreSettings class +jest.mock('../../../src/settings/sfdxCoreSettings', () => ({ + SfdxCoreSettings: { + getInstance: jest.fn() + } +})); + +const mockWorkspaceContextInstance = { + onOrgChange: jest.fn() +}; + +// Mock the WorkspaceContext class +jest.mock('../../../src/context', () => ({ + WorkspaceContext: jest.fn().mockImplementation(() => ({ + getInstance: jest.fn().mockReturnValue(mockWorkspaceContextInstance) + })) +})); + +describe('colorWhenProductionOrg', () => { + let mockConfiguration: any; + let mockOnOrgChange: any; + + beforeEach(() => { + mockConfiguration = { + update: jest.fn() + }; + + mockOnOrgChange = jest.fn(); + (SfdxCoreSettings.getInstance as jest.Mock).mockReturnValue({ + getColorWarningWhenProductionOrg: jest.fn(() => true), + getColorWarningWhenProductionOrgColor: jest.fn() + }); + + WorkspaceContext.getInstance = jest.fn().mockReturnValue({ + onOrgChange: mockOnOrgChange + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should update status bar color when production org is detected', async () => { + (getDefaultUsernameOrAlias as jest.Mock).mockResolvedValue('testUsername'); + (OrgAuthInfo.getUsername as jest.Mock).mockResolvedValue('testUsername'); + (OrgAuthInfo.isAProductionOrg as jest.Mock).mockResolvedValue(true); + + const updated = await colorWhenProductionOrg(); + + expect(updated).toBe(true); + }); + + it('should not update status bar color when no username or alias is found', async () => { + (getDefaultUsernameOrAlias as jest.Mock).mockResolvedValue(null); + + const updated = await colorWhenProductionOrg(); + + expect(updated).toBe(false); + }); + + it('should not update status bar color when color warning for production org is not activated', async () => { + (getDefaultUsernameOrAlias as jest.Mock).mockResolvedValue('testUsername'); + (OrgAuthInfo.getUsername as jest.Mock).mockResolvedValue('testUsername'); + (SfdxCoreSettings.getInstance as jest.Mock).mockReturnValue({ + getColorWarningWhenProductionOrg: jest.fn(() => false), + getColorWarningWhenProductionOrgColor: jest.fn(() => undefined) + }); + + const updated = await colorWhenProductionOrg(); + expect(updated).toBe(false); + }); + + it('should not update status bar color when org is not a production org', async () => { + (getDefaultUsernameOrAlias as jest.Mock).mockResolvedValue('testUsername'); + (OrgAuthInfo.getUsername as jest.Mock).mockResolvedValue('testUsername'); + (OrgAuthInfo.isAProductionOrg as jest.Mock).mockResolvedValue(false); + + const updated = await colorWhenProductionOrg(); + + expect(updated).toBe(true); + }); +}); diff --git a/scripts/setup-jest.ts b/scripts/setup-jest.ts index 5fed95bce0..2ee153c396 100644 --- a/scripts/setup-jest.ts +++ b/scripts/setup-jest.ts @@ -104,7 +104,8 @@ const getMockVSCode = () => { show: jest.fn() }, createStatusBarItem: jest.fn(), - createTextEditorDecorationType: jest.fn() + createTextEditorDecorationType: jest.fn(), + onDidChangeWindowState: jest.fn() }, workspace: { getConfiguration: () => { @@ -176,7 +177,15 @@ const getMockVSCode = () => { public constructor() {} }, LanguageStatusSeverity, - TreeItemCollapsibleState + TreeItemCollapsibleState, + ThemeColor: class { + public constructor(id: string) {} + }, + ConfigurationTarget: { + Global: 1, + Workspace: 2, + WorkspaceFolder: 3 + } }; }; From 257fbfb092cdb8dd46cde6f113405af930cba429 Mon Sep 17 00:00:00 2001 From: Pierre Vanobbergen <37369168+PierreVanobbergen@users.noreply.github.com> Date: Mon, 25 Mar 2024 09:31:05 +0100 Subject: [PATCH 2/2] refactor: fix typo on colorWHenProductionOrgHandler --- .../src/settings/colorWarningWhenProdOrg.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts b/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts index 0a718fc209..6dddaa6f54 100644 --- a/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts +++ b/packages/salesforcedx-vscode-core/src/settings/colorWarningWhenProdOrg.ts @@ -18,7 +18,7 @@ import { SfdxCoreSettings } from './sfdxCoreSettings'; export const colorWhenProductionOrg = async () => { const baseColorStatusBar = new vscode.ThemeColor('statusBar.background'); - const colorWHenProductionOrgHandler = async () => { + const colorWhenProductionOrgHandler = async () => { const usernameOrAlias = await getDefaultUsernameOrAlias(); const settings = SfdxCoreSettings.getInstance(); @@ -47,7 +47,7 @@ export const colorWhenProductionOrg = async () => { }; WorkspaceContext.getInstance().onOrgChange(() => - colorWHenProductionOrgHandler() + colorWhenProductionOrgHandler() ); /** @@ -55,7 +55,7 @@ export const colorWhenProductionOrg = async () => { */ vscode.window.onDidChangeWindowState(async e => { if (e.focused) { - await colorWHenProductionOrgHandler(); + await colorWhenProductionOrgHandler(); } else { await vscode.workspace .getConfiguration() @@ -66,5 +66,5 @@ export const colorWhenProductionOrg = async () => { ); } }); - return await colorWHenProductionOrgHandler(); + return await colorWhenProductionOrgHandler(); };