Skip to content

Commit

Permalink
feat: change status bar color when in production org setting
Browse files Browse the repository at this point in the history
Create a setting to allow the status bar to change color when the default org is a production one

fix forcedotcom#5517
  • Loading branch information
PierreVanobbergen committed Mar 22, 2024
1 parent 8164e8d commit 165d499
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 11 deletions.
10 changes: 10 additions & 0 deletions packages/salesforcedx-vscode-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/salesforcedx-vscode-core/package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "ファイルを開く",
Expand Down
2 changes: 2 additions & 0 deletions packages/salesforcedx-vscode-core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions packages/salesforcedx-vscode-core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
17 changes: 13 additions & 4 deletions packages/salesforcedx-vscode-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -711,7 +720,7 @@ const initializeProject = async (extensionContext: vscode.ExtensionContext) => {
}
};

export const deactivate = async (): Promise<void> => {
export const deactivate = async (): Promise<void> => {
console.log('SFDX CLI Extension Deactivated');

// Send metric data.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean>} - 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();
};
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<T>(key: string, defaultValue: T): T {
return this.getConfiguration().get<T>(key, defaultValue);
}
Expand Down
17 changes: 13 additions & 4 deletions packages/salesforcedx-vscode-core/src/util/authInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -34,7 +33,8 @@ export class OrgAuthInfo {
enableWarning: boolean
): Promise<string | undefined> {
try {
const defaultUsernameOrAlias = await ConfigUtil.getDefaultUsernameOrAlias();
const defaultUsernameOrAlias =
await ConfigUtil.getDefaultUsernameOrAlias();
if (!defaultUsernameOrAlias) {
displayMessage(
nls.localize('error_no_default_username'),
Expand Down Expand Up @@ -114,6 +114,15 @@ export class OrgAuthInfo {
);
}

public static async isAProductionOrg(username: string): Promise<boolean> {
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<Connection> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
13 changes: 11 additions & 2 deletions scripts/setup-jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ const getMockVSCode = () => {
show: jest.fn()
},
createStatusBarItem: jest.fn(),
createTextEditorDecorationType: jest.fn()
createTextEditorDecorationType: jest.fn(),
onDidChangeWindowState: jest.fn()
},
workspace: {
getConfiguration: () => {
Expand Down Expand Up @@ -176,7 +177,15 @@ const getMockVSCode = () => {
public constructor() {}
},
LanguageStatusSeverity,
TreeItemCollapsibleState
TreeItemCollapsibleState,
ThemeColor: class {
public constructor(id: string) {}
},
ConfigurationTarget: {
Global: 1,
Workspace: 2,
WorkspaceFolder: 3
}
};
};

Expand Down

0 comments on commit 165d499

Please sign in to comment.