Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: change status bar color when in production org setting #5519

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo colorWHenProductionOrgHandler -> colorWhenProductionOrgHandler

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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix typo

);

/**
* 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here as well

} else {
await vscode.workspace
.getConfiguration()
.update(
'workbench.colorCustomizations',
{},
vscode.ConfigurationTarget.Global
);
}
});
return await colorWHenProductionOrgHandler();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And one more here

};
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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peternhale is there an affirmative case for "this is a production org"? I'm not seeing that in these docs, so it's looking like Production would be the fallback case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@randi274 Yes. production should be the fallback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! @interfaceconjurer and I were chatting about this detail today, it might be worth y'all discussing the possibility of "Production" being overly inclusive.

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