diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index 93cfb458a5..61f7073cca 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ### Bug fixes +- Fixed an issue where the `ZoweVsCodeExtension.updateCredentials` method could remove credentials from session when input prompt was cancelled. [#3018](https://github.com/zowe/zowe-explorer-vscode/pull/3018) + ## `2.17.0` ### New features and enhancements diff --git a/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts index a35c24a907..eb07c71dc7 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts @@ -555,10 +555,11 @@ describe("ZoweVsCodeExtension", () => { }); it("should do nothing if user input is cancelled", async () => { + const fakeProfile = { user: "fakeUser", password: "fakePass" }; const mockUpdateProperty = jest.fn(); jest.spyOn(ZoweVsCodeExtension as any, "profilesCache", "get").mockReturnValue({ getLoadedProfConfig: jest.fn().mockReturnValue({ - profile: {}, + profile: fakeProfile, }), getProfileInfo: jest.fn().mockReturnValue({ isSecured: jest.fn().mockReturnValue(true), @@ -568,19 +569,22 @@ describe("ZoweVsCodeExtension", () => { }); const showInputBoxSpy = jest.spyOn(Gui, "showInputBox").mockResolvedValueOnce(undefined); const profileLoaded = await ZoweVsCodeExtension.updateCredentials( - promptCredsOptions, + { ...promptCredsOptions, rePrompt: true }, undefined as unknown as ZoweExplorerApi.IApiRegisterClient ); expect(profileLoaded).toBeUndefined(); expect(showInputBoxSpy).toHaveBeenCalledTimes(1); expect(mockUpdateProperty).toHaveBeenCalledTimes(0); + expect(fakeProfile.user).toBeDefined(); + expect(fakeProfile.password).toBeDefined(); }); it("should do nothing if password input is cancelled", async () => { + const fakeProfile = { user: "fakeUser", password: "fakePass" }; const mockUpdateProperty = jest.fn(); jest.spyOn(ZoweVsCodeExtension as any, "profilesCache", "get").mockReturnValue({ getLoadedProfConfig: jest.fn().mockReturnValue({ - profile: {}, + profile: fakeProfile, }), getProfileInfo: jest.fn().mockReturnValue({ isSecured: jest.fn().mockReturnValue(true), @@ -590,12 +594,14 @@ describe("ZoweVsCodeExtension", () => { }); const showInputBoxSpy = jest.spyOn(Gui, "showInputBox").mockResolvedValueOnce("fakeUser").mockResolvedValueOnce(undefined); const profileLoaded = await ZoweVsCodeExtension.updateCredentials( - promptCredsOptions, + { ...promptCredsOptions, rePrompt: true }, undefined as unknown as ZoweExplorerApi.IApiRegisterClient ); expect(profileLoaded).toBeUndefined(); expect(showInputBoxSpy).toHaveBeenCalledTimes(2); expect(mockUpdateProperty).toHaveBeenCalledTimes(0); + expect(fakeProfile.user).toBeDefined(); + expect(fakeProfile.password).toBeDefined(); }); it("should do nothing if profile and sessionName args are not provided", async () => { diff --git a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts index 4fa009b7b8..a1cc9337e7 100644 --- a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts +++ b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts @@ -339,7 +339,6 @@ export class ZoweVsCodeExtension { value: newUser, ...(options.userInputBoxOptions ?? {}), }); - options.session.user = newUser; } if (!newUser || (options.rePrompt && newUser === "")) { return undefined; @@ -355,13 +354,14 @@ export class ZoweVsCodeExtension { value: newPass, ...(options.passwordInputBoxOptions ?? {}), }); - options.session.password = newPass; } if (!newPass || (options.rePrompt && newPass === "")) { return undefined; } - return [newUser.trim(), newPass.trim()]; + options.session.user = newUser.trim(); + options.session.password = newPass.trim(); + return [options.session.user, options.session.password]; } private static async promptCertificate(options: IPromptCertificateOptions): Promise { diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 629c754d46..a729645852 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### Bug fixes +- Fixed issue where creating a new team configuration file could cause Zowe Explorer to crash, resulting in all sessions disappearing from trees. [#2906](https://github.com/zowe/zowe-explorer-vscode/issues/2906) + ## `2.17.0` ### New features and enhancements diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts index 97ac15d78e..85acd51746 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts @@ -109,9 +109,7 @@ describe("Test src/shared/extension", () => { { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: true }, { spy: jest.spyOn(Profiles, "getInstance"), arg: [], ret: { refresh: jest.fn() } }, - { spy: jest.spyOn(refreshActions, "refreshAll"), arg: ["ds"] }, - { spy: jest.spyOn(refreshActions, "refreshAll"), arg: ["uss"] }, - { spy: jest.spyOn(refreshActions, "refreshAll"), arg: ["job"] }, + { spy: jest.spyOn(refreshActions, "refreshAll"), arg: [] }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_HIDE], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_SECURE_CREDENTIALS_ENABLED], ret: false }, ], @@ -312,55 +310,69 @@ describe("Test src/shared/extension", () => { describe("watchConfigProfile", () => { let context: any; + let watcherPromise: any; const spyReadFile = jest.fn().mockReturnValue("test"); - const spyExecuteCommand = jest.fn(); + const mockEmitter = jest.fn(); const watcher: any = { - onDidCreate: jest.fn().mockImplementation((fun) => fun()), - onDidDelete: jest.fn().mockImplementation((fun) => fun()), - onDidChange: jest.fn().mockImplementation((fun) => fun("uri")), + onDidCreate: jest.fn(), + onDidDelete: jest.fn(), + onDidChange: jest.fn(), }; beforeEach(() => { context = { subscriptions: [] }; jest.clearAllMocks(); - Object.defineProperty(globals, "ISTHEIA", { value: false, configurable: true }); - Object.defineProperty(vscode.workspace, "createFileSystemWatcher", { value: () => watcher, configurable: true }); Object.defineProperty(vscode.workspace, "workspaceFolders", { value: [{ uri: { fsPath: "fsPath" } }], configurable: true }); - Object.defineProperty(vscode.commands, "executeCommand", { value: spyExecuteCommand, configurable: true }); Object.defineProperty(vscode.workspace, "fs", { value: { readFile: spyReadFile }, configurable: true }); Object.defineProperty(globals, "SAVED_PROFILE_CONTENTS", { value: "test", configurable: true }); + jest.spyOn(vscode.workspace, "createFileSystemWatcher").mockReturnValue(watcher); + jest.spyOn(ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter, "fire").mockImplementation(mockEmitter); }); afterAll(() => { jest.restoreAllMocks(); }); - it("should be able to trigger all listeners", async () => { - const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(jest.fn()); - jest.spyOn(ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter, "fire").mockImplementation(); - await sharedExtension.watchConfigProfile(context, { ds: "ds", uss: "uss", job: "job" } as any); - expect(spyExecuteCommand).toHaveBeenCalledWith("zowe.extRefresh"); + it("should be able to trigger onDidCreate listener", async () => { + const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); + watcher.onDidCreate.mockImplementationOnce((fun) => (watcherPromise = fun())); + sharedExtension.watchConfigProfile(context); + await watcherPromise; expect(context.subscriptions).toContain(watcher); - expect(spyReadFile).toHaveBeenCalledWith("uri"); - expect(spyRefreshAll).not.toHaveBeenCalled(); + expect(spyRefreshAll).toHaveBeenCalledTimes(1); + expect(mockEmitter).toHaveBeenCalledTimes(1); + }); - spyReadFile.mockReturnValue("other"); - await sharedExtension.watchConfigProfile(context, { ds: "ds", uss: "uss", job: "job" } as any); - expect(spyRefreshAll).toHaveBeenCalled(); + it("should be able to trigger onDidDelete listener", async () => { + const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); + watcher.onDidDelete.mockImplementationOnce((fun) => (watcherPromise = fun())); + sharedExtension.watchConfigProfile(context); + await watcherPromise; + expect(context.subscriptions).toContain(watcher); + expect(spyRefreshAll).toHaveBeenCalledTimes(1); + expect(mockEmitter).toHaveBeenCalledTimes(1); }); - it("should be able to refresh zowe explorer on theia after updating config file", async () => { - Object.defineProperty(globals, "ISTHEIA", { value: true, configurable: true }); - jest.spyOn(ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter, "fire").mockImplementation(); - const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(jest.fn()); - await sharedExtension.watchConfigProfile(context, { ds: "ds", uss: "uss", job: "job" } as any); + it("should be able to trigger onDidChange listener", async () => { + const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + sharedExtension.watchConfigProfile(context); + await watcherPromise; expect(context.subscriptions).toContain(watcher); expect(spyReadFile).toHaveBeenCalledWith("uri"); - expect(spyRefreshAll).toHaveBeenCalled(); + expect(spyRefreshAll).not.toHaveBeenCalled(); + expect(mockEmitter).not.toHaveBeenCalled(); + }); - spyReadFile.mockReturnValue("other"); - await sharedExtension.watchConfigProfile(context, { ds: "ds", uss: "uss", job: "job" } as any); - expect(spyRefreshAll).toHaveBeenCalled(); - expect(spyExecuteCommand).toHaveBeenCalledWith("zowe.extRefresh"); + it("should be able to trigger onDidChange listener with changes", async () => { + const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); + spyReadFile.mockReturnValueOnce("other"); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + sharedExtension.watchConfigProfile(context); + await watcherPromise; + expect(context.subscriptions).toContain(watcher); + expect(spyReadFile).toHaveBeenCalledWith("uri"); + expect(spyRefreshAll).toHaveBeenCalledTimes(1); + expect(mockEmitter).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/refresh.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/refresh.unit.test.ts index 963921d13b..d1d72f9090 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/refresh.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/refresh.unit.test.ts @@ -17,6 +17,7 @@ import { createInstanceOfProfile, createIProfile, createISessionWithoutCredentials, + createTreeProviders, createTreeView, } from "../../../__mocks__/mockCreators/shared"; import { createFavoriteUSSNode, createUSSNode, createUSSTree } from "../../../__mocks__/mockCreators/uss"; @@ -27,6 +28,8 @@ import * as globals from "../../../src/globals"; import * as sessUtils from "../../../src/utils/SessionUtils"; import { SettingsConfig } from "../../../src/utils/SettingsConfig"; import { ZoweLogger } from "../../../src/utils/LoggerUtils"; +import { TreeProviders } from "../../../src/shared/TreeProviders"; +import { TreeViewUtils } from "../../../src/utils/TreeViewUtils"; function createGlobalMocks() { const globalMocks = { @@ -157,4 +160,21 @@ describe("Refresh Unit Tests - Function refreshAll", () => { await expect(refreshActions.refreshAll(blockMocks.testDatasetTree)).resolves.not.toThrow(); spy.mockClear(); }); + it("should refresh all tree providers and update session nodes", async () => { + await createGlobalMocks(); + jest.spyOn(TreeProviders, "providers", "get").mockReturnValue(createTreeProviders()); + const removedProfNames = new Set(); + const addedProfTypes = new Set(); + const removeSessionSpy = jest + .spyOn(sessUtils, "removeSession") + .mockImplementation(async (treeProvider, profileName) => removedProfNames.add(profileName) as any); + const addDefaultSessionSpy = jest + .spyOn(TreeViewUtils, "addDefaultSession") + .mockImplementation(async (treeProvider, profileType) => addedProfTypes.add(profileType) as any); + await refreshActions.refreshAll(); + expect(removeSessionSpy).toHaveBeenCalledTimes(6); + expect([...removedProfNames]).toEqual(["zosmf2", "zosmf"]); + expect(addDefaultSessionSpy).toHaveBeenCalledTimes(3); + expect([...addedProfTypes]).toEqual(["zosmf"]); + }); }); diff --git a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts index acb020a126..9d2c64cd98 100644 --- a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts +++ b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts @@ -25,6 +25,7 @@ import { ZoweLogger } from "../utils/LoggerUtils"; import { TreeProviders } from "../shared/TreeProviders"; import { IZoweProviders } from "../shared/init"; import { resetValidationSettings } from "../shared/actions"; +import { TreeViewUtils } from "../utils/TreeViewUtils"; // Set up localization nls.config({ @@ -376,12 +377,6 @@ export class ZoweTreeProvider { } } } - if (treeProvider.mSessionNodes.length === 1) { - try { - await treeProvider.addSingleSession(Profiles.getInstance().getDefaultProfile(profileType)); - } catch (error) { - ZoweLogger.warn(error); - } - } + await TreeViewUtils.addDefaultSession(treeProvider, profileType); } } diff --git a/packages/zowe-explorer/src/extension.ts b/packages/zowe-explorer/src/extension.ts index b872ac9109..0b5eb2ebda 100644 --- a/packages/zowe-explorer/src/extension.ts +++ b/packages/zowe-explorer/src/extension.ts @@ -62,7 +62,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { // If the log folder location has been changed, update current log folder preference - if (e.affectsConfiguration(globals.SETTINGS_LOGS_FOLDER_PATH)) { + if (e.affectsConfiguration(globals.SETTINGS_LOGS_FOLDER_PATH) || e.affectsConfiguration(globals.LOGGER_SETTINGS)) { await ZoweLogger.initializeZoweLogger(context); } // If the temp folder location has been changed, update current temp folder preference @@ -125,20 +125,14 @@ export function registerCommonCommands(context: vscode.ExtensionContext, provide } if (e.affectsConfiguration(globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION)) { await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); - await refreshActions.refreshAll(providers.ds); - await refreshActions.refreshAll(providers.uss); - await refreshActions.refreshAll(providers.job); + await refreshActions.refreshAll(); } if (e.affectsConfiguration(globals.SETTINGS_TEMP_FOLDER_HIDE)) { await hideTempFolder(getZoweDir()); } - if (e.affectsConfiguration(globals.SETTINGS_SECURE_CREDENTIALS_ENABLED)) { await vscode.commands.executeCommand("zowe.updateSecureCredentials"); } - if (e.affectsConfiguration(globals.LOGGER_SETTINGS)) { - await vscode.commands.executeCommand("zowe.extRefresh"); - } }) ); @@ -232,7 +226,7 @@ export function registerCredentialManager(context: vscode.ExtensionContext): voi ); } -export function watchConfigProfile(context: vscode.ExtensionContext, providers: IZoweProviders): void { +export function watchConfigProfile(context: vscode.ExtensionContext): void { ZoweLogger.trace("shared.init.watchConfigProfile called."); const watchers: vscode.FileSystemWatcher[] = []; watchers.push(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(getZoweDir(), "{zowe.config,zowe.config.user}.json"))); @@ -250,12 +244,12 @@ export function watchConfigProfile(context: vscode.ExtensionContext, providers: watchers.forEach((watcher) => { watcher.onDidCreate(async () => { ZoweLogger.info(localize("watchConfigProfile.create", "Team config file created, refreshing Zowe Explorer.")); - await vscode.commands.executeCommand("zowe.extRefresh"); + await refreshActions.refreshAll(); ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter.fire(EventTypes.CREATE); }); watcher.onDidDelete(async () => { ZoweLogger.info(localize("watchConfigProfile.delete", "Team config file deleted, refreshing Zowe Explorer.")); - await vscode.commands.executeCommand("zowe.extRefresh"); + await refreshActions.refreshAll(); ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter.fire(EventTypes.DELETE); }); watcher.onDidChange(async (uri: vscode.Uri) => { @@ -265,13 +259,8 @@ export function watchConfigProfile(context: vscode.ExtensionContext, providers: return; } globals.setSavedProfileContents(newProfileContents); - await refreshActions.refreshAll(providers.ds); - await refreshActions.refreshAll(providers.uss); - await refreshActions.refreshAll(providers.job); + await refreshActions.refreshAll(); ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter.fire(EventTypes.UPDATE); - if (globals.ISTHEIA) { - await vscode.commands.executeCommand("zowe.extRefresh"); - } }); }); } diff --git a/packages/zowe-explorer/src/shared/refresh.ts b/packages/zowe-explorer/src/shared/refresh.ts index 1616fa339e..a1cb548b66 100644 --- a/packages/zowe-explorer/src/shared/refresh.ts +++ b/packages/zowe-explorer/src/shared/refresh.ts @@ -17,6 +17,8 @@ import { returnIconState } from "./actions"; import * as contextually from "../shared/context"; import { removeSession } from "../utils/SessionUtils"; import { ZoweLogger } from "../utils/LoggerUtils"; +import { TreeProviders } from "./TreeProviders"; +import { TreeViewUtils } from "../utils/TreeViewUtils"; /** * View (DATA SETS, JOBS, USS) refresh button @@ -24,8 +26,14 @@ import { ZoweLogger } from "../utils/LoggerUtils"; * * @param {IZoweTree} treeProvider */ -export async function refreshAll(treeProvider: IZoweTree): Promise { +export async function refreshAll(treeProvider?: IZoweTree): Promise { ZoweLogger.trace("refresh.refreshAll called."); + if (treeProvider == null) { + for (const provider of Object.values(TreeProviders.providers)) { + await this.refreshAll(provider); + } + return; + } await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); for (const sessNode of treeProvider.mSessionNodes) { const profiles = await Profiles.getInstance().fetchAllProfiles(); @@ -40,5 +48,8 @@ export async function refreshAll(treeProvider: IZoweTree): Promis await removeSession(treeProvider, sessNode.label.toString().trim()); } } + for (const profType of ZoweExplorerApiRegister.getInstance().registeredApiTypes()) { + await TreeViewUtils.addDefaultSession(treeProvider, profType); + } treeProvider.refresh(); } diff --git a/packages/zowe-explorer/src/utils/LoggerUtils.ts b/packages/zowe-explorer/src/utils/LoggerUtils.ts index d73e593990..cae2f6916d 100644 --- a/packages/zowe-explorer/src/utils/LoggerUtils.ts +++ b/packages/zowe-explorer/src/utils/LoggerUtils.ts @@ -83,7 +83,7 @@ export class ZoweLogger { } private static async initVscLogger(logFileLocation: string): Promise { - this.zeOutputChannel = Gui.createOutputChannel(localize("zoweExplorer.outputchannel.title", "Zowe Explorer")); + this.zeOutputChannel ??= Gui.createOutputChannel(localize("zoweExplorer.outputchannel.title", "Zowe Explorer")); this.writeVscLoggerInfo(logFileLocation); this.info(localize("initialize.log.info", "Initialized logger for Zowe Explorer")); await this.compareCliLogSetting(); diff --git a/packages/zowe-explorer/src/utils/TreeViewUtils.ts b/packages/zowe-explorer/src/utils/TreeViewUtils.ts index 652e528fe5..784d3e7145 100644 --- a/packages/zowe-explorer/src/utils/TreeViewUtils.ts +++ b/packages/zowe-explorer/src/utils/TreeViewUtils.ts @@ -14,6 +14,7 @@ import { ZoweLogger } from "./LoggerUtils"; import { TreeViewExpansionEvent } from "vscode"; import { getIconByNode } from "../generators/icons"; import { ZoweTreeProvider } from "../abstract/ZoweTreeProvider"; +import { Profiles } from "../Profiles"; export class TreeViewUtils { /** @@ -53,4 +54,14 @@ export class TreeViewUtils { } }; } + + public static async addDefaultSession(treeProvider: IZoweTree, profileType: string): Promise { + if (treeProvider.mSessionNodes.length === 1) { + try { + await treeProvider.addSingleSession(Profiles.getInstance().getDefaultProfile(profileType)); + } catch (error) { + ZoweLogger.warn(error); + } + } + } }