diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 0000000000..cab92eb79e --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,3 @@ +allowRemediationCommits: + individual: true + thirdParty: true \ No newline at end of file diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 6a14c7be8e..a82c4f52c8 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -37,7 +37,7 @@ jobs: - name: Use Node.js LTS uses: actions/setup-node@v4 with: - node-version: '18.x' + node-version: '20.x' # use pnpm version 8 until Octorelease supports pnpm@9 - name: Install pnpm and Lerna diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8bcada0004..a3fe643f3d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - name: Use Node.js LTS uses: actions/setup-node@v4 with: - node-version: 18 + node-version: lts/* - run: | npm install -g pnpm@8 diff --git a/packages/eslint-plugin-zowe-explorer/CHANGELOG.md b/packages/eslint-plugin-zowe-explorer/CHANGELOG.md index 52b496f5f7..cd217f3d1d 100644 --- a/packages/eslint-plugin-zowe-explorer/CHANGELOG.md +++ b/packages/eslint-plugin-zowe-explorer/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the "eslint-plugin-zowe-explorer" package will be documen ### Bug fixes +## `3.0.1` + ## `3.0.0` ### New features and enhancements diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index 199c8c475f..7e0091fa24 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -8,6 +8,12 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ### Bug fixes +## `3.0.1` + +### Bug fixes + +- Updated the `ZoweTreeNode.setProfileToChoice` function so that it propagates profile changes to its child nodes. [#3150](https://github.com/zowe/zowe-explorer-vscode/issues/3150) + ## `3.0.0` ### New features and enhancements diff --git a/packages/zowe-explorer-api/__tests__/__unit__/tree/ZoweTreeNode.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/tree/ZoweTreeNode.unit.test.ts index fe3ca62227..c8a0ae5618 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/tree/ZoweTreeNode.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/tree/ZoweTreeNode.unit.test.ts @@ -94,6 +94,7 @@ describe("ZoweTreeNode", () => { it("setProfileToChoice should update profile for associated FSProvider entry", () => { const node = makeNode("test", vscode.TreeItemCollapsibleState.None, undefined); + node.resourceUri = vscode.Uri.file(__dirname); const fsEntry = { metadata: { profile: { name: "oldProfile" }, @@ -108,4 +109,23 @@ describe("ZoweTreeNode", () => { expect(node.getProfileName()).toBe("newProfile"); expect(fsEntry.metadata.profile.name).toBe("newProfile"); }); + + it("setProfileToChoice should update child nodes with the new profile", () => { + const node = makeNode("test", vscode.TreeItemCollapsibleState.Expanded, undefined); + const nodeChild = makeNode("child", vscode.TreeItemCollapsibleState.None, undefined); + node.children = [nodeChild as any]; + const setProfileToChoiceChildMock = jest.spyOn(nodeChild, "setProfileToChoice").mockImplementation(); + const fsEntry = { + metadata: { + profile: { name: "oldProfile" }, + }, + }; + const mockNewProfile = { name: "newProfile" } as unknown as imperative.IProfileLoaded; + const mockProvider = { + lookup: jest.fn().mockReturnValue(fsEntry), + } as unknown as BaseProvider; + node.setProfileToChoice(mockNewProfile, mockProvider); + expect(node.getProfileName()).toBe("newProfile"); + expect(setProfileToChoiceChildMock).toHaveBeenCalledWith(mockNewProfile, mockProvider); + }); }); diff --git a/packages/zowe-explorer-api/src/fs/types/abstract.ts b/packages/zowe-explorer-api/src/fs/types/abstract.ts index f29305bc6b..b26c64ba59 100644 --- a/packages/zowe-explorer-api/src/fs/types/abstract.ts +++ b/packages/zowe-explorer-api/src/fs/types/abstract.ts @@ -68,7 +68,6 @@ export type ConflictData = { export interface IFileSystemEntry extends vscode.FileStat { name: string; metadata: EntryMetadata; - type: vscode.FileType; wasAccessed: boolean; data?: Uint8Array; } diff --git a/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts b/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts index d65612211e..392ad02020 100644 --- a/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts +++ b/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts @@ -181,6 +181,11 @@ export interface IZoweDatasetTreeNode extends IZoweTreeNode { */ encoding?: string; + /** + * Use Dataset-specific tree node for children. + */ + children?: IZoweDatasetTreeNode[]; + /** * Retrieves child nodes of this IZoweDatasetTreeNode * @@ -296,6 +301,11 @@ export interface IZoweUSSTreeNode extends IZoweTreeNode { */ onUpdateEmitter?: vscode.EventEmitter; + /** + * Use USS-specific tree node for children. + */ + children?: IZoweUSSTreeNode[]; + /** * Event that fires whenever an existing node is updated. */ diff --git a/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts b/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts index 7c03c4c2c2..bdcc061f99 100644 --- a/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts +++ b/packages/zowe-explorer-api/src/tree/ZoweTreeNode.ts @@ -107,9 +107,14 @@ export class ZoweTreeNode extends vscode.TreeItem { // Don't reassign profile, we want to keep object reference shared across nodes this.profile.profile = aProfile.profile; } - const fsEntry = fsProvider?.lookup(this.resourceUri, true); - if (fsEntry != null) { - fsEntry.metadata.profile = aProfile; + if (this.resourceUri != null) { + const fsEntry = fsProvider?.lookup(this.resourceUri, true); + if (fsEntry != null) { + fsEntry.metadata.profile = aProfile; + } + } + for (const child of this.children) { + (child as unknown as ZoweTreeNode).setProfileToChoice(aProfile, fsProvider); } } /** diff --git a/packages/zowe-explorer-ftp-extension/CHANGELOG.md b/packages/zowe-explorer-ftp-extension/CHANGELOG.md index ea613f1e86..a86c7ff5d9 100644 --- a/packages/zowe-explorer-ftp-extension/CHANGELOG.md +++ b/packages/zowe-explorer-ftp-extension/CHANGELOG.md @@ -6,6 +6,12 @@ All notable changes to the "zowe-explorer-ftp-extension" extension will be docum ### Bug fixes +## `3.0.1` + +### Bug fixes + +- Fixed bug where the `getContents` MVS and USS APIs failed to return whenever a local file path was provided. [#3199](https://github.com/zowe/zowe-explorer-vscode/issues/3199) + ## `3.0.0` ### New features and enhancements diff --git a/packages/zowe-explorer-ftp-extension/__mocks__/mockUtils.ts b/packages/zowe-explorer-ftp-extension/__mocks__/mockUtils.ts new file mode 100644 index 0000000000..06a03e3e0a --- /dev/null +++ b/packages/zowe-explorer-ftp-extension/__mocks__/mockUtils.ts @@ -0,0 +1,13 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +// Idea is borrowed from: https://github.com/kulshekhar/ts-jest/blob/master/src/util/testing.ts +export const mocked = any>(fn: T): jest.Mock> => fn as any; diff --git a/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Mvs/ZoweExplorerFtpMvsApi.unit.test.ts b/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Mvs/ZoweExplorerFtpMvsApi.unit.test.ts index 3d90f828e8..cca7a1954f 100644 --- a/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Mvs/ZoweExplorerFtpMvsApi.unit.test.ts +++ b/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Mvs/ZoweExplorerFtpMvsApi.unit.test.ts @@ -20,6 +20,7 @@ import * as tmp from "tmp"; import { Gui, imperative } from "@zowe/zowe-explorer-api"; import * as globals from "../../../src/globals"; import { ZoweFtpExtensionError } from "../../../src/ZoweFtpExtensionError"; +import { mocked } from "../../../__mocks__/mockUtils"; // two methods to mock modules: create a __mocks__ file for zowe-explorer-api.ts and direct mock for extension.ts jest.mock("../../../__mocks__/@zowe/zowe-explorer-api.ts"); @@ -93,6 +94,7 @@ describe("FtpMvsApi", () => { expect(result.apiResponse.etag).toHaveLength(64); expect(DataSetUtils.downloadDataSet).toHaveBeenCalledTimes(1); + expect(mocked(DataSetUtils.downloadDataSet).mock.calls[0][2].localFile).toBe(localFile); expect(MvsApi.releaseConnection).toHaveBeenCalled(); expect((response._readableState.buffer.head?.data ?? response._readableState.buffer).toString()).toContain("Hello world"); diff --git a/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Uss/ZoweExplorerFtpUssApi.unit.test.ts b/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Uss/ZoweExplorerFtpUssApi.unit.test.ts index d2e5718670..e2a60713c0 100644 --- a/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Uss/ZoweExplorerFtpUssApi.unit.test.ts +++ b/packages/zowe-explorer-ftp-extension/__tests__/__unit__/Uss/ZoweExplorerFtpUssApi.unit.test.ts @@ -21,6 +21,7 @@ import * as globals from "../../../src/globals"; import { ZoweFtpExtensionError } from "../../../src/ZoweFtpExtensionError"; import * as tmp from "tmp"; import { imperative } from "@zowe/zowe-explorer-api"; +import { mocked } from "../../../__mocks__/mockUtils"; // two methods to mock modules: create a __mocks__ file for zowe-explorer-api.ts and direct mock for extension.ts jest.mock("../../../__mocks__/@zowe/zowe-explorer-api.ts"); @@ -78,6 +79,7 @@ describe("FtpUssApi", () => { expect(result.apiResponse.etag).toHaveLength(64); expect(UssUtils.downloadFile).toHaveBeenCalledTimes(1); + expect(mocked(UssUtils.downloadFile).mock.calls[0][2].localFile).toBe(localFile); expect(UssApi.releaseConnection).toHaveBeenCalled(); expect((response._readableState.buffer.head?.data ?? response._readableState.buffer).toString()).toContain("Hello world"); diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts index 7ae41a5810..a8f39a0f51 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts @@ -84,7 +84,7 @@ export class FtpMvsApi extends AbstractFtpApi implements MainframeInteraction.IM const result = this.getDefaultResponse(); const transferOptions = { encoding: options.encoding, - localFile: undefined, + localFile: options.file, transferType: CoreUtils.getBinaryTransferModeOrDefault(options.binary), }; const fileOrStreamSpecified = options.file != null || options.stream != null; diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts index e536cefdb1..4ad999d339 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts @@ -64,7 +64,7 @@ export class FtpUssApi extends AbstractFtpApi implements MainframeInteraction.IU const result = this.getDefaultResponse(); const transferOptions = { transferType: CoreUtils.getBinaryTransferModeOrDefault(options.binary), - localFile: undefined, + localFile: options.file, size: 1, }; const fileOrStreamSpecified = options.file != null || options.stream != null; diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index a9ef91d52b..3dca452496 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -10,9 +10,19 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### Bug fixes +## `3.0.1` + +### Bug fixes + - Fixed an issue where opening sequential data sets within favorited searches resulted in an error. [#3163](https://github.com/zowe/zowe-explorer-vscode/pull/3163) +- Fixed an issue where automatic file extension detection identified file types incorrectly. [#3181](https://github.com/zowe/zowe-explorer-vscode/pull/3181) - Fixed an issue where Zowe Explorer displayed a "No Zowe client configurations" prompt when a project user configuration existed but no global configuration was present. [#3168](https://github.com/zowe/zowe-explorer-vscode/issues/3168) - Fixed an issue where the `ProfilesUtils.getProfileInfo` function returned a new `ProfileInfo` instance that ignored the `ZOWE_CLI_HOME` environment variable and workspace paths. [#3168](https://github.com/zowe/zowe-explorer-vscode/issues/3168) +- Fixed an issue where the location prompt for the `Create Directory` and `Create File` USS features would appear even when a path is already set for the profile or parent folder. [#3183](https://github.com/zowe/zowe-explorer-vscode/pull/3183) +- Fixed an issue where the `Create Directory` and `Create File` features would continue processing when the first prompt was dismissed, causing an incorrect URI to be generated. [#3183](https://github.com/zowe/zowe-explorer-vscode/pull/3183) +- Fixed an issue where the `Create Directory` and `Create File` features would incorrectly handle user-specified locations with trailing slashes. [#3183](https://github.com/zowe/zowe-explorer-vscode/pull/3183) +- Fixed an issue where a 401 error could occur when opening PDS members after updating credentials within the same user session. [#3150](https://github.com/zowe/zowe-explorer-vscode/issues/3150) +- Fixed the "Edit Profile" operation to open the correct files when both global and project team configs are present. [#3125](https://github.com/zowe/zowe-explorer-vscode/issues/3125) ## `3.0.0` @@ -92,8 +102,6 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed issue where obsolete credentials persisted for PDS member nodes in Data Sets tree. [#3112](https://github.com/zowe/zowe-explorer-vscode/issues/3112) - Fixed issue where Search operation did not prompt for credentials if profile contains expired token. [#2259](https://github.com/zowe/zowe-explorer-vscode/issues/2259) - Fixed issue where inactive status was not displayed for profiles loaded from Global Config. [#3134](https://github.com/zowe/zowe-explorer-vscode/issues/3134) -- Fixed issue where switching from token-based authentication to user/password would cause an error for nested profiles. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) -- Fixed the "Edit Profile" operation to open the correct files when both global and project team configs are present. [#3125](https://github.com/zowe/zowe-explorer-vscode/issues/3125) ## `3.0.0-next.202409132122` diff --git a/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts b/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts index f53b84a82c..3e3a844d4e 100644 --- a/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts +++ b/packages/zowe-explorer/__tests__/__mocks__/@zowe/imperative.ts @@ -17,6 +17,10 @@ import * as os from "os"; import * as path from "path"; const log4js = require("log4js"); +export class EventProcessor { + public emitZoweEvent(eventName: string): void {} +} + export enum ProfLocType { OLD_PROFILE = 0, // an old-school profile TEAM_CONFIG = 1, // a team configuration diff --git a/packages/zowe-explorer/__tests__/__mocks__/mockCreators/jobs.ts b/packages/zowe-explorer/__tests__/__mocks__/mockCreators/jobs.ts index 5ff05ac1ba..5c787e51a1 100644 --- a/packages/zowe-explorer/__tests__/__mocks__/mockCreators/jobs.ts +++ b/packages/zowe-explorer/__tests__/__mocks__/mockCreators/jobs.ts @@ -160,8 +160,8 @@ export function createJobInfoNode(session: any, profile: imperative.IProfileLoad collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: session.getSessionNode(), profile, + contextOverride: Constants.INFORMATION_CONTEXT, }); - jobNode.contextValue = Constants.INFORMATION_CONTEXT; return jobNode; } diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/ZoweTreeProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/ZoweTreeProvider.unit.test.ts index 3068638a61..472ed89871 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/ZoweTreeProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/ZoweTreeProvider.unit.test.ts @@ -277,7 +277,7 @@ describe("Tree Provider unit tests, function getTreeItem", () => { }); }); -describe("Tree Provider unit tests, function getTreeItem", () => { +describe("Tree Provider unit tests, function flipState", () => { it("Testing that expand tree is executed successfully", async () => { const globalMocks = await createGlobalMocks(); const spy = jest.spyOn(ZoweLogger, "trace"); @@ -288,14 +288,17 @@ describe("Tree Provider unit tests, function getTreeItem", () => { session: globalMocks.testSession, }); folder.contextValue = Constants.USS_DIR_CONTEXT; + folder.dirty = false; // Testing flipState to open await globalMocks.testUSSTree.flipState(folder, true); expect(JSON.stringify(folder.iconPath)).toContain("folder-open.svg"); + expect(folder.dirty).toBe(false); // Testing flipState to closed await globalMocks.testUSSTree.flipState(folder, false); expect(JSON.stringify(folder.iconPath)).toContain("folder-closed.svg"); + expect(folder.dirty).toBe(true); expect(spy).toHaveBeenCalled(); spy.mockClear(); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts index c8d1301585..dcfa23e152 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts @@ -871,57 +871,6 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); }); -describe("Dataset Actions Unit Tests - Function enterPattern", () => { - afterAll(() => jest.restoreAllMocks()); - - it("Checking common dataset filter action", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocksShared(); - const node = new ZoweDatasetNode({ - label: "node", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: blockMocks.datasetSessionNode, - }); - node.pattern = "TEST"; - node.contextValue = Constants.DS_SESSION_CONTEXT; - - const mySpy = mocked(vscode.window.showInputBox).mockResolvedValue("test"); - await DatasetActions.enterPattern(node, blockMocks.testDatasetTree); - - expect(mySpy).toHaveBeenCalledWith( - expect.objectContaining({ - prompt: "Search Data Sets: use a comma to separate multiple patterns", - value: node.pattern, - }) - ); - expect(mocked(Gui.showMessage)).not.toHaveBeenCalled(); - }); - it("Checking common dataset filter failed attempt", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocksShared(); - const node = new ZoweDatasetNode({ - label: "node", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: blockMocks.datasetSessionNode, - }); - node.pattern = "TEST"; - node.contextValue = Constants.DS_SESSION_CONTEXT; - - mocked(vscode.window.showInputBox).mockResolvedValueOnce(""); - await DatasetActions.enterPattern(node, blockMocks.testDatasetTree); - - expect(mocked(Gui.showMessage)).toHaveBeenCalledWith("You must enter a pattern."); - }); - it("Checking favorite dataset filter action", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocksShared(); - const favoriteSample = new ZoweDatasetNode({ label: "[sestest]: HLQ.TEST", collapsibleState: vscode.TreeItemCollapsibleState.None }); - - await DatasetActions.enterPattern(favoriteSample, blockMocks.testDatasetTree); - expect(blockMocks.testDatasetTree.addSession).toHaveBeenCalledWith({ sessionName: "sestest" }); - }); -}); - describe("Dataset Actions Unit Tests - Function showAttributes", () => { function createBlockMocks() { const session = createISession(); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts index eb20a3e785..e217c18606 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts @@ -550,30 +550,6 @@ describe("Dataset Tree Unit Tests - Function getChildren", () => { expect(loadProfilesForFavoritesSpy).toHaveBeenCalledWith(log, favProfileNode); }); - - it("returns 'No data sets found' if there are no children", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocks(); - - mocked(Profiles.getInstance).mockReturnValue(blockMocks.profile); - mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView); - const testTree = new DatasetTree(); - testTree.mSessionNodes.push(blockMocks.datasetSessionNode); - const parent = new ZoweDatasetNode({ - label: "BRTVS99.PUBLIC", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: testTree.mSessionNodes[1], - }); - parent.dirty = true; - jest.spyOn(parent, "getChildren").mockResolvedValueOnce([]); - - const children = await testTree.getChildren(parent); - - // This function should never return undefined. - expect(children).toBeDefined(); - expect(children).toHaveLength(1); - expect(children[0].label).toBe("No data sets found"); - }); }); describe("Dataset Tree Unit Tests - Function loadProfilesForFavorites", () => { function createBlockMocks() { @@ -3340,7 +3316,6 @@ describe("Dataset Tree Unit Tests - Function applyPatternsToChildren", () => { const withProfileMock = jest.spyOn(SharedContext, "withProfile").mockImplementation((child) => String(child.contextValue)); testTree.applyPatternsToChildren(fakeChildren as any[], [{ dsn: "HLQ.PROD.PDS", member: "A*" }], fakeSessionNode as any); expect(SharedContext.isFilterFolder(fakeChildren[0])).toBe(true); - expect(fakeSessionNode.dirty).toBe(true); withProfileMock.mockRestore(); }); it("applies a closed filter folder icon to the PDS if collapsed", () => { diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts index 7f79ff5e82..8fe99b0354 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts @@ -34,6 +34,6 @@ describe("Dataset utils unit tests - function getExtension", () => { } }); it("returns null if no language was detected", () => { - expect(DatasetUtils.getExtension("TEST.DS")).toBe(null); + expect(DatasetUtils.getExtension("TEST.DS.X")).toBe(null); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/ZoweDatasetNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/ZoweDatasetNode.unit.test.ts index 05b31159f4..51a17ff69a 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/ZoweDatasetNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/ZoweDatasetNode.unit.test.ts @@ -125,7 +125,7 @@ describe("ZoweDatasetNode Unit Tests", () => { /************************************************************************************************************* * Checks that returning an unsuccessful response results in an error being thrown and caught *************************************************************************************************************/ - it("Checks that when List.dataSet/allMembers() returns an unsuccessful response, " + "it returns a label of 'No data sets found'", async () => { + it("Checks that when List.dataSet/allMembers() throws an error, it returns an empty list", async () => { Object.defineProperty(Profiles, "getInstance", { value: jest.fn(() => { return { @@ -139,8 +139,41 @@ describe("ZoweDatasetNode Unit Tests", () => { collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile: profileOne, + contextOverride: Constants.DS_SESSION_CONTEXT, + }); + rootNode.dirty = true; + const subNode = new ZoweDatasetNode({ + label: "Response Fail", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }); + jest.spyOn(zosfiles.List, "allMembers").mockRejectedValueOnce(new Error(subNode.label as string)); + // Populate node with children from previous search to ensure they are removed + subNode.children = [ + new ZoweDatasetNode({ label: "old", collapsibleState: vscode.TreeItemCollapsibleState.None, session, profile: profileOne }), + ]; + subNode.dirty = true; + const response = await subNode.getChildren(); + expect(response).toEqual([]); + }); + + it("Checks that when List.dataSet/allMembers() returns an empty response, it returns a label of 'No data sets found'", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + contextOverride: Constants.DS_SESSION_CONTEXT, }); - rootNode.contextValue = Constants.DS_SESSION_CONTEXT; rootNode.dirty = true; const subNode = new ZoweDatasetNode({ label: "Response Fail", @@ -148,7 +181,7 @@ describe("ZoweDatasetNode Unit Tests", () => { parentNode: rootNode, profile: profileOne, }); - jest.spyOn(subNode as any, "getDatasets").mockReturnValueOnce([ + jest.spyOn(subNode as any, "getDatasets").mockResolvedValueOnce([ { success: true, apiResponse: { @@ -171,19 +204,14 @@ describe("ZoweDatasetNode Unit Tests", () => { collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile: profileOne, + contextOverride: Constants.DS_SESSION_CONTEXT, }); const infoChild = new ZoweDatasetNode({ label: "Use the search button to display data sets", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: rootNode, - profile: profileOne, contextOverride: Constants.INFORMATION_CONTEXT, }); - infoChild.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; - rootNode.contextValue = Constants.DS_SESSION_CONTEXT; rootNode.dirty = false; expect(await rootNode.getChildren()).toEqual([infoChild]); }); @@ -198,19 +226,14 @@ describe("ZoweDatasetNode Unit Tests", () => { collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile: profileOne, + contextOverride: Constants.DS_SESSION_CONTEXT, }); const infoChild = new ZoweDatasetNode({ label: "Use the search button to display data sets", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: rootNode, - profile: profileOne, contextOverride: Constants.INFORMATION_CONTEXT, }); - infoChild.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; - rootNode.contextValue = Constants.DS_SESSION_CONTEXT; expect(await rootNode.getChildren()).toEqual([infoChild]); }); @@ -301,9 +324,9 @@ describe("ZoweDatasetNode Unit Tests", () => { parentNode: sessionNode, session, profile: profileOne, + contextOverride: Constants.DS_PDS_CONTEXT, }); pds.dirty = true; - pds.contextValue = Constants.DS_PDS_CONTEXT; const allMembers = jest.fn().mockReturnValueOnce({ success: true, apiResponse: { @@ -323,6 +346,48 @@ describe("ZoweDatasetNode Unit Tests", () => { getSessionNodeSpy.mockRestore(); getStatsMock.mockRestore(); }); + it("Testing what happens when response has multiple members and member pattern is set", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + + const getStatsMock = jest.spyOn(ZoweDatasetNode.prototype, "getStats").mockImplementation(); + + const sessionNode = createDatasetSessionNode(session, profileOne); + const getSessionNodeSpy = jest.spyOn(ZoweDatasetNode.prototype, "getSessionNode").mockReturnValue(sessionNode); + // Creating a rootNode + const pds = new ZoweDatasetNode({ + label: "[root]: something", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: sessionNode, + session, + profile: profileOne, + contextOverride: Constants.DS_PDS_CONTEXT, + }); + pds.dirty = true; + pds.memberPattern = "MEM*"; + const allMembers = jest.fn().mockReturnValueOnce({ + success: true, + apiResponse: { + items: [{ member: "MEMBER1" }], + returnedRows: 1, + }, + }); + jest.spyOn(DatasetFSProvider.instance, "exists").mockReturnValue(false); + jest.spyOn(DatasetFSProvider.instance, "writeFile").mockImplementation(); + jest.spyOn(DatasetFSProvider.instance, "createDirectory").mockImplementation(); + Object.defineProperty(zosfiles.List, "allMembers", { value: allMembers }); + const pdsChildren = await pds.getChildren(); + expect(pdsChildren[0].label).toEqual("MEMBER1"); + expect(pdsChildren[0].contextValue).toEqual(Constants.DS_MEMBER_CONTEXT); + expect(allMembers).toHaveBeenCalledWith(expect.any(imperative.Session), pds.label, expect.objectContaining({ pattern: pds.memberPattern })); + getSessionNodeSpy.mockRestore(); + getStatsMock.mockRestore(); + }); /************************************************************************************************************* * Profile properties have changed diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts index 0fa961cfc4..5e573cdbec 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts @@ -96,7 +96,6 @@ async function createGlobalMocks() { mockDeleteJobs: jest.fn(), mockShowInputBox: jest.fn(), mockDeleteJob: jest.fn(), - mockGetJobsByOwnerAndPrefix: jest.fn(), mockShowInformationMessage: jest.fn(), mockShowWarningMessage: jest.fn(), mockLoadNamedProfile: jest.fn(), @@ -110,7 +109,6 @@ async function createGlobalMocks() { testSessionNoCred: createISessionWithoutCredentials(), testProfile: createIProfile(), testIJob: createIJobObject(), - testIJobComplete: createIJobObject(), testJobNode: null, testSessionNode: null, mockIJobFile: createIJobFile(), @@ -237,7 +235,6 @@ async function createGlobalMocks() { Object.defineProperty(ZoweLogger, "trace", { value: jest.fn(), configurable: true }); globalMocks.testSessionNode = createJobSessionNode(globalMocks.testSession, globalMocks.testProfile); globalMocks.mockGetJob.mockReturnValue(globalMocks.testIJob); - globalMocks.mockGetJobsByOwnerAndPrefix.mockReturnValue([globalMocks.testIJob, globalMocks.testIJobComplete]); globalMocks.mockProfileInstance.editSession = jest.fn(() => globalMocks.testProfile); globalMocks.mockGetConfiguration.mockReturnValue({ persistence: true, diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/job/ZoweJobNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/job/ZoweJobNode.unit.test.ts index fa5309ff4d..54f98b38d3 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/job/ZoweJobNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/job/ZoweJobNode.unit.test.ts @@ -30,7 +30,7 @@ import { Profiles } from "../../../../src/configuration/Profiles"; import { ZoweExplorerApiRegister } from "../../../../src/extending/ZoweExplorerApiRegister"; import { ZoweLocalStorage } from "../../../../src/tools/ZoweLocalStorage"; import { JobFSProvider } from "../../../../src/trees/job/JobFSProvider"; -import { ZoweJobNode } from "../../../../src/trees/job/ZoweJobNode"; +import { ZoweJobNode, ZoweSpoolNode } from "../../../../src/trees/job/ZoweJobNode"; import { SharedContext } from "../../../../src/trees/shared/SharedContext"; import { SharedTreeProviders } from "../../../../src/trees/shared/SharedTreeProviders"; import { JobInit } from "../../../../src/trees/job/JobInit"; @@ -52,7 +52,7 @@ async function createGlobalMocks() { mockDeleteJobs: jest.fn(), mockShowInputBox: jest.fn(), mockDeleteJob: jest.fn(), - mockGetJobsByOwnerAndPrefix: jest.fn(), + mockGetJobsByParameters: jest.fn(), mockShowInformationMessage: jest.fn(), mockLoadNamedProfile: jest.fn(), mockCreateQuickPick: jest.fn(), @@ -114,6 +114,10 @@ async function createGlobalMocks() { configurable: true, }); Object.defineProperty(globalMocks.mockGetJobs, "getJob", { value: globalMocks.mockGetJob, configurable: true }); + Object.defineProperty(globalMocks.mockGetJobs, "getJobsByParameters", { + value: globalMocks.mockGetJobsByParameters, + configurable: true, + }); Object.defineProperty(zosmf.ZosmfSession, "createSessCfgFromArgs", { value: globalMocks.mockCreateSessCfgFromArgs, configurable: true, @@ -174,7 +178,7 @@ async function createGlobalMocks() { globalMocks.mockCreateSessCfgFromArgs.mockReturnValue(globalMocks.testSession); globalMocks.testSessionNode = createJobSessionNode(globalMocks.testSession, globalMocks.testProfile); globalMocks.mockGetJob.mockReturnValue(globalMocks.testIJob); - globalMocks.mockGetJobsByOwnerAndPrefix.mockReturnValue([globalMocks.testIJob, globalMocks.testIJobComplete]); + globalMocks.mockGetJobsByParameters.mockReturnValue([globalMocks.testIJob, globalMocks.testIJobComplete]); globalMocks.mockProfileInstance.editSession = jest.fn(() => globalMocks.testProfile); globalMocks.testJobNode = new ZoweJobNode({ label: "jobtest", @@ -298,17 +302,18 @@ describe("ZoweJobNode unit tests - Function onDidConfiguration", () => { }); describe("ZoweJobNode unit tests - Function getChildren", () => { - xit("Tests that getChildren returns the jobs of the session, when called on the session", async () => { + it("Tests that getChildren returns the jobs of the session, when called on the session", async () => { const globalMocks = await createGlobalMocks(); await globalMocks.testJobsProvider.addSession("fake"); + globalMocks.testJobsProvider.mSessionNodes[1].filtered = true; const jobs = await globalMocks.testJobsProvider.mSessionNodes[1].getChildren(); expect(jobs.length).toBe(2); expect(jobs[0].job.jobid).toEqual(globalMocks.testIJob.jobid); - expect(jobs[0].tooltip).toEqual("TESTJOB(JOB1234)"); + expect(jobs[0].tooltip).toEqual("TESTJOB(JOB1234) - ACTIVE"); expect(jobs[1].job.jobid).toEqual(globalMocks.testIJobComplete.jobid); - expect(jobs[1].tooltip).toEqual("TESTJOB(JOB1235) - 0"); + expect(jobs[1].tooltip).toEqual("TESTJOB(JOB1235) - sampleMember - 0"); }); it("Tests that getChildren updates existing job nodes with new statuses", async () => { @@ -379,7 +384,26 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { globalMocks.testJobNode.dirty = true; const spoolFilesAfter = await globalMocks.testJobNode.getChildren(); expect(spoolFilesAfter.length).toBe(1); - expect(spoolFilesAfter[0].label).toEqual("There are no JES spool messages to display"); + expect(spoolFilesAfter[0].label).toEqual("No spool files found"); + }); + + it("Tests that getChildren returns empty list if there is error retrieving spool files", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(ZoweExplorerApiRegister, "getJesApi").mockReturnValueOnce({ + getSpoolFiles: jest.fn().mockResolvedValue(new Error("Response Fail")), + } as any); + // Populate node with children from previous search to ensure they are removed + globalMocks.testJobNode.children = [ + new ZoweSpoolNode({ + label: "old", + collapsibleState: vscode.TreeItemCollapsibleState.None, + session: globalMocks.testSession, + profile: globalMocks.testProfile, + }), + ]; + globalMocks.testJobNode.dirty = true; + const spools = await globalMocks.testJobNode.getChildren(); + expect(spools).toEqual([]); }); it("Tests that getChildren returns the spool files if user/owner is not defined", async () => { @@ -395,19 +419,14 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { expect(spoolFiles[0].owner).toEqual("*"); }); - it("should return a new job if not owner and is a session", async () => { + it("should return placeholder node if session node expanded without search params", async () => { const globalMocks = await createGlobalMocks(); const expectedJob = new ZoweJobNode({ label: "Use the search button to display jobs", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: globalMocks.testJobNode, - profile: globalMocks.testProfile, contextOverride: Constants.INFORMATION_CONTEXT, }); - expectedJob.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; globalMocks.testJobNode._owner = null; jest.spyOn(SharedContext, "isSession").mockReturnValueOnce(true); @@ -420,13 +439,8 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { label: "No jobs found", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: globalMocks.testJobsProvider.mSessionNodes[1], + contextOverride: Constants.INFORMATION_CONTEXT, }); - job.iconPath = undefined; - job.contextValue = "information"; - job.command = { - title: "Placeholder", - command: "zowe.placeholderCommand", - }; await globalMocks.testJobsProvider.addSession("fake"); globalMocks.testJobsProvider.mSessionNodes[1].filtered = true; jest.spyOn(globalMocks.testJobsProvider.mSessionNodes[1], "getJobs").mockResolvedValue([]); @@ -434,6 +448,27 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { expect(jobs[0]).toEqual(job); }); + it("should return empty list if there is error retrieving jobs", async () => { + const globalMocks = await createGlobalMocks(); + await globalMocks.testJobsProvider.addSession("fake"); + // Populate node with children from previous search to ensure they are removed + globalMocks.testJobsProvider.mSessionNodes[1].children = [ + new ZoweJobNode({ + label: "old", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.testSession, + profile: globalMocks.testProfile, + }), + ]; + globalMocks.testJobsProvider.mSessionNodes[1].filtered = true; + globalMocks.mockGetJobsByParameters.mockRejectedValue(new Error("Response Fail")); + jest.spyOn(ZoweExplorerApiRegister, "getJesApi").mockReturnValueOnce({ + getSession: jest.fn().mockReturnValue(globalMocks.testSession), + } as any); + const jobs = await globalMocks.testJobsProvider.mSessionNodes[1].getChildren(); + expect(jobs).toEqual([]); + }); + it("To check smfid field in Jobs Tree View", async () => { const globalMocks = await createGlobalMocks(); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts index 7a54f6aaa8..600c3a11d9 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts @@ -30,6 +30,7 @@ import { Gui, imperative, ZoweScheme } from "@zowe/zowe-explorer-api"; import { MockedProperty } from "../../../__mocks__/mockUtils"; import { DatasetFSProvider } from "../../../../src/trees/dataset/DatasetFSProvider"; import { UssFSProvider } from "../../../../src/trees/uss/UssFSProvider"; +import { ZoweLogger } from "../../../../src/tools/ZoweLogger"; jest.mock("../../../../src/utils/LoggerUtils"); jest.mock("../../../../src/tools/ZoweLogger"); @@ -322,7 +323,8 @@ describe("Test src/shared/extension", () => { describe("watchConfigProfile", () => { let context: any; let watcherPromise: any; - const spyReadFile = jest.fn().mockReturnValue("test"); + const fakeUri = { fsPath: "fsPath" }; + const spyReadFile = jest.fn().mockReturnValue(Buffer.from("test")); const mockEmitter = jest.fn(); const watcher: any = { onDidCreate: jest.fn(), @@ -332,9 +334,12 @@ describe("Test src/shared/extension", () => { beforeEach(() => { context = { subscriptions: [] }; jest.clearAllMocks(); - Object.defineProperty(vscode.workspace, "workspaceFolders", { value: [{ uri: { fsPath: "fsPath" } }], configurable: true }); + Object.defineProperty(vscode.workspace, "workspaceFolders", { value: [{ uri: fakeUri }], configurable: true }); Object.defineProperty(vscode.workspace, "fs", { value: { readFile: spyReadFile }, configurable: true }); - Object.defineProperty(Constants, "SAVED_PROFILE_CONTENTS", { value: "test", configurable: true }); + Object.defineProperty(Constants, "SAVED_PROFILE_CONTENTS", { + value: new Map(Object.entries({ [fakeUri.fsPath]: Buffer.from("test") })), + configurable: true, + }); jest.spyOn(vscode.workspace, "createFileSystemWatcher").mockReturnValue(watcher); jest.spyOn(ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter, "fire").mockImplementation(mockEmitter); }); @@ -365,23 +370,23 @@ describe("Test src/shared/extension", () => { it("should be able to trigger onDidChange listener", async () => { const spyRefreshAll = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); - watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun(fakeUri))); SharedInit.watchConfigProfile(context); await watcherPromise; expect(context.subscriptions).toContain(watcher); - expect(spyReadFile).toHaveBeenCalledWith("uri"); + expect(spyReadFile).toHaveBeenCalledWith(fakeUri); expect(spyRefreshAll).not.toHaveBeenCalled(); expect(mockEmitter).not.toHaveBeenCalled(); }); it("should be able to trigger onDidChange listener with changes", async () => { const spyRefreshAll = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); - spyReadFile.mockReturnValueOnce("other"); - watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + spyReadFile.mockReturnValueOnce(Buffer.from("other")); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun(fakeUri))); SharedInit.watchConfigProfile(context); await watcherPromise; expect(context.subscriptions).toContain(watcher); - expect(spyReadFile).toHaveBeenCalledWith("uri"); + expect(spyReadFile).toHaveBeenCalledWith(fakeUri); expect(spyRefreshAll).toHaveBeenCalledTimes(1); expect(mockEmitter).toHaveBeenCalledTimes(1); }); @@ -408,7 +413,7 @@ describe("Test src/shared/extension", () => { const spyRefreshAll = jest.spyOn(SharedActions, "refreshAll").mockImplementation(jest.fn()); // Setup watchers - await SharedInit.watchConfigProfile(context); + SharedInit.watchConfigProfile(context); expect(spyWatcher).toHaveBeenCalled(); expect(spyGuiError).not.toHaveBeenCalled(); @@ -435,7 +440,7 @@ describe("Test src/shared/extension", () => { }); const spyGuiError = jest.spyOn(Gui, "errorMessage"); - await SharedInit.watchConfigProfile(context); + SharedInit.watchConfigProfile(context); expect(spyWatcher).toHaveBeenCalled(); expect(spyGuiError.mock.calls[0][0]).toContain("vault changes"); @@ -443,6 +448,39 @@ describe("Test src/shared/extension", () => { expect(spyGuiError.mock.calls[1][0]).toContain("credential manager changes"); expect(spyGuiError.mock.calls[1][0]).toContain(testError); }); + + it("should replace the function signature for EventProcessor.emitZoweEvent to set Constant.IGNORE_VAULT_CHANGE", async () => { + const emitZoweEventOverride = jest.fn(); + const emitZoweEventMock = new MockedProperty(imperative.EventProcessor.prototype, "emitZoweEvent", { + set: emitZoweEventOverride, + configurable: true, + }); + SharedInit.watchConfigProfile(context); + expect(emitZoweEventOverride).toHaveBeenCalled(); + emitZoweEventMock[Symbol.dispose](); + }); + + it("should replace the function signature for EventProcessor.emitZoweEvent to set Constant.IGNORE_VAULT_CHANGE", async () => { + const emitZoweEventOverride = jest.fn(); + const emitZoweEventMock = new MockedProperty(imperative.EventProcessor.prototype, "emitZoweEvent", { + set: emitZoweEventOverride, + configurable: true, + }); + SharedInit.watchConfigProfile(context); + expect(emitZoweEventOverride).toHaveBeenCalled(); + emitZoweEventMock[Symbol.dispose](); + }); + + it("should subscribe to the ON_VAULT_CHANGED event using EventProcessor.subscribeUser", async () => { + const subscribeUser = jest.fn(); + const getWatcherMock = jest.spyOn(imperative.EventOperator, "getWatcher").mockReturnValue({ + subscribeUser, + } as any); + + SharedInit.watchConfigProfile(context); + expect(getWatcherMock).toHaveBeenCalled(); + expect(subscribeUser).toHaveBeenCalledWith(imperative.ZoweUserEvents.ON_VAULT_CHANGED, SharedInit.onVaultChanged); + }); }); describe("initSubscribers", () => { @@ -518,4 +556,33 @@ describe("Test src/shared/extension", () => { expect(remoteLookupUssSpy).not.toHaveBeenCalled(); }); }); + + describe("emitZoweEventHook", () => { + it("sets Constants.IGNORE_VAULT_CHANGE to true if emitZoweEvent is called and calls the original function", () => { + const originalEmitZoweEvent = new MockedProperty(SharedInit, "originalEmitZoweEvent", undefined, jest.fn()); + SharedInit.emitZoweEventHook({} as any, imperative.ZoweUserEvents.ON_VAULT_CHANGED); + expect(Constants.IGNORE_VAULT_CHANGE).toBe(true); + expect(originalEmitZoweEvent.mock).toHaveBeenCalled(); + originalEmitZoweEvent[Symbol.dispose](); + }); + }); + describe("onVaultChanged", () => { + it("resets Constants.IGNORE_VAULT_CHANGE if it is true and returns early", async () => { + const infoSpy = jest.spyOn(ZoweLogger, "info"); + Constants.IGNORE_VAULT_CHANGE = true; + await SharedInit.onVaultChanged(); + expect(Constants.IGNORE_VAULT_CHANGE).toBe(false); + expect(infoSpy).not.toHaveBeenCalled(); + }); + + it("calls SharedActions.refreshAll and ProfilesUtils.readConfigFromDisk on vault change", async () => { + const loggerInfo = jest.spyOn(ZoweLogger, "info").mockImplementation(); + const readCfgFromDisk = jest.spyOn(profUtils.ProfilesUtils, "readConfigFromDisk").mockImplementation(); + const refreshAll = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); + await SharedInit.onVaultChanged(); + expect(loggerInfo).toHaveBeenCalledWith("Changes in the credential vault detected, refreshing Zowe Explorer."); + expect(readCfgFromDisk).toHaveBeenCalled(); + expect(refreshAll).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts index 3fda35525a..645f8de4d8 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts @@ -24,6 +24,7 @@ import { ZoweJobNode } from "../../../../src/trees/job/ZoweJobNode"; import { SharedUtils } from "../../../../src/trees/shared/SharedUtils"; import { ZoweUSSNode } from "../../../../src/trees/uss/ZoweUSSNode"; import { AuthUtils } from "../../../../src/utils/AuthUtils"; +import { SharedTreeProviders } from "../../../../src/trees/shared/SharedTreeProviders"; function createGlobalMocks() { const newMocks = { @@ -110,6 +111,30 @@ describe("syncSessionNode shared util function", () => { expect(sessionNode.getSession()).toEqual(expectedSession); expect(sessionNode.getProfile()).toEqual(createIProfile()); }); + it("should update session node and refresh tree node if provided", async () => { + const globalMocks = createGlobalMocks(); + // given + Object.defineProperty(globalMocks.mockProfilesCache, "loadNamedProfile", { + value: jest.fn().mockReturnValue(createIProfile()), + }); + const getChildrenSpy = jest.spyOn(sessionNode, "getChildren").mockResolvedValueOnce([]); + const refreshElementMock = jest.fn(); + jest.spyOn(SharedTreeProviders, "getProviderForNode").mockReturnValueOnce({ + refreshElement: refreshElementMock, + } as any); + const getSessionMock = jest.fn().mockReturnValue(new imperative.Session({})); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const sessionForProfile = (_profile) => + ({ + getSession: getSessionMock, + } as any); + // when + AuthUtils.syncSessionNode(sessionForProfile, sessionNode, sessionNode); + expect(getSessionMock).toHaveBeenCalled(); + expect(sessionNode.dirty).toBe(true); + expect(await getChildrenSpy).toHaveBeenCalled(); + expect(refreshElementMock).toHaveBeenCalledWith(sessionNode); + }); it("should do nothing, if there is no profile from provided node in the file system", () => { const profiles = createInstanceOfProfile(serviceProfile); profiles.loadNamedProfile = jest.fn(() => diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSActions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSActions.unit.test.ts index ad3b05e951..3cd04ee5c5 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSActions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSActions.unit.test.ts @@ -174,7 +174,7 @@ function createGlobalMocks() { return globalMocks; } -describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { +describe("USS Action Unit Tests - Function createUSSNode", () => { function createBlockMocks(globalMocks) { const newMocks = { testUSSTree: null, @@ -193,18 +193,50 @@ describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { return newMocks; } - it("Tests that only the child node is refreshed when createUSSNode() is called on a child node", async () => { + it("should prompt the user for a location if one is not set on the node", async () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); - globalMocks.mockShowInputBox.mockReturnValue("USSFolder"); - jest.spyOn(blockMocks.ussNode, "getChildren").mockResolvedValueOnce([]); - const isTopLevel = false; - jest.spyOn(SharedActions, "refreshAll"); + globalMocks.mockShowInputBox.mockResolvedValueOnce("/u/myuser/"); + globalMocks.mockShowInputBox.mockResolvedValueOnce("folderName"); + const refreshAllMock = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); + const createApiMock = jest.spyOn(blockMocks.ussApi, "create").mockImplementation(); + blockMocks.ussNode.getParent().fullPath = ""; - await USSActions.createUSSNode(blockMocks.ussNode, blockMocks.testUSSTree, "folder", isTopLevel); - expect(blockMocks.testUSSTree.refreshElement).toHaveBeenCalled(); - expect(SharedActions.refreshAll).not.toHaveBeenCalled(); + await USSActions.createUSSNode(blockMocks.ussNode.getParent(), blockMocks.testUSSTree, "directory"); + expect(createApiMock).toHaveBeenCalledWith("/u/myuser/folderName", "directory"); + expect(refreshAllMock).toHaveBeenCalled(); + createApiMock.mockRestore(); + }); + + it("returns early if a location was never provided", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); + + globalMocks.mockShowInputBox.mockResolvedValueOnce(undefined); + const createApiMock = jest.spyOn(blockMocks.ussApi, "create").mockImplementation(); + blockMocks.ussNode.getParent().fullPath = ""; + + await USSActions.createUSSNode(blockMocks.ussNode.getParent(), blockMocks.testUSSTree, "directory"); + expect(createApiMock).not.toHaveBeenCalled(); + createApiMock.mockRestore(); + }); + + it("handles trailing slashes in the location", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); + + globalMocks.mockShowInputBox.mockResolvedValueOnce("/u/myuser/aDir/"); + globalMocks.mockShowInputBox.mockResolvedValueOnce("testFile.txt"); + const createApiMock = jest.spyOn(blockMocks.ussApi, "create").mockImplementation(); + const refreshAllMock = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); + blockMocks.ussNode.getParent().fullPath = ""; + + await USSActions.createUSSNode(blockMocks.ussNode.getParent(), blockMocks.testUSSTree, "file"); + expect(createApiMock).toHaveBeenCalledWith("/u/myuser/aDir/testFile.txt", "file"); + expect(refreshAllMock).toHaveBeenCalled(); + createApiMock.mockRestore(); + refreshAllMock.mockRestore(); }); it("Tests if createUSSNode is executed successfully with Unverified profile", async () => { @@ -214,20 +246,24 @@ describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { Object.defineProperty(Profiles, "getInstance", { value: jest.fn(() => { return { - checkCurrentProfile: blockMocks.mockCheckCurrentProfile.mockReturnValueOnce({ + checkCurrentProfile: blockMocks.mockCheckCurrentProfile.mockResolvedValueOnce({ name: globalMocks.testProfile.name, status: "unverified", }), + loadNamedProfile: globalMocks.mockLoadNamedProfile, validProfile: Validation.ValidationType.UNVERIFIED, }; }), }); - globalMocks.showQuickPick.mockResolvedValueOnce("File"); globalMocks.mockShowInputBox.mockReturnValueOnce("USSFolder"); + const refreshAllMock = jest.spyOn(SharedActions, "refreshAll").mockImplementation(); + const createApiMock = jest.spyOn(blockMocks.ussApi, "create").mockImplementation(); - await USSActions.createUSSNodeDialog(blockMocks.ussNode.getParent(), blockMocks.testUSSTree); + await USSActions.createUSSNode(blockMocks.ussNode.getParent(), blockMocks.testUSSTree, "directory"); + expect(refreshAllMock).toHaveBeenCalled(); + expect(createApiMock).toHaveBeenCalled(); expect(blockMocks.testUSSTree.refreshElement).not.toHaveBeenCalled(); - expect(globalMocks.showErrorMessage.mock.calls.length).toBe(0); + createApiMock.mockRestore(); }); it("Tests that createUSSNode does not execute if node name was not entered", async () => { @@ -292,7 +328,7 @@ describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); - jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockImplementationOnce(() => { + const ussApi = jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockImplementationOnce(() => { throw Error("Test error"); }); globalMocks.mockShowInputBox.mockReturnValueOnce("USSFolder"); @@ -305,6 +341,7 @@ describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { } expect(testError?.message).toEqual("Test error"); + ussApi.mockRestore(); }); it("Tests that only the child node is refreshed when createUSSNode() is called on a child node", async () => { @@ -312,31 +349,28 @@ describe("USS Action Unit Tests - Function createUSSNodeDialog", () => { const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowInputBox.mockReturnValueOnce("USSFolder"); - jest.spyOn(blockMocks.ussNode, "getChildren").mockResolvedValueOnce([]); - const isTopLevel = false; - jest.spyOn(SharedActions, "refreshAll"); + const refreshAllSpy = jest.spyOn(SharedActions, "refreshAll"); + refreshAllSpy.mockRestore(); - await USSActions.createUSSNode(blockMocks.ussNode, blockMocks.testUSSTree, "folder", isTopLevel); + await USSActions.createUSSNode(blockMocks.ussNode, blockMocks.testUSSTree, "folder"); expect(blockMocks.testUSSTree.refreshElement).toHaveBeenCalled(); - expect(SharedActions.refreshAll).not.toHaveBeenCalled(); + expect(refreshAllSpy).not.toHaveBeenCalled(); }); it("Tests that the error is handled if createUSSNode is unsuccessful", async () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowInputBox.mockReturnValueOnce("USSFolder"); - const isTopLevel = false; const errorHandlingSpy = jest.spyOn(AuthUtils, "errorHandling"); // Simulate unsuccessful api call - Object.defineProperty(blockMocks.ussApi, "create", { - value: jest.fn(() => { - throw new Error(); - }), + const createMock = jest.spyOn(blockMocks.ussApi, "create").mockImplementationOnce(async (ussPath, type, mode) => { + throw new Error(); }); - await expect(USSActions.createUSSNode(blockMocks.ussNode, blockMocks.testUSSTree, "folder", isTopLevel)).rejects.toThrow(); + await expect(USSActions.createUSSNode(blockMocks.ussNode, blockMocks.testUSSTree, "folder")).rejects.toThrow(); expect(errorHandlingSpy).toHaveBeenCalledTimes(1); + createMock.mockRestore(); }); }); @@ -398,8 +432,10 @@ describe("USS Action Unit Tests - Function deleteFromDisk", () => { jest.spyOn(fs, "unlinkSync").mockImplementation(() => { throw new Error(); }); + const warnSpy = jest.spyOn(ZoweLogger, "warn"); + warnSpy.mockRestore(); USSActions.deleteFromDisk(null, "some/where/that/does/not/exist"); - expect(ZoweLogger.warn).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts index 9905de3c40..1f03687097 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts @@ -1688,6 +1688,7 @@ describe("USSTree Unit Tests - Function crossLparMove", () => { profile: ussDirNode.getProfile(), }), ]; + ussDirNode.dirty = false; const deleteMock = jest.spyOn(UssFSProvider.instance, "delete").mockResolvedValue(undefined); const readFileMock = jest.spyOn(UssFSProvider.instance, "readFile").mockResolvedValue(new Uint8Array([1, 2, 3])); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts index f2ea39ae06..5d00e1ac6e 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts @@ -25,7 +25,7 @@ import { createInstanceOfProfile, createValidIProfile, } from "../../../__mocks__/mockCreators/shared"; -import { createUSSTree } from "../../../__mocks__/mockCreators/uss"; +import { createUSSNode, createUSSTree } from "../../../__mocks__/mockCreators/uss"; import { Constants } from "../../../../src/configuration/Constants"; import { ZoweLocalStorage } from "../../../../src/tools/ZoweLocalStorage"; import { UssFSProvider } from "../../../../src/trees/uss/UssFSProvider"; @@ -831,7 +831,7 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { setAttrsMock.mockRestore(); }); - it("Tests that error is thrown when node label is blank", async () => { + it("Tests that error is thrown when node label is blank", () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); @@ -842,29 +842,29 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { expect(blockMocks.rootNode.getChildren()).rejects.toEqual(Error("Invalid node")); }); - it( - "Tests that when zowe.List. causes an error on the zowe call, " + "node.getChildren() throws an error and the catch block is reached", - async () => { - const globalMocks = createGlobalMocks(); - const blockMocks = createBlockMocks(globalMocks); - - blockMocks.childNode.contextValue = Constants.USS_SESSION_CONTEXT; - blockMocks.childNode.fullPath = "Throw Error"; - blockMocks.childNode.dirty = true; - blockMocks.childNode.profile = globalMocks.profileOne; - jest.spyOn(UssFSProvider.instance, "listFiles").mockImplementation(() => { - throw new Error("Throwing an error to check error handling for unit tests!"); - }); - - await blockMocks.childNode.getChildren(); - expect(globalMocks.showErrorMessage.mock.calls.length).toEqual(1); - expect(globalMocks.showErrorMessage.mock.calls[0][0]).toEqual( - "Retrieving response from uss-file-list Error: Throwing an error to check error handling for unit tests!" - ); - } - ); + it("Tests that when List.fileList throws an error, node.getChildren() throws an error and the catch block is reached", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); + + // Populate node with children from previous search to ensure they are removed + blockMocks.childNode.children = [createUSSNode(globalMocks.session, globalMocks.profileOne)]; + blockMocks.childNode.contextValue = Constants.USS_SESSION_CONTEXT; + blockMocks.childNode.fullPath = "Throw Error"; + blockMocks.childNode.dirty = true; + blockMocks.childNode.profile = globalMocks.profileOne; + jest.spyOn(UssFSProvider.instance, "listFiles").mockImplementation(() => { + throw new Error("Throwing an error to check error handling for unit tests!"); + }); - it("Tests that when passing a globalMocks.session node that is not dirty the node.getChildren() method is exited early", async () => { + const response = await blockMocks.childNode.getChildren(); + expect(response).toEqual([]); + expect(globalMocks.showErrorMessage.mock.calls.length).toEqual(1); + expect(globalMocks.showErrorMessage.mock.calls[0][0]).toEqual( + "Retrieving response from USS list API Error: Throwing an error to check error handling for unit tests!" + ); + }); + + it("Tests that when passing a session node that is not dirty the node.getChildren() method is exited early", async () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); @@ -874,6 +874,21 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { expect(await blockMocks.rootNode.getChildren()).toEqual([]); }); + + it("Tests that when passing a session node without path the node.getChildren() method is exited early", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); + + blockMocks.rootNode.contextValue = Constants.USS_SESSION_CONTEXT; + const expectedNode = new ZoweUSSNode({ + label: "Use the search button to list USS files", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: blockMocks.rootNode, + contextOverride: Constants.INFORMATION_CONTEXT, + }); + + expect(await blockMocks.rootNode.getChildren()).toEqual([expectedNode]); + }); }); describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index 166f67f87f..c276a01e3e 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -286,7 +286,7 @@ describe("ProfilesUtils unit tests", () => { const mockReadProfilesFromDisk = jest.fn(); const profInfoSpy = jest.spyOn(ProfilesUtils, "getProfileInfo").mockReturnValue({ readProfilesFromDisk: mockReadProfilesFromDisk, - getTeamConfig: () => ({ exists: true }), + getTeamConfig: () => ({ exists: true, layers: [] }), } as never); await expect(ProfilesUtils.readConfigFromDisk()).resolves.not.toThrow(); expect(mockReadProfilesFromDisk).toHaveBeenCalledTimes(1); diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index 999fe372c4..d8fc0352e4 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -143,9 +143,6 @@ "Favorites": "Favorites", "Use the search button to list USS files": "Use the search button to list USS files", "Invalid node": "Invalid node", - "Profile auth error": "Profile auth error", - "Profile is not authenticated, please log in to continue": "Profile is not authenticated, please log in to continue", - "Retrieving response from uss-file-list": "Retrieving response from uss-file-list", "Delete action was cancelled.": "Delete action was cancelled.", "Unable to delete node: {0}/Error message": { "message": "Unable to delete node: {0}", @@ -171,6 +168,9 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.", "Uploading USS files...": "Uploading USS files...", "Error uploading files": "Error uploading files", + "Profile auth error": "Profile auth error", + "Profile is not authenticated, please log in to continue": "Profile is not authenticated, please log in to continue", + "Retrieving response from USS list API": "Retrieving response from USS list API", "Downloaded: {0}/Download time": { "message": "Downloaded: {0}", "comment": [ @@ -334,10 +334,10 @@ ] }, "Enter a codepage (e.g., 1047, IBM-1047)": "Enter a codepage (e.g., 1047, IBM-1047)", + "Changes in the credential vault detected, refreshing Zowe Explorer.": "Changes in the credential vault detected, refreshing Zowe Explorer.", "Team config file created, refreshing Zowe Explorer.": "Team config file created, refreshing Zowe Explorer.", "Team config file deleted, refreshing Zowe Explorer.": "Team config file deleted, refreshing Zowe Explorer.", "Team config file updated.": "Team config file updated.", - "Changes in the credential vault detected, refreshing Zowe Explorer.": "Changes in the credential vault detected, refreshing Zowe Explorer.", "Changes in credential management detected, refreshing Zowe Explorer.": "Changes in credential management detected, refreshing Zowe Explorer.", "Zowe Explorer has activated successfully.": "Zowe Explorer has activated successfully.", "Type the new pattern to add to history": "Type the new pattern to add to history", @@ -350,9 +350,9 @@ "Select a recent member to open": "Select a recent member to open", "No recent members found.": "No recent members found.", "Use the search button to display jobs": "Use the search button to display jobs", - "There are no JES spool messages to display": "There are no JES spool messages to display", + "No spool files found": "No spool files found", "No jobs found": "No jobs found", - "Retrieving response from zowe.GetJobs": "Retrieving response from zowe.GetJobs", + "Retrieving response from JES list API": "Retrieving response from JES list API", "$(list-ordered) Job ID (default)": "$(list-ordered) Job ID (default)", "$(calendar) Date Submitted": "$(calendar) Date Submitted", "$(calendar) Date Completed": "$(calendar) Date Completed", @@ -538,6 +538,7 @@ "The response from Zowe CLI was not successful": "The response from Zowe CLI was not successful", "{0} members failed to load due to invalid name errors for {1}": "{0} members failed to load due to invalid name errors for {1}", "No data sets found": "No data sets found", + "Retrieving response from MVS list API": "Retrieving response from MVS list API", "Cannot download, item invalid.": "Cannot download, item invalid.", "$(case-sensitive) Name (default)": "$(case-sensitive) Name (default)", "$(calendar) Date Created": "$(calendar) Date Created", @@ -801,12 +802,6 @@ "Stringified JSON error" ] }, - "Prompted for a data set pattern, recieved {0}./Data Set pattern": { - "message": "Prompted for a data set pattern, recieved {0}.", - "comment": [ - "Data Set pattern" - ] - }, "Cannot perform the copy operation as the data sets selected have different types": "Cannot perform the copy operation as the data sets selected have different types", "Migration of data set {0} requested./Data Set name": { "message": "Migration of data set {0} requested.", diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index f7380ce2c4..f9aee4c4fd 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -478,9 +478,6 @@ "Favorites": "", "Use the search button to list USS files": "", "Invalid node": "", - "Profile auth error": "", - "Profile is not authenticated, please log in to continue": "", - "Retrieving response from uss-file-list": "", "Delete action was cancelled.": "", "Unable to delete node: {0}": "", "The item {0} has been deleted.": "", @@ -491,6 +488,9 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "", "Uploading USS files...": "", "Error uploading files": "", + "Profile auth error": "", + "Profile is not authenticated, please log in to continue": "", + "Retrieving response from USS list API": "", "Downloaded: {0}": "", "Encoding: {0}": "", "Binary": "", @@ -554,10 +554,10 @@ "Choose encoding for {0}": "", "Current encoding is {0}": "", "Enter a codepage (e.g., 1047, IBM-1047)": "", + "Changes in the credential vault detected, refreshing Zowe Explorer.": "", "Team config file created, refreshing Zowe Explorer.": "", "Team config file deleted, refreshing Zowe Explorer.": "", "Team config file updated.": "", - "Changes in the credential vault detected, refreshing Zowe Explorer.": "", "Changes in credential management detected, refreshing Zowe Explorer.": "", "Zowe Explorer has activated successfully.": "", "Type the new pattern to add to history": "", @@ -570,9 +570,9 @@ "Select a recent member to open": "", "No recent members found.": "", "Use the search button to display jobs": "", - "There are no JES spool messages to display": "", + "No spool files found": "", "No jobs found": "", - "Retrieving response from zowe.GetJobs": "", + "Retrieving response from JES list API": "", "$(list-ordered) Job ID (default)": "", "$(calendar) Date Submitted": "", "$(calendar) Date Completed": "", @@ -648,6 +648,7 @@ "The response from Zowe CLI was not successful": "", "{0} members failed to load due to invalid name errors for {1}": "", "No data sets found": "", + "Retrieving response from MVS list API": "", "Cannot download, item invalid.": "", "$(case-sensitive) Name (default)": "", "$(calendar) Date Created": "", @@ -740,7 +741,6 @@ "Item invalid.": "", "$(sync~spin) Fetching data set...": "", "Error encountered when refreshing data set view. {0}": "", - "Prompted for a data set pattern, recieved {0}.": "", "Cannot perform the copy operation as the data sets selected have different types": "", "Migration of data set {0} requested.": "", "Recall of data set {0} requested.": "", diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index c92c6fc420..325e098b3e 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -1794,8 +1794,7 @@ "madge": "madge -c --no-color --no-spinner --exclude __mocks__ --extensions js,ts src/", "pretty": "prettier --write .", "generateLocalization": "pnpm dlx @vscode/l10n-dev export --o ./l10n ./src && node ./scripts/generatePoeditorJson.js", - "copy-secrets": "node ./scripts/getSecretsPrebuilds.js", - "strip-l10n": "node ./scripts/stripL10nComments.js" + "copy-secrets": "node ./scripts/getSecretsPrebuilds.js" }, "engines": { "vscode": "^1.79.0" diff --git a/packages/zowe-explorer/scripts/generatePoeditorJson.js b/packages/zowe-explorer/scripts/generatePoeditorJson.js index 90753e24e9..afd8bac577 100644 --- a/packages/zowe-explorer/scripts/generatePoeditorJson.js +++ b/packages/zowe-explorer/scripts/generatePoeditorJson.js @@ -5,7 +5,7 @@ */ const fs = require("fs"); -const langId = process.argv[2]; +const langId = process.argv.slice(2).find(arg => !arg.startsWith("-")); const fileSuffix = langId ? `${langId}.json` : "json"; const poeditorJson = {}; const packageNls = require(__dirname + "/../package.nls." + fileSuffix); @@ -25,3 +25,16 @@ for (const [k, v] of Object.entries(l10nBundle)) { } } fs.writeFileSync(__dirname + "/../l10n/poeditor." + fileSuffix, JSON.stringify(poeditorJson, null, 2) + "\n"); + +if (process.argv.includes("--strip")) { + // Strip comments out of bundle.l10n.json and sort properties by key + const jsonFilePath = __dirname + "/../l10n/bundle.l10n.json"; + let l10nBundle = JSON.parse(fs.readFileSync(jsonFilePath, "utf-8")); + for (const [k, v] of Object.entries(l10nBundle)) { + if (typeof v === "object") { + l10nBundle[k] = l10nBundle[k].message; + } + } + l10nBundle = Object.fromEntries(Object.entries(l10nBundle).sort(([a], [b]) => a.localeCompare(b))); + fs.writeFileSync(jsonFilePath, JSON.stringify(l10nBundle, null, 2) + "\n"); +} diff --git a/packages/zowe-explorer/scripts/stripL10nComments.js b/packages/zowe-explorer/scripts/stripL10nComments.js deleted file mode 100644 index 23c60b78eb..0000000000 --- a/packages/zowe-explorer/scripts/stripL10nComments.js +++ /dev/null @@ -1,11 +0,0 @@ -// Strip comments out of bundle.l10n.json and sort properties by key -const fs = require("fs"); -const jsonFilePath = process.argv[2] || (__dirname + "/../l10n/bundle.l10n.json"); -let l10nBundle = JSON.parse(fs.readFileSync(jsonFilePath, "utf-8")); -for (const [k, v] of Object.entries(l10nBundle)) { - if (typeof v === "object") { - l10nBundle[k] = l10nBundle[k].message; - } -} -l10nBundle = Object.fromEntries(Object.entries(l10nBundle).sort(([a], [b]) => a.localeCompare(b))); -fs.writeFileSync(jsonFilePath + ".template", JSON.stringify(l10nBundle, null, 2) + "\n"); diff --git a/packages/zowe-explorer/src/configuration/Constants.ts b/packages/zowe-explorer/src/configuration/Constants.ts index f2643a3504..24a965867f 100644 --- a/packages/zowe-explorer/src/configuration/Constants.ts +++ b/packages/zowe-explorer/src/configuration/Constants.ts @@ -16,7 +16,6 @@ import { imperative, PersistenceSchemaEnum } from "@zowe/zowe-explorer-api"; import type { Profiles } from "./Profiles"; export class Constants { - public static CONFIG_PATH: string; public static readonly COMMAND_COUNT = 99; public static readonly MAX_SEARCH_HISTORY = 5; public static readonly MAX_FILE_HISTORY = 10; @@ -82,7 +81,8 @@ export class Constants { public static DS_NAME_REGEX_CHECK = /^[a-zA-Z#@$][a-zA-Z0-9#@$-]{0,7}(\.[a-zA-Z#@$][a-zA-Z0-9#@$-]{0,7})*$/; public static MEMBER_NAME_REGEX_CHECK = /^[a-zA-Z#@$][a-zA-Z0-9#@$]{0,7}$/; public static ACTIVATED = false; - public static SAVED_PROFILE_CONTENTS = new Uint8Array(); + public static SAVED_PROFILE_CONTENTS = new Map(); + public static IGNORE_VAULT_CHANGE = false; public static readonly JOBS_MAX_PREFIX = 8; public static PROFILES_CACHE: Profiles; public static readonly WORKSPACE_UTIL_TAB_SWITCH_DELAY = 200; diff --git a/packages/zowe-explorer/src/configuration/Profiles.ts b/packages/zowe-explorer/src/configuration/Profiles.ts index dd66d4ee25..de3fd1b590 100644 --- a/packages/zowe-explorer/src/configuration/Profiles.ts +++ b/packages/zowe-explorer/src/configuration/Profiles.ts @@ -760,7 +760,7 @@ export class Profiles extends ProfilesCache { return filteredProfile; } - public async ssoLogin(node?: Types.IZoweNodeType, label?: string): Promise { + public async ssoLogin(node?: Types.IZoweNodeType, label?: string): Promise { ZoweLogger.trace("Profiles.ssoLogin called."); let loginTokenType: string; let serviceProfile: imperative.IProfileLoaded; @@ -772,7 +772,7 @@ export class Profiles extends ProfilesCache { // This check will handle service profiles that have username and password if (AuthUtils.isProfileUsingBasicAuth(serviceProfile)) { Gui.showMessage(vscode.l10n.t(`This profile is using basic authentication and does not support token authentication.`)); - return; + return false; } const zeInstance = ZoweExplorerApiRegister.getInstance(); @@ -787,7 +787,7 @@ export class Profiles extends ProfilesCache { comment: [`Service profile name`], }) ); - return; + return false; } try { let loginOk = false; @@ -814,6 +814,7 @@ export class Profiles extends ProfilesCache { } else { Gui.showMessage(this.profilesOpCancelled); } + return loginOk; } catch (err) { const message = vscode.l10n.t({ message: `Unable to log in with {0}. {1}`, @@ -822,7 +823,7 @@ export class Profiles extends ProfilesCache { }); ZoweLogger.error(message); Gui.errorMessage(message); - return; + return false; } } diff --git a/packages/zowe-explorer/src/trees/ZoweTreeProvider.ts b/packages/zowe-explorer/src/trees/ZoweTreeProvider.ts index 20f4260e24..ea2ddbd1ed 100644 --- a/packages/zowe-explorer/src/trees/ZoweTreeProvider.ts +++ b/packages/zowe-explorer/src/trees/ZoweTreeProvider.ts @@ -128,9 +128,11 @@ export class ZoweTreeProvider { if (icon) { element.iconPath = icon.path; } - element.dirty = true; if (isOpen) { this.mOnDidChangeTreeData.fire(element); + } else { + // Don't mark as dirty when expanded to avoid duplicate refresh + element.dirty = true; } } diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts b/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts index a5e0b8cf3f..ab8919fdd8 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts @@ -1297,58 +1297,6 @@ export class DatasetActions { } } - /** - * Prompts the user for a pattern, and populates the [TreeView]{@link vscode.TreeView} based on the pattern - * - * @param {IZoweDatasetTreeNode} node - The session node - * @param datasetProvider - Current DatasetTree used to populate the TreeView - * @returns {Promise} - */ - // This function does not appear to be called by anything except unit and integration tests. - public static async enterPattern(node: IZoweDatasetTreeNode, datasetProvider: Types.IZoweDatasetTreeType): Promise { - ZoweLogger.trace("dataset.actions.enterPattern called."); - let pattern: string; - if (SharedContext.isSessionNotFav(node)) { - // manually entering a search - const options: vscode.InputBoxOptions = { - prompt: vscode.l10n.t("Search Data Sets: use a comma to separate multiple patterns"), - value: node.pattern, - }; - // get user input - pattern = await Gui.showInputBox(options); - if (!pattern) { - Gui.showMessage(vscode.l10n.t("You must enter a pattern.")); - return; - } - ZoweLogger.debug( - vscode.l10n.t({ - message: "Prompted for a data set pattern, recieved {0}.", - args: [pattern], - comment: ["Data Set pattern"], - }) - ); - } else { - // executing search from saved search in favorites - pattern = node.label.toString().substring(node.label.toString().indexOf(":") + 2); - const sessionName = node.label.toString().substring(node.label.toString().indexOf("[") + 1, node.label.toString().indexOf("]")); - await datasetProvider.addSession({ sessionName: sessionName.trim() }); - node = datasetProvider.mSessionNodes.find((tempNode) => tempNode.label.toString().trim() === sessionName.trim()) as IZoweDatasetTreeNode; - } - - // update the treeview with the new pattern - // TODO figure out why a label change is needed to refresh the treeview, - // instead of changing the collapsible state - // change label so the treeview updates - node.tooltip = node.pattern = pattern.toUpperCase(); - node.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; - node.dirty = true; - const icon = IconGenerator.getIconByNode(node); - if (icon) { - node.iconPath = icon.path; - } - datasetProvider.addSearchHistory(node.pattern); - } - /** * Copy data sets * diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts index 3575bf2404..8a7a60d428 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts @@ -23,6 +23,7 @@ import { SharedUtils } from "../shared/SharedUtils"; export class DatasetInit { public static async createDatasetTree(log: imperative.Logger): Promise { + ZoweLogger.trace("DatasetInit.createDatasetTree called."); const tree = new DatasetTree(); await tree.initializeFavorites(log); await tree.addSession(); @@ -30,7 +31,7 @@ export class DatasetInit { } public static async initDatasetProvider(context: vscode.ExtensionContext): Promise { - ZoweLogger.trace("dataset.init.initDatasetProvider called."); + ZoweLogger.trace("DatasetInit.initDatasetProvider called."); context.subscriptions.push(vscode.workspace.registerFileSystemProvider(ZoweScheme.DS, DatasetFSProvider.instance, { isCaseSensitive: true })); const datasetProvider = await DatasetInit.createDatasetTree(ZoweLogger.log); if (datasetProvider == null) { diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts b/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts index e6cbf56516..b99db6fd30 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts @@ -147,13 +147,7 @@ export class DatasetTree extends ZoweTreeProvider implemen if (element.contextValue && element.contextValue === Constants.FAV_PROFILE_CONTEXT) { return this.loadProfilesForFavorites(this.log, element); } - let response: IZoweDatasetTreeNode[] = []; - try { - response = await element.getChildren(); - } catch (error) { - await AuthUtils.errorHandling(error, String(element.label)); - return []; - } + const response = await element.getChildren(); const finalResponse: IZoweDatasetTreeNode[] = []; for (const item of response) { @@ -169,16 +163,6 @@ export class DatasetTree extends ZoweTreeProvider implemen item.contextValue = SharedContext.withProfile(item); } - if (finalResponse.length === 0) { - return (element.children = [ - new ZoweDatasetNode({ - label: vscode.l10n.t("No data sets found"), - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: element, - contextOverride: Constants.INFORMATION_CONTEXT, - }), - ]); - } return finalResponse; } return this.mSessionNodes; @@ -407,13 +391,13 @@ export class DatasetTree extends ZoweTreeProvider implemen for (const favorite of favsForProfile) { // If profile and session already exists for favorite node, add to updatedFavsForProfile and go to next array item if (favorite.getProfile() && favorite.getSession()) { - updatedFavsForProfile.push(favorite as IZoweDatasetTreeNode); + updatedFavsForProfile.push(favorite); continue; } // If no profile/session for favorite node yet, then add session and profile to favorite node: favorite.setProfileToChoice(profile); favorite.setSessionToChoice(session); - updatedFavsForProfile.push(favorite as IZoweDatasetTreeNode); + updatedFavsForProfile.push(favorite); } // This updates the profile node's children in the this.mFavorites array, as well. return updatedFavsForProfile; @@ -457,8 +441,12 @@ export class DatasetTree extends ZoweTreeProvider implemen collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile, + contextOverride: Constants.DS_SESSION_CONTEXT, }); - node.contextValue = Constants.DS_SESSION_CONTEXT + (profile.type !== "zosmf" ? `.profile=${profile.type}.` : ""); + if (profile.type !== "zosmf") { + // TODO: Why do we inject profiles in context value only for DS tree? + node.contextValue += `.profile=${profile.type}.`; + } await this.refreshHomeProfileContext(node); const icon = IconGenerator.getIconByNode(node); if (icon) { @@ -571,7 +559,7 @@ export class DatasetTree extends ZoweTreeProvider implemen if (matchingNode) { matchingNode.label = afterLabel; matchingNode.tooltip = afterLabel; - this.refreshElement(matchingNode as IZoweDatasetTreeNode); + this.refreshElement(matchingNode); } } } @@ -892,10 +880,10 @@ export class DatasetTree extends ZoweTreeProvider implemen if (session.children) { for (const node of session.children) { if (node.contextValue !== Constants.INFORMATION_CONTEXT) { - loadedItems.push(node as IZoweDatasetTreeNode); + loadedItems.push(node); for (const member of node.children) { if (member.contextValue !== Constants.INFORMATION_CONTEXT) { - loadedItems.push(member as IZoweDatasetTreeNode); + loadedItems.push(member); } } } @@ -983,8 +971,7 @@ export class DatasetTree extends ZoweTreeProvider implemen let setIcon: IconUtils.IIconItem; if (child.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) { setIcon = IconGenerator.getIconById(IconUtils.IconId.filterFolder); - } - if (child.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) { + } else if (child.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) { setIcon = IconGenerator.getIconById(IconUtils.IconId.filterFolderOpen); } if (setIcon) { @@ -992,7 +979,6 @@ export class DatasetTree extends ZoweTreeProvider implemen } } } - sessionNode.dirty = true; const icon = IconGenerator.getIconByNode(sessionNode); if (icon) { sessionNode.iconPath = icon.path; @@ -1003,13 +989,13 @@ export class DatasetTree extends ZoweTreeProvider implemen public async datasetFilterPrompt(node: IZoweDatasetTreeNode): Promise { ZoweLogger.trace("DatasetTree.datasetFilterPrompt called."); - ZoweLogger.debug(vscode.l10n.t("Prompting the user for a data set pattern")); let pattern: string; await this.checkCurrentProfile(node); const sessionNode = node; if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) { if (SharedContext.isSessionNotFav(node)) { + ZoweLogger.debug(vscode.l10n.t("Prompting the user for a data set pattern")); if (this.mHistory.getSearchHistory().length > 0) { const createPick = new FilterDescriptor(DatasetTree.defaultDialogText); const items: vscode.QuickPickItem[] = this.mHistory.getSearchHistory().map((element) => new FilterItem({ text: element })); @@ -1032,12 +1018,12 @@ export class DatasetTree extends ZoweTreeProvider implemen pattern = choice.label; } } - const options2: vscode.InputBoxOptions = { + const options: vscode.InputBoxOptions = { prompt: vscode.l10n.t("Search Data Sets: use a comma to separate multiple patterns"), value: pattern, }; // get user input - pattern = await Gui.showInputBox(options2); + pattern = await Gui.showInputBox(options); if (!pattern) { Gui.showMessage(vscode.l10n.t("You must enter a pattern.")); return; @@ -1055,7 +1041,6 @@ export class DatasetTree extends ZoweTreeProvider implemen } } // looking for members in pattern - node.children = []; node.dirty = true; AuthUtils.syncSessionNode((profile) => ZoweExplorerApiRegister.getMvsApi(profile), sessionNode); @@ -1088,6 +1073,7 @@ export class DatasetTree extends ZoweTreeProvider implemen sessionNode.resourceUri = sessionNode.resourceUri.with({ query: `pattern=${pattern}` }); } await TreeViewUtils.expandNode(sessionNode, this); + this.refresh(); } public checkFilterPattern(dsName: string, itemName: string): boolean { @@ -1276,7 +1262,7 @@ export class DatasetTree extends ZoweTreeProvider implemen } c.children.sort(ZoweDatasetNode.sortBy(node.sort)); - this.nodeDataChanged(c as IZoweDatasetTreeNode); + this.nodeDataChanged(c); } } } @@ -1389,7 +1375,7 @@ export class DatasetTree extends ZoweTreeProvider implemen if (node.children?.length > 0) { // children nodes already exist, sort and repaint to avoid extra refresh for (const c of node.children) { - const asDs = c as IZoweDatasetTreeNode; + const asDs = c; // PDS-level filters should have precedence over a session-level filter if (asDs.filter != null) { @@ -1400,15 +1386,15 @@ export class DatasetTree extends ZoweTreeProvider implemen // If there was an old session-wide filter set: refresh to get any // missing nodes - new filter will be applied if (oldFilter != null) { - this.refreshElement(c as IZoweDatasetTreeNode); + this.refreshElement(c); continue; } if (newFilter != null && c.children?.length > 0) { c.children = c.children.filter(ZoweDatasetNode.filterBy(newFilter)); - this.nodeDataChanged(c as IZoweDatasetTreeNode); + this.nodeDataChanged(c); } else { - this.refreshElement(c as IZoweDatasetTreeNode); + this.refreshElement(c); } } } diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index 1483cf89a0..73444e3628 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -75,14 +75,8 @@ export class DatasetUtils { const split = bracket > -1 ? label.substring(0, bracket).split(".", limit) : label.split(".", limit); for (let i = split.length - 1; i > 0; i--) { for (const [ext, matches] of DS_EXTENSION_MAP.entries()) { - for (const match of matches) { - if (match instanceof RegExp) { - if (match.test(split[i])) { - return ext; - } - } else if (match.includes(split[i])) { - return ext; - } + if (matches.some((match) => (match instanceof RegExp ? match.test(split[i]) : match === split[i]))) { + return ext; } } } diff --git a/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts index 3991235673..355c9a5397 100644 --- a/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts @@ -102,13 +102,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod if (this.label !== vscode.l10n.t("Favorites")) { const sessionLabel = opts.profile?.name ?? SharedUtils.getSessionLabel(this); - if (this.getParent() == null || this.getParent().label === vscode.l10n.t("Favorites")) { - this.resourceUri = vscode.Uri.from({ - scheme: ZoweScheme.DS, - path: `/${sessionLabel}/`, - }); - DatasetFSProvider.instance.createDirectory(this.resourceUri); - } else if ( + if ( this.contextValue === Constants.DS_DS_CONTEXT || this.contextValue === Constants.DS_PDS_CONTEXT || this.contextValue === Constants.DS_MIGRATED_FILE_CONTEXT @@ -130,7 +124,15 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod }); this.command = { command: "vscode.open", title: "", arguments: [this.resourceUri] }; } else { - this.resourceUri = null; + this.resourceUri = vscode.Uri.from({ + scheme: ZoweScheme.DS, + path: `/${sessionLabel}/`, + }); + if (this.getParent() == null || this.getParent().label === vscode.l10n.t("Favorites")) { + DatasetFSProvider.instance.createDirectory(this.resourceUri); + } else if (this.contextValue === Constants.INFORMATION_CONTEXT) { + this.command = { command: "zowe.placeholderCommand", title: "Placeholder" }; + } } if (opts.encoding != null) { @@ -201,20 +203,15 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod * @returns {Promise} */ public async getChildren(): Promise { - ZoweLogger.trace("ZoweDatasetNode.getChildren called."); + ZoweLogger.trace(`ZoweDatasetNode.getChildren called for ${this.label as string}.`); if (!this.pattern && SharedContext.isSessionNotFav(this)) { const placeholder = new ZoweDatasetNode({ label: vscode.l10n.t("Use the search button to display data sets"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, contextOverride: Constants.INFORMATION_CONTEXT, - profile: null, }); - placeholder.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; - return [placeholder]; + return (this.children = [placeholder]); } if (SharedContext.isDocument(this) || SharedContext.isInformation(this)) { return []; @@ -232,8 +229,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod // Gets the datasets from the pattern or members of the dataset and displays any thrown errors const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); const responses = await this.getDatasets(cachedProfile); - if (responses.length === 0) { - return; + if (responses == null) { + return []; } // push nodes to an object with property names to avoid duplicates @@ -243,7 +240,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod // The dataSetsMatchingPattern API may return success=false and apiResponse=[] when no data sets found if (!response.success && !(Array.isArray(response.apiResponse) && response.apiResponse.length === 0)) { await AuthUtils.errorHandling(vscode.l10n.t("The response from Zowe CLI was not successful")); - return; + return []; } // Loops through all the returned dataset members and creates nodes for them @@ -377,10 +374,6 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod parentNode: this, contextOverride: Constants.INFORMATION_CONTEXT, }); - placeholder.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; this.children = [placeholder]; } else { const newChildren = Object.keys(elementChildren) @@ -539,14 +532,15 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod return fileEntry?.etag; } - private async getDatasets(profile: imperative.IProfileLoaded): Promise { + private async getDatasets(profile: imperative.IProfileLoaded): Promise { ZoweLogger.trace("ZoweDatasetNode.getDatasets called."); const responses: zosfiles.IZosFilesResponse[] = []; const options: zosfiles.IListOptions = { attributes: true, responseTimeout: profile.profile.responseTimeout, }; - if (SharedContext.isSession(this) || SharedContext.isFavoriteSearch(this)) { + const isSession = SharedContext.isSession(this) || SharedContext.isFavoriteSearch(this); + if (isSession) { const fullPattern = SharedContext.isFavoriteSearch(this) ? (this.label as string) : this.pattern; const dsTree = SharedTreeProviders.ds as DatasetTree; this.patternMatches = dsTree.extractPatterns(fullPattern); @@ -558,38 +552,46 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod } this.tooltip = this.pattern = dsPattern.toUpperCase(); } + } - const dsPatterns = [ - ...new Set( - this.pattern - .toUpperCase() - .split(",") - .map((p) => p.trim()) - ), - ]; - const mvsApi = ZoweExplorerApiRegister.getMvsApi(profile); - if (!mvsApi.getSession(profile)) { - throw new imperative.ImperativeError({ - msg: vscode.l10n.t("Profile auth error"), - additionalDetails: vscode.l10n.t("Profile is not authenticated, please log in to continue"), - errorCode: `${imperative.RestConstants.HTTP_STATUS_401}`, - }); - } - if (mvsApi.dataSetsMatchingPattern) { - responses.push(await mvsApi.dataSetsMatchingPattern(dsPatterns)); - } else { - for (const dsp of dsPatterns) { - responses.push(await mvsApi.dataSet(dsp)); + try { + if (isSession) { + const dsPatterns = [ + ...new Set( + this.pattern + .toUpperCase() + .split(",") + .map((p) => p.trim()) + ), + ]; + const mvsApi = ZoweExplorerApiRegister.getMvsApi(profile); + if (!mvsApi.getSession(profile)) { + throw new imperative.ImperativeError({ + msg: vscode.l10n.t("Profile auth error"), + additionalDetails: vscode.l10n.t("Profile is not authenticated, please log in to continue"), + errorCode: `${imperative.RestConstants.HTTP_STATUS_401}`, + }); } - } - } else if (this.memberPattern) { - this.memberPattern = this.memberPattern.toUpperCase(); - for (const memPattern of this.memberPattern.split(",")) { - options.pattern = memPattern; + if (mvsApi.dataSetsMatchingPattern) { + responses.push(await mvsApi.dataSetsMatchingPattern(dsPatterns)); + } else { + for (const dsp of dsPatterns) { + responses.push(await mvsApi.dataSet(dsp)); + } + } + } else if (this.memberPattern) { + this.memberPattern = this.memberPattern.toUpperCase(); + for (const memPattern of this.memberPattern.split(",")) { + options.pattern = memPattern; + responses.push(await ZoweExplorerApiRegister.getMvsApi(profile).allMembers(this.label as string, options)); + } + } else { responses.push(await ZoweExplorerApiRegister.getMvsApi(profile).allMembers(this.label as string, options)); } - } else { - responses.push(await ZoweExplorerApiRegister.getMvsApi(profile).allMembers(this.label as string, options)); + } catch (error) { + const updated = await AuthUtils.errorHandling(error, this.getProfileName(), vscode.l10n.t("Retrieving response from MVS list API")); + AuthUtils.syncSessionNode((prof) => ZoweExplorerApiRegister.getMvsApi(prof), this.getSessionNode(), updated && this); + return; } return responses; } diff --git a/packages/zowe-explorer/src/trees/job/JobInit.ts b/packages/zowe-explorer/src/trees/job/JobInit.ts index 7367955e39..2f4516ba08 100644 --- a/packages/zowe-explorer/src/trees/job/JobInit.ts +++ b/packages/zowe-explorer/src/trees/job/JobInit.ts @@ -32,7 +32,7 @@ export class JobInit { * @implements {vscode.TreeDataProvider} */ public static async createJobsTree(log: imperative.Logger): Promise { - ZoweLogger.trace("ZosJobsProvider.createJobsTree called."); + ZoweLogger.trace("JobInit.createJobsTree called."); const tree = new JobTree(); await tree.initializeJobsTree(log); await tree.addSession(); @@ -40,7 +40,7 @@ export class JobInit { } public static async initJobsProvider(context: vscode.ExtensionContext): Promise { - ZoweLogger.trace("job.init.initJobsProvider called."); + ZoweLogger.trace("JobInit.initJobsProvider called."); context.subscriptions.push(vscode.workspace.registerFileSystemProvider(ZoweScheme.Jobs, JobFSProvider.instance, { isCaseSensitive: false })); const jobsProvider = await JobInit.createJobsTree(ZoweLogger.log); if (jobsProvider == null) { diff --git a/packages/zowe-explorer/src/trees/job/JobTree.ts b/packages/zowe-explorer/src/trees/job/JobTree.ts index 176447cb89..0262ab49ec 100644 --- a/packages/zowe-explorer/src/trees/job/JobTree.ts +++ b/packages/zowe-explorer/src/trees/job/JobTree.ts @@ -32,21 +32,6 @@ import { AuthUtils } from "../../utils/AuthUtils"; import { PollProvider } from "./JobPollProvider"; import { Definitions } from "../../configuration/Definitions"; -/** - * Creates the Job tree that contains nodes of sessions, jobs and spool items - * - * @export - * @class ZosJobsProvider - * @implements {vscode.TreeDataProvider} - */ -export async function createJobsTree(log: imperative.Logger): Promise { - ZoweLogger.trace("ZosJobsProvider.createJobsTree called."); - const tree = new JobTree(); - await tree.initializeJobsTree(log); - await tree.addSession(); - return tree; -} - export class JobTree extends ZoweTreeProvider implements Types.IZoweJobTreeType { public static readonly JobId = "JobId: "; public static readonly Owner = "Owner: "; @@ -139,7 +124,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param {IZoweJobTreeNode} node */ public saveSearch(node: IZoweJobTreeNode): void { - ZoweLogger.trace("ZosJobsProvider.saveSearch called."); + ZoweLogger.trace("JobTree.saveSearch called."); node.contextValue = SharedContext.asFavorite(node); } public saveFile(_document: vscode.TextDocument): void { @@ -162,7 +147,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns {IZoweJobTreeNode[] | Promise} */ public async getChildren(element?: IZoweJobTreeNode | undefined): Promise { - ZoweLogger.trace("ZosJobsProvider.getChildren called."); + ZoweLogger.trace("JobTree.getChildren called."); if (element) { // solution for optional credentials. Owner is having error on initialization. if (element.owner === "") { @@ -185,7 +170,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns {vscode.TreeView} */ public getTreeView(): vscode.TreeView { - ZoweLogger.trace("ZosJobsProvider.getTreeView called."); + ZoweLogger.trace("JobTree.getTreeView called."); return this.treeView; } @@ -194,7 +179,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param profile the profile to add to the tree */ public async addSingleSession(profile: imperative.IProfileLoaded): Promise { - ZoweLogger.trace("ZosJobsProvider.addSingleSession called."); + ZoweLogger.trace("JobTree.addSingleSession called."); if (profile) { // If session is already added, do nothing if (this.mSessionNodes.find((tNode) => tNode.label.toString() === profile.name)) { @@ -217,8 +202,8 @@ export class JobTree extends ZoweTreeProvider implements Types collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile, + contextOverride: Constants.JOBS_SESSION_CONTEXT, }); - node.contextValue = Constants.JOBS_SESSION_CONTEXT; await this.refreshHomeProfileContext(node); const icon = IconGenerator.getIconByNode(node); if (icon) { @@ -231,7 +216,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async delete(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.delete called."); + ZoweLogger.trace("JobTree.delete called."); await JobFSProvider.instance.delete(node.resourceUri, { recursive: false, deleteRemote: true }); const favNode = this.relabelFavoritedJob(node); @@ -248,7 +233,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns {IZoweJobTreeNode | undefined} Returns matching profile node if found. Otherwise, returns undefined. */ public findMatchingProfileInArray(jobsProvider: IZoweJobTreeNode[], profileName: string): IZoweJobTreeNode | undefined { - ZoweLogger.trace("ZosJobsProvider.findMatchingProfileInArray called."); + ZoweLogger.trace("JobTree.findMatchingProfileInArray called."); return jobsProvider.find((treeNode) => treeNode.label === profileName); } @@ -257,7 +242,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param node */ public findFavoritedNode(node: IZoweJobTreeNode): IZoweJobTreeNode { - ZoweLogger.trace("ZosJobsProvider.findFavoritedNode called."); + ZoweLogger.trace("JobTree.findFavoritedNode called."); const profileNodeInFavorites = this.findMatchingProfileInArray(this.mFavorites, node.getProfileName()); return profileNodeInFavorites?.children.find( (temp) => temp.label === node.getLabel().toString() && temp.contextValue.includes(node.contextValue) @@ -269,7 +254,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param node */ public findNonFavoritedNode(node: IZoweJobTreeNode): IZoweJobTreeNode { - ZoweLogger.trace("ZosJobsProvider.findNonFavoritedNode called."); + ZoweLogger.trace("JobTree.findNonFavoritedNode called."); const profileName = node.getProfileName(); const sessionNode = this.mSessionNodes.find((session) => session.label.toString().trim() === profileName); return sessionNode?.children.find((temp) => temp.label === node.label); @@ -280,7 +265,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param node */ public findEquivalentNode(node: IZoweJobTreeNode, isFavorite: boolean): IZoweJobTreeNode { - ZoweLogger.trace("ZosJobsProvider.findEquivalentNode called."); + ZoweLogger.trace("JobTree.findEquivalentNode called."); return isFavorite ? this.findNonFavoritedNode(node) : this.findFavoritedNode(node); } @@ -290,7 +275,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns {ZoweJobNode} */ public async createProfileNodeForFavs(profileName: string): Promise { - ZoweLogger.trace("ZosJobsProvider.createProfileNodeForFavs called."); + ZoweLogger.trace("JobTree.createProfileNodeForFavs called."); let favProfileNode: ZoweJobNode; try { const profile = Profiles.getInstance().loadNamedProfile(profileName); @@ -334,7 +319,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param log - Logger */ public async initializeJobsTree(log: imperative.Logger): Promise { - ZoweLogger.trace("ZosJobsProvider.initializeJobsTree called."); + ZoweLogger.trace("JobTree.initializeJobsTree called."); this.log = log; ZoweLogger.debug(vscode.l10n.t("Initializing profiles with jobs favorites.")); const lines: string[] = this.mHistory.readFavorites(); @@ -369,7 +354,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns IZoweJobTreeNode */ public initializeFavChildNodeForProfile(label: string, contextValue: string, parentNode: IZoweJobTreeNode): ZoweJobNode { - ZoweLogger.trace("ZosJobsProvider.initializeFavChildNodeForProfile called."); + ZoweLogger.trace("JobTree.initializeFavChildNodeForProfile called."); let favJob: ZoweJobNode; if (contextValue.startsWith(Constants.JOBS_JOB_CONTEXT)) { favJob = new ZoweJobNode({ @@ -406,7 +391,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param parentNode */ public async loadProfilesForFavorites(log: imperative.Logger, parentNode: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.loadProfilesForFavorites called."); + ZoweLogger.trace("JobTree.loadProfilesForFavorites called."); const profileName = parentNode.label as string; const updatedFavsForProfile: IZoweJobTreeNode[] = []; let profile: imperative.IProfileLoaded; @@ -490,7 +475,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param {IZoweJobTreeNode} node */ public async addFavorite(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.addFavorite called."); + ZoweLogger.trace("JobTree.addFavorite called."); let favJob: ZoweJobNode; // Get node's profile node in favorites const profileName = node.getProfileName(); @@ -544,7 +529,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param {IZoweJobTreeNode} node */ public async removeFavorite(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.removeFavorite called."); + ZoweLogger.trace("JobTree.removeFavorite called."); // Get node's profile node in favorites const profileName = node.getProfileName(); const profileNodeInFavorites = this.findMatchingProfileInArray(this.mFavorites, profileName); @@ -565,7 +550,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public updateFavorites(): void { - ZoweLogger.trace("ZosJobsProvider.updateFavorites called."); + ZoweLogger.trace("JobTree.updateFavorites called."); const favoritesArray = []; this.mFavorites.forEach((profileNode) => { profileNode.children.forEach((fav) => { @@ -589,7 +574,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param userSelected True if the function is being called directly because the user selected to remove the profile from Favorites */ public async removeFavProfile(profileName: string, userSelected: boolean): Promise { - ZoweLogger.trace("ZosJobsProvider.removeFavProfile called."); + ZoweLogger.trace("JobTree.removeFavProfile called."); // If user selected the "Remove profile from Favorites option", confirm they are okay with deleting all favorited items for that profile. let cancelled = false; if (userSelected) { @@ -626,17 +611,17 @@ export class JobTree extends ZoweTreeProvider implements Types } public removeSearchHistory(name: string): void { - ZoweLogger.trace("ZosJobsProvider.removeSearchHistory called."); + ZoweLogger.trace("JobTree.removeSearchHistory called."); this.mHistory.removeSearchHistory(name); } public removeSession(name: string): void { - ZoweLogger.trace("ZosJobsProvider.removeSession called."); + ZoweLogger.trace("JobTree.removeSession called."); this.mHistory.removeSession(name); } public resetSearchHistory(): void { - ZoweLogger.trace("ZosJobsProvider.resetSearchHistory called."); + ZoweLogger.trace("JobTree.resetSearchHistory called."); this.mHistory.resetSearchHistory(); } @@ -656,7 +641,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async getUserJobsMenuChoice(): Promise { - ZoweLogger.trace("ZosJobsProvider.getUserJobsMenuChoice called."); + ZoweLogger.trace("JobTree.getUserJobsMenuChoice called."); const items: FilterItem[] = this.mHistory .getSearchHistory() .map((element) => new FilterItem({ text: element, menuType: Definitions.JobPickerTypes.History })); @@ -677,7 +662,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async getUserSearchQueryInput(choice: FilterItem, node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.getUserSearchQueryInput called."); + ZoweLogger.trace("JobTree.getUserSearchQueryInput called."); if (!choice) { return undefined; } @@ -703,7 +688,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async applyRegularSessionSearchLabel(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.applyRegularSessionsSearchLabel called."); + ZoweLogger.trace("JobTree.applyRegularSessionsSearchLabel called."); const choice = await this.getUserJobsMenuChoice(); const searchCriteriaObj = await this.getUserSearchQueryInput(choice, node); @@ -721,7 +706,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async handleSearchByJobId(jobId?: string): Promise { - ZoweLogger.trace("ZosJobsProvider.handleSearchByJobId called."); + ZoweLogger.trace("JobTree.handleSearchByJobId called."); const options = { prompt: vscode.l10n.t("Enter a job ID"), value: jobId, @@ -740,7 +725,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public parseJobSearchQuery(searchCriteria: string): Definitions.IJobSearchCriteria { - ZoweLogger.trace("ZosJobsProvider.parseJobSearchQuery called."); + ZoweLogger.trace("JobTree.parseJobSearchQuery called."); const searchCriteriaObj: Definitions.IJobSearchCriteria = { Owner: undefined, Prefix: undefined, @@ -769,7 +754,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public getPopulatedPickerValues(searchObj: Definitions.IJobSearchCriteria): Definitions.IJobPickerOption[] { - ZoweLogger.trace("ZosJobsProvider.getPopulatedPickerValues called."); + ZoweLogger.trace("JobTree.getPopulatedPickerValues called."); const historyPopulatedItems = this.JOB_PROPERTIES; historyPopulatedItems.forEach((prop) => { if (prop.key === "owner") { @@ -786,7 +771,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async applySavedFavoritesSearchLabel(node): Promise { - ZoweLogger.trace("ZosJobsProvider.applySavedFavoritesSearchLabel called."); + ZoweLogger.trace("JobTree.applySavedFavoritesSearchLabel called."); // executing search from saved search in favorites const searchCriteria = node.label as string; const session = node.getProfileName(); @@ -808,7 +793,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @returns {Promise} */ public async searchPrompt(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.searchPrompt called."); + ZoweLogger.trace("JobTree.searchPrompt called."); await this.checkCurrentProfile(node); let searchCriteria: string = ""; if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) { @@ -847,7 +832,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public async onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent): Promise { - ZoweLogger.trace("ZosJobsProvider.onDidChangeConfiguration called."); + ZoweLogger.trace("JobTree.onDidChangeConfiguration called."); if (e.affectsConfiguration(JobTree.persistenceSchema)) { const setting: any = { ...SettingsConfig.getDirectValue(JobTree.persistenceSchema), @@ -861,7 +846,7 @@ export class JobTree extends ZoweTreeProvider implements Types } public deleteSession(node: IZoweJobTreeNode, hideFromAllTrees?: boolean): void { - ZoweLogger.trace("ZosJobsProvider.deleteSession called."); + ZoweLogger.trace("JobTree.deleteSession called."); super.deleteSession(node, hideFromAllTrees); } @@ -872,7 +857,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param jobid - A specific jobid search item */ public createSearchLabel(owner: string, prefix: string, jobid: string, status: string): string { - ZoweLogger.trace("ZosJobsProvider.createSearchLabel called."); + ZoweLogger.trace("JobTree.createSearchLabel called."); const alphaNumeric = new RegExp("^w+$"); if (jobid && !alphaNumeric.exec(jobid.trim())) { return JobTree.JobId + jobid.toUpperCase().trim(); @@ -892,12 +877,12 @@ export class JobTree extends ZoweTreeProvider implements Types } public addFileHistory(_criteria: string): void { - ZoweLogger.trace("ZosJobsProvider.addFileHistory called."); + ZoweLogger.trace("JobTree.addFileHistory called."); throw new Error("Method not implemented."); } private async setJobStatus(node: IZoweJobTreeNode): Promise { - ZoweLogger.trace("ZosJobsProvider.setJobStatus called."); + ZoweLogger.trace("JobTree.setJobStatus called."); const jobStatusSelection = ZoweExplorerApiRegister.getJesApi(node.getProfile()).getJobsByParameters ? Constants.JOB_STATUS : Constants.JOB_STATUS_UNSUPPORTED; @@ -912,7 +897,7 @@ export class JobTree extends ZoweTreeProvider implements Types jobProperties: Definitions.IJobPickerOption[], node: IZoweJobTreeNode ): Promise { - ZoweLogger.trace("ZosJobsProvider.handleEditingMultiJobParameters called."); + ZoweLogger.trace("JobTree.handleEditingMultiJobParameters called."); const editableItems: vscode.QuickPickItem[] = [new FilterItem({ text: JobTree.submitJobQueryLabel, show: true }), Constants.SEPARATORS.BLANK]; jobProperties.forEach((prop) => { if (prop.key === "owner" && !prop.value) { @@ -949,7 +934,6 @@ export class JobTree extends ZoweTreeProvider implements Types }; this.resetJobProperties(jobProperties); - await TreeViewUtils.expandNode(node, this); return searchCriteriaObj; } default: { @@ -967,7 +951,7 @@ export class JobTree extends ZoweTreeProvider implements Types return this.handleEditingMultiJobParameters(jobProperties, node); } private resetJobProperties(jobProperties: Definitions.IJobPickerOption[]): Definitions.IJobPickerOption[] { - ZoweLogger.trace("ZosJobsProvider.resetJobProperties called."); + ZoweLogger.trace("JobTree.resetJobProperties called."); jobProperties.forEach((prop) => { if (prop.key === "owner") { prop.value = ""; @@ -988,7 +972,7 @@ export class JobTree extends ZoweTreeProvider implements Types * @param storedSearch - The original search string */ private applySearchLabelToNode(node: IZoweJobTreeNode, storedSearchObj: Definitions.IJobSearchCriteria): void { - ZoweLogger.trace("ZosJobsProvider.applySearchLabelToNode called."); + ZoweLogger.trace("JobTree.applySearchLabelToNode called."); if (storedSearchObj) { node.searchId = storedSearchObj.JobId || ""; node.owner = storedSearchObj.Owner || "*"; @@ -998,7 +982,7 @@ export class JobTree extends ZoweTreeProvider implements Types } private relabelFavoritedJob(node: IZoweJobTreeNode): IZoweJobTreeNode { - ZoweLogger.trace("ZosJobsProvider.relabelFavoritedJob called."); + ZoweLogger.trace("JobTree.relabelFavoritedJob called."); node.label = node.label.toString().substring(0, node.label.toString().lastIndexOf(")") + 1); return node; } diff --git a/packages/zowe-explorer/src/trees/job/ZoweJobNode.ts b/packages/zowe-explorer/src/trees/job/ZoweJobNode.ts index 46edc1c6a9..4d4f7dfc86 100644 --- a/packages/zowe-explorer/src/trees/job/ZoweJobNode.ts +++ b/packages/zowe-explorer/src/trees/job/ZoweJobNode.ts @@ -103,6 +103,8 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { if (this.getParent()?.label !== vscode.l10n.t("Favorites") && !SharedContext.isFavorite(this)) { this.id = this.label as string; } + } else if (this.contextValue === Constants.INFORMATION_CONTEXT) { + this.command = { command: "zowe.placeholderCommand", title: "Placeholder" }; } else if (this.job != null) { this.resourceUri = vscode.Uri.from({ scheme: ZoweScheme.Jobs, @@ -117,49 +119,36 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { * @returns {Promise} */ public async getChildren(): Promise { - const thisSessionNode = this.getSessionNode(); - ZoweLogger.trace(`ZoweJobNode.getChildren called for ${String(thisSessionNode.label)}.`); - if (this?.filter !== undefined) { - return this.children; - } + ZoweLogger.trace(`ZoweJobNode.getChildren called for ${this.label as string}.`); if (SharedContext.isSession(this) && !this.filtered && !SharedContext.isFavorite(this)) { const placeholder = new ZoweJobNode({ label: vscode.l10n.t("Use the search button to display jobs"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, - profile: thisSessionNode.getProfile(), contextOverride: Constants.INFORMATION_CONTEXT, }); - placeholder.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; return [placeholder]; } - if (!this.dirty) { + if (!this.dirty || this.filter !== undefined) { return this.children; } const elementChildren: Record = {}; if (SharedContext.isJob(this)) { // Fetch spool files under job node - const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); - const spools: zosjobs.IJobFile[] = ( - (await ZoweExplorerApiRegister.getJesApi(cachedProfile).getSpoolFiles(this.job.jobname, this.job.jobid)) ?? [] - ) - // filter out all the objects which do not seem to be correct Job File Document types - // see an issue #845 for the details - .filter((item) => !(item.id === undefined && item.ddname === undefined && item.stepname === undefined)); - if (!spools.length) { + const spools = await this.getSpoolFiles(this.job); + if (spools == null) { + return []; + } else if (!spools.length) { const noSpoolNode = new ZoweSpoolNode({ - label: vscode.l10n.t("There are no JES spool messages to display"), + label: vscode.l10n.t("No spool files found"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, profile: this.getProfile(), + contextOverride: Constants.INFORMATION_CONTEXT, }); - noSpoolNode.iconPath = undefined; - return [noSpoolNode]; + return (this.children = [noSpoolNode]); } spools.forEach((spool) => { const procstep = spool.procstep ? spool.procstep : undefined; @@ -202,20 +191,17 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { } else { // Fetch jobs under session node const jobs = await this.getJobs(this._owner, this._prefix, this._searchId, this._jobStatus); - if (jobs.length === 0) { + if (jobs == null) { + return []; + } else if (jobs.length === 0) { const noJobsNode = new ZoweJobNode({ label: vscode.l10n.t("No jobs found"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, profile: this.getProfile(), + contextOverride: Constants.INFORMATION_CONTEXT, }); - noJobsNode.contextValue = Constants.INFORMATION_CONTEXT; - noJobsNode.iconPath = undefined; - noJobsNode.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; - return [noJobsNode]; + return (this.children = [noJobsNode]); } jobs.forEach((job) => { let nodeTitle: string; @@ -371,10 +357,10 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { return this._searchId; } - private async getJobs(owner: string, prefix: string, searchId: string, status: string): Promise { + private async getJobs(owner: string, prefix: string, searchId: string, status: string): Promise { ZoweLogger.trace("ZoweJobNode.getJobs called."); - let jobsInternal: zosjobs.IJob[] = []; const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + let jobsInternal: zosjobs.IJob[] = []; try { if (this.searchId.length > 0) { jobsInternal.push(await ZoweExplorerApiRegister.getJesApi(cachedProfile).getJob(searchId)); @@ -410,12 +396,29 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { }, []); } } catch (error) { - ZoweLogger.trace("Error getting jobs from Rest API."); - await AuthUtils.errorHandling(error, cachedProfile.name, vscode.l10n.t("Retrieving response from zowe.GetJobs")); - AuthUtils.syncSessionNode((profile) => ZoweExplorerApiRegister.getJesApi(profile), this.getSessionNode()); + const updated = await AuthUtils.errorHandling(error, this.getProfileName(), vscode.l10n.t("Retrieving response from JES list API")); + AuthUtils.syncSessionNode((profile) => ZoweExplorerApiRegister.getJesApi(profile), this.getSessionNode(), updated && this); + return; } return jobsInternal; } + + private async getSpoolFiles(job: zosjobs.IJob = this.job): Promise { + ZoweLogger.trace("ZoweJobNode.getSpoolFiles called."); + const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + let spools: zosjobs.IJobFile[] = []; + try { + spools = (await ZoweExplorerApiRegister.getJesApi(cachedProfile).getSpoolFiles(job.jobname, job.jobid)) ?? []; + // filter out all the objects which do not seem to be correct Job File Document types + // see an issue #845 for the details + spools = spools.filter((item) => !(item.id === undefined && item.ddname === undefined && item.stepname === undefined)); + } catch (error) { + const updated = await AuthUtils.errorHandling(error, this.getProfileName(), vscode.l10n.t("Retrieving response from JES list API")); + AuthUtils.syncSessionNode((profile) => ZoweExplorerApiRegister.getJesApi(profile), this.getSessionNode(), updated && this); + return; + } + return spools; + } } export class ZoweSpoolNode extends ZoweJobNode { diff --git a/packages/zowe-explorer/src/trees/shared/SharedInit.ts b/packages/zowe-explorer/src/trees/shared/SharedInit.ts index b45dd82a61..daefdba139 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedInit.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedInit.ts @@ -48,6 +48,8 @@ import { CertificateWizard } from "../../utils/CertificateWizard"; import { ZosConsoleViewProvider } from "../../zosconsole/ZosConsolePanel"; export class SharedInit { + private static originalEmitZoweEvent: typeof imperative.EventProcessor.prototype.emitEvent; + public static registerCommonCommands(context: vscode.ExtensionContext, providers: Definitions.IZoweProviders): void { ZoweLogger.trace("shared.init.registerCommonCommands called."); @@ -284,6 +286,24 @@ export class SharedInit { } } + public static emitZoweEventHook(this: void, processor: imperative.EventProcessor, eventName: string): void { + if (eventName === imperative.ZoweUserEvents.ON_VAULT_CHANGED) { + Constants.IGNORE_VAULT_CHANGE = true; + } + SharedInit.originalEmitZoweEvent.call(processor, eventName); + } + + public static async onVaultChanged(this: void): Promise { + if (Constants.IGNORE_VAULT_CHANGE) { + Constants.IGNORE_VAULT_CHANGE = false; + return; + } + ZoweLogger.info(vscode.l10n.t("Changes in the credential vault detected, refreshing Zowe Explorer.")); + await ProfilesUtils.readConfigFromDisk(); + await SharedActions.refreshAll(); + ZoweExplorerApiRegister.getInstance().onVaultUpdateEmitter.fire(Validation.EventType.UPDATE); + } + public static watchConfigProfile(context: vscode.ExtensionContext): void { ZoweLogger.trace("shared.init.watchConfigProfile called."); const watchers: vscode.FileSystemWatcher[] = []; @@ -311,23 +331,28 @@ export class SharedInit { }); watcher.onDidChange(async (uri: vscode.Uri) => { ZoweLogger.info(vscode.l10n.t("Team config file updated.")); - const newProfileContents = await vscode.workspace.fs.readFile(uri); - if (newProfileContents.toString() === Constants.SAVED_PROFILE_CONTENTS.toString()) { + const newProfileContents = Buffer.from(await vscode.workspace.fs.readFile(uri)); + if (Constants.SAVED_PROFILE_CONTENTS.get(uri.fsPath)?.equals(newProfileContents)) { return; } - Constants.SAVED_PROFILE_CONTENTS = newProfileContents; + Constants.SAVED_PROFILE_CONTENTS.set(uri.fsPath, newProfileContents); void SharedActions.refreshAll(); ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter.fire(Validation.EventType.UPDATE); }); }); try { - const zoweWatcher = imperative.EventOperator.getWatcher().subscribeUser(imperative.ZoweUserEvents.ON_VAULT_CHANGED, async () => { - ZoweLogger.info(vscode.l10n.t("Changes in the credential vault detected, refreshing Zowe Explorer.")); - await ProfilesUtils.readConfigFromDisk(); - await SharedActions.refreshAll(); - ZoweExplorerApiRegister.getInstance().onVaultUpdateEmitter.fire(Validation.EventType.UPDATE); - }); + // Workaround to skip ON_VAULT_CHANGED events triggered by ZE and not by external app + // TODO: Remove this hack once https://github.com/zowe/zowe-cli/issues/2279 is implemented + SharedInit.originalEmitZoweEvent = (imperative.EventProcessor.prototype as any).emitZoweEvent; + (imperative.EventProcessor.prototype as any).emitZoweEvent = function (eventName: string): void { + SharedInit.emitZoweEventHook(this, eventName); + }; + + const zoweWatcher = imperative.EventOperator.getWatcher().subscribeUser( + imperative.ZoweUserEvents.ON_VAULT_CHANGED, + SharedInit.onVaultChanged + ); context.subscriptions.push(new vscode.Disposable(zoweWatcher.close.bind(zoweWatcher))); } catch (err) { Gui.errorMessage("Unable to watch for vault changes. " + JSON.stringify(err)); @@ -354,11 +379,11 @@ export class SharedInit { const theTreeView = theProvider.getTreeView(); context.subscriptions.push(theTreeView); context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(async (e) => SharedInit.setupRemoteWorkspaceFolders(e))); - theTreeView.onDidCollapseElement(async (e) => { - await theProvider.flipState(e.element, false); + theTreeView.onDidCollapseElement((e) => { + theProvider.flipState(e.element, false); }); - theTreeView.onDidExpandElement(async (e) => { - await theProvider.flipState(e.element, true); + theTreeView.onDidExpandElement((e) => { + theProvider.flipState(e.element, true); }); } diff --git a/packages/zowe-explorer/src/trees/uss/USSActions.ts b/packages/zowe-explorer/src/trees/uss/USSActions.ts index ef365dac02..0f89d1be0c 100644 --- a/packages/zowe-explorer/src/trees/uss/USSActions.ts +++ b/packages/zowe-explorer/src/trees/uss/USSActions.ts @@ -13,13 +13,12 @@ import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; import * as zosfiles from "@zowe/zos-files-for-zowe-sdk"; -import { Gui, imperative, Validation, IZoweUSSTreeNode, Types } from "@zowe/zowe-explorer-api"; +import { Gui, imperative, IZoweUSSTreeNode, Types } from "@zowe/zowe-explorer-api"; import { isBinaryFileSync } from "isbinaryfile"; import { USSAttributeView } from "./USSAttributeView"; import { USSFileStructure } from "./USSFileStructure"; import { ZoweUSSNode } from "./ZoweUSSNode"; import { Constants } from "../../configuration/Constants"; -import { Profiles } from "../../configuration/Profiles"; import { ZoweExplorerApiRegister } from "../../extending/ZoweExplorerApiRegister"; import { LocalFileManagement } from "../../management/LocalFileManagement"; import { ZoweLogger } from "../../tools/ZoweLogger"; @@ -36,16 +35,12 @@ export class USSActions { * @param {ussTree} ussFileProvider - Current ussTree used to populate the TreeView * @returns {Promise} */ - public static async createUSSNode( - node: IZoweUSSTreeNode, - ussFileProvider: Types.IZoweUSSTreeType, - nodeType: string, - isTopLevel?: boolean - ): Promise { + public static async createUSSNode(node: IZoweUSSTreeNode, ussFileProvider: Types.IZoweUSSTreeType, nodeType: string): Promise { ZoweLogger.trace("uss.actions.createUSSNode called."); await ussFileProvider.checkCurrentProfile(node); let filePath = ""; - if (SharedContext.isSession(node)) { + const isTopLevel = SharedContext.isSession(node); + if (isTopLevel && node.fullPath?.length === 0) { const filePathOptions: vscode.InputBoxOptions = { placeHolder: vscode.l10n.t({ message: "{0} location", @@ -60,17 +55,25 @@ export class USSActions { value: node.tooltip as string, }; filePath = await Gui.showInputBox(filePathOptions); + node.fullPath = filePath; } else { filePath = node.fullPath; } + + if (filePath == null || filePath.length === 0) { + return; + } + const nameOptions: vscode.InputBoxOptions = { placeHolder: vscode.l10n.t("Name of file or directory"), }; const name = await Gui.showInputBox(nameOptions); if (name && filePath) { try { - filePath = `${filePath}/${name}`; - const uri = node.resourceUri.with({ path: path.posix.join(node.resourceUri.path, name) }); + filePath = path.posix.join(filePath, name); + const uri = node.resourceUri.with({ + path: isTopLevel ? path.posix.join(node.resourceUri.path, filePath) : path.posix.join(node.resourceUri.path, name), + }); await ZoweExplorerApiRegister.getUssApi(node.getProfile()).create(filePath, nodeType); if (nodeType === "file") { await vscode.workspace.fs.writeFile(uri, new Uint8Array()); @@ -82,6 +85,7 @@ export class USSActions { } else { ussFileProvider.refreshElement(node); } + const newNode = await node.getChildren().then((children) => children.find((child) => child.label === name) as ZoweUSSNode); await ussFileProvider.getTreeView().reveal(node, { select: true, focus: true }); ussFileProvider.getTreeView().reveal(newNode, { select: true, focus: true }); @@ -117,24 +121,6 @@ export class USSActions { } } - public static async createUSSNodeDialog(node: IZoweUSSTreeNode, ussFileProvider: Types.IZoweUSSTreeType): Promise { - ZoweLogger.trace("uss.actions.createUSSNodeDialog called."); - await ussFileProvider.checkCurrentProfile(node); - if ( - Profiles.getInstance().validProfile === Validation.ValidationType.VALID || - Profiles.getInstance().validProfile === Validation.ValidationType.UNVERIFIED - ) { - const quickPickOptions: vscode.QuickPickOptions = { - placeHolder: `What would you like to create at ${node.fullPath}?`, - ignoreFocusOut: true, - canPickMany: false, - }; - const type = await Gui.showQuickPick([Constants.USS_DIR_CONTEXT, "File"], quickPickOptions); - const isTopLevel = true; - return USSActions.createUSSNode(node, ussFileProvider, type, isTopLevel); - } - } - /** * Marks file as deleted from disk * diff --git a/packages/zowe-explorer/src/trees/uss/USSInit.ts b/packages/zowe-explorer/src/trees/uss/USSInit.ts index 638c98a1b9..91bba48005 100644 --- a/packages/zowe-explorer/src/trees/uss/USSInit.ts +++ b/packages/zowe-explorer/src/trees/uss/USSInit.ts @@ -27,7 +27,7 @@ export class USSInit { * @export */ public static async createUSSTree(log: imperative.Logger): Promise { - ZoweLogger.trace("uss.USSTree.createUSSTree called."); + ZoweLogger.trace("USSInit.createUSSTree called."); const tree = new USSTree(); await tree.initializeFavorites(log); await tree.addSession(); @@ -35,7 +35,7 @@ export class USSInit { } public static async initUSSProvider(context: vscode.ExtensionContext): Promise { - ZoweLogger.trace("init.initUSSProvider called."); + ZoweLogger.trace("USSInit.initUSSProvider called."); context.subscriptions.push(vscode.workspace.registerFileSystemProvider(ZoweScheme.USS, UssFSProvider.instance, { isCaseSensitive: true })); const ussFileProvider: USSTree = await USSInit.createUSSTree(ZoweLogger.log); diff --git a/packages/zowe-explorer/src/trees/uss/USSTree.ts b/packages/zowe-explorer/src/trees/uss/USSTree.ts index 3cae74470b..f0098ac766 100644 --- a/packages/zowe-explorer/src/trees/uss/USSTree.ts +++ b/packages/zowe-explorer/src/trees/uss/USSTree.ts @@ -495,8 +495,8 @@ export class USSTree extends ZoweTreeProvider implements Types collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile, + contextOverride: Constants.USS_SESSION_CONTEXT, }); - node.contextValue = Constants.USS_SESSION_CONTEXT; await this.refreshHomeProfileContext(node); const icon = IconGenerator.getIconByNode(node); if (icon) { @@ -676,9 +676,7 @@ export class USSTree extends ZoweTreeProvider implements Types */ public async getAllLoadedItems(): Promise { ZoweLogger.trace("USSTree.getAllLoadedItems called."); - if (this.log) { - ZoweLogger.debug(vscode.l10n.t("Prompting the user to choose a member from the filtered list")); - } + ZoweLogger.debug(vscode.l10n.t("Prompting the user to choose a member from the filtered list")); const loadedNodes: IZoweUSSTreeNode[] = []; const sessions = await this.getChildren(); @@ -691,7 +689,7 @@ export class USSTree extends ZoweTreeProvider implements Types const children = nodeToCheck.children; if (children.length !== 0) { for (const child of children) { - await checkForChildren(child as IZoweUSSTreeNode); + await checkForChildren(child); } } loadedNodes.push(nodeToCheck); @@ -715,13 +713,11 @@ export class USSTree extends ZoweTreeProvider implements Types */ public async filterPrompt(node: IZoweUSSTreeNode): Promise { ZoweLogger.trace("USSTree.filterPrompt called."); - if (this.log) { - ZoweLogger.debug(vscode.l10n.t("Prompting the user for a USS path")); - } await this.checkCurrentProfile(node); if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) { let remotepath: string; if (SharedContext.isSessionNotFav(node)) { + ZoweLogger.debug(vscode.l10n.t("Prompting the user for a USS path")); if (this.mHistory.getSearchHistory().length > 0) { const createPick = new FilterDescriptor(USSTree.defaultDialogText); const items: vscode.QuickPickItem[] = this.mHistory.getSearchHistory().map((element) => new FilterItem({ text: element })); @@ -982,9 +978,8 @@ export class USSTree extends ZoweTreeProvider implements Types collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode, parentPath: parentNode.fullPath, + contextOverride: Constants.INFORMATION_CONTEXT, }); - infoNode.contextValue = Constants.INFORMATION_CONTEXT; - infoNode.iconPath = undefined; return [infoNode]; } } catch (error) { @@ -1016,13 +1011,13 @@ export class USSTree extends ZoweTreeProvider implements Types for (const favorite of favsForProfile) { // If profile and session already exists for favorite node, add to updatedFavsForProfile and go to next array item if (favorite.getProfile() && favorite.getSession()) { - updatedFavsForProfile.push(favorite as IZoweUSSTreeNode); + updatedFavsForProfile.push(favorite); continue; } // If no profile/session for favorite node yet, then add session and profile to favorite node: favorite.setProfileToChoice(profile); favorite.setSessionToChoice(session); - updatedFavsForProfile.push(favorite as IZoweUSSTreeNode); + updatedFavsForProfile.push(favorite); } // This updates the profile node's children in the this.mFavorites array, as well. return updatedFavsForProfile; @@ -1123,11 +1118,11 @@ export class USSTree extends ZoweTreeProvider implements Types } const isFullPathChild: boolean = SharedUtils.checkIfChildPath(node.fullPath, fullPath); if (isFullPathChild) { - return this.findMatchInLoadedChildren(node as IZoweUSSTreeNode, fullPath); + return this.findMatchInLoadedChildren(node, fullPath); } } } - return match as IZoweUSSTreeNode; + return match; } public async openWithEncoding(node: IZoweUSSTreeNode, encoding?: ZosEncoding): Promise { diff --git a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts index 5931fc94cc..f7f7951131 100644 --- a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts +++ b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts @@ -112,6 +112,8 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { }); if (isSession) { UssFSProvider.instance.createDirectory(this.resourceUri); + } else if (this.contextValue === Constants.INFORMATION_CONTEXT) { + this.command = { command: "zowe.placeholderCommand", title: "Placeholder" }; } else if (this.collapsibleState === vscode.TreeItemCollapsibleState.None) { this.command = { command: "vscode.open", title: "", arguments: [this.resourceUri] }; } @@ -177,7 +179,7 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { * @returns {Promise} */ public async getChildren(): Promise { - ZoweLogger.trace("ZoweUSSNode.getChildren called."); + ZoweLogger.trace(`ZoweUSSNode.getChildren called for ${this.label as string}.`); if ((!this.fullPath && SharedContext.isSession(this)) || SharedContext.isDocument(this)) { const placeholder = new ZoweUSSNode({ label: vscode.l10n.t("Use the search button to list USS files"), @@ -185,11 +187,7 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { parentNode: this, contextOverride: Constants.INFORMATION_CONTEXT, }); - placeholder.command = { - command: "zowe.placeholderCommand", - title: "Placeholder", - }; - return [placeholder]; + return (this.children = [placeholder]); } if (!this.dirty) { @@ -205,35 +203,10 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { } // Get the list of files/folders at the given USS path and handle any errors - let response: zosfiles.IZosFilesResponse; - const sessNode = this.getSessionNode(); - let nodeProfile; - try { - const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); - if (!ZoweExplorerApiRegister.getUssApi(cachedProfile).getSession(cachedProfile)) { - throw new imperative.ImperativeError({ - msg: vscode.l10n.t("Profile auth error"), - additionalDetails: vscode.l10n.t("Profile is not authenticated, please log in to continue"), - errorCode: `${imperative.RestConstants.HTTP_STATUS_401 as number}`, - }); - } - nodeProfile = cachedProfile; - if (SharedContext.isSession(this)) { - response = await UssFSProvider.instance.listFiles( - nodeProfile, - SharedContext.isFavorite(this) - ? this.resourceUri - : this.resourceUri.with({ - path: path.posix.join(this.resourceUri.path, this.fullPath), - }) - ); - } else { - response = await UssFSProvider.instance.listFiles(nodeProfile, this.resourceUri); - } - } catch (err) { - await AuthUtils.errorHandling(err, this.label.toString(), vscode.l10n.t("Retrieving response from uss-file-list")); - AuthUtils.syncSessionNode((profile) => ZoweExplorerApiRegister.getUssApi(profile), sessNode); - return this.children; + const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + const response = await this.getUssFiles(cachedProfile); + if (!response.success) { + return []; } // If search path has changed, invalidate all children @@ -271,8 +244,8 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { collapsibleState: collapseState, parentNode: this, parentPath: this.fullPath, - profile: nodeProfile, - encoding: isDir ? undefined : await this.getEncodingInMap(`${this.fullPath}/${item.name as string}`), + profile: cachedProfile, + encoding: isDir ? undefined : this.getEncodingInMap(`${this.fullPath}/${item.name as string}`), }); if (isDir) { // Create an entry for the USS folder if it doesn't exist. @@ -684,4 +657,32 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { await AuthUtils.errorHandling(error, this.label.toString(), vscode.l10n.t("Error uploading files")); } } + + private async getUssFiles(profile: imperative.IProfileLoaded): Promise { + try { + if (!ZoweExplorerApiRegister.getUssApi(profile).getSession(profile)) { + throw new imperative.ImperativeError({ + msg: vscode.l10n.t("Profile auth error"), + additionalDetails: vscode.l10n.t("Profile is not authenticated, please log in to continue"), + errorCode: `${imperative.RestConstants.HTTP_STATUS_401 as number}`, + }); + } + if (SharedContext.isSession(this)) { + return await UssFSProvider.instance.listFiles( + profile, + SharedContext.isFavorite(this) + ? this.resourceUri + : this.resourceUri.with({ + path: path.posix.join(this.resourceUri.path, this.fullPath), + }) + ); + } else { + return await UssFSProvider.instance.listFiles(profile, this.resourceUri); + } + } catch (error) { + const updated = await AuthUtils.errorHandling(error, this.getProfileName(), vscode.l10n.t("Retrieving response from USS list API")); + AuthUtils.syncSessionNode((prof) => ZoweExplorerApiRegister.getUssApi(prof), this.getSessionNode(), updated && this); + return { success: false, commandResponse: null }; + } + } } diff --git a/packages/zowe-explorer/src/utils/AuthUtils.ts b/packages/zowe-explorer/src/utils/AuthUtils.ts index 0d743c8587..93c7dcfbed 100644 --- a/packages/zowe-explorer/src/utils/AuthUtils.ts +++ b/packages/zowe-explorer/src/utils/AuthUtils.ts @@ -14,6 +14,7 @@ import * as vscode from "vscode"; import { imperative, Gui, MainframeInteraction, IZoweTreeNode } from "@zowe/zowe-explorer-api"; import { Constants } from "../configuration/Constants"; import { ZoweLogger } from "../tools/ZoweLogger"; +import { SharedTreeProviders } from "../trees/shared/SharedTreeProviders"; export class AuthUtils { /************************************************************************************************************* @@ -22,7 +23,7 @@ export class AuthUtils { * @param {label} - additional information such as profile name, credentials, messageID etc * @param {moreInfo} - additional/customized error messages *************************************************************************************************************/ - public static async errorHandling(errorDetails: Error | string, label?: string, moreInfo?: string): Promise { + public static async errorHandling(errorDetails: Error | string, label?: string, moreInfo?: string): Promise { // Use util.inspect instead of JSON.stringify to handle circular references // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ZoweLogger.error(`${errorDetails.toString()}\n` + util.inspect({ errorDetails, label, moreInfo }, { depth: null })); @@ -38,7 +39,7 @@ export class AuthUtils { if (prof.profName === label.trim()) { const filePath = prof.profLoc.osLoc[0]; await Constants.PROFILES_CACHE.openConfigFile(filePath); - return; + return false; } } } else if ( @@ -65,7 +66,7 @@ export class AuthUtils { } } const checkCredsButton = vscode.l10n.t("Update Credentials"); - await Gui.errorMessage(errMsg, { + const creds = await Gui.errorMessage(errMsg, { items: [checkCredsButton], vsCodeOpts: { modal: true }, }).then(async (selection) => { @@ -73,13 +74,13 @@ export class AuthUtils { Gui.showMessage(vscode.l10n.t("Operation Cancelled")); return; } - await Constants.PROFILES_CACHE.promptCredentials(label.trim(), true); + return Constants.PROFILES_CACHE.promptCredentials(label.trim(), true); }); - return; + return creds != null ? true : false; } } if (errorDetails.toString().includes("Could not find profile")) { - return; + return false; } if (moreInfo === undefined) { moreInfo = errorDetails.toString().includes("Error") ? "" : "Error: "; @@ -88,6 +89,7 @@ export class AuthUtils { } // Try to keep message readable since VS Code doesn't support newlines in error messages Gui.errorMessage(moreInfo + errorDetails.toString().replace(/\n/g, " | ")); + return false; } /** @@ -118,7 +120,8 @@ export class AuthUtils { */ public static syncSessionNode( getCommonApi: (profile: imperative.IProfileLoaded) => MainframeInteraction.ICommon, - sessionNode: IZoweTreeNode + sessionNode: IZoweTreeNode, + nodeToRefresh?: IZoweTreeNode ): void { ZoweLogger.trace("ProfilesUtils.syncSessionNode called."); @@ -135,6 +138,10 @@ export class AuthUtils { sessionNode.setProfileToChoice(profile); const session = getCommonApi(profile).getSession(); sessionNode.setSessionToChoice(session); + if (nodeToRefresh) { + nodeToRefresh.dirty = true; + void nodeToRefresh.getChildren().then(() => SharedTreeProviders.getProviderForNode(nodeToRefresh).refreshElement(nodeToRefresh)); + } } /** diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index b951b3fca0..9e1a87b569 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -337,7 +337,12 @@ export class ProfilesUtils { Gui.warningMessage(schemaWarning); ZoweLogger.warn(schemaWarning); } - Constants.CONFIG_PATH = rootPath ? rootPath : FileManagement.getZoweDir(); + Constants.SAVED_PROFILE_CONTENTS.clear(); + for (const layer of mProfileInfo.getTeamConfig().layers) { + if (layer.exists) { + Constants.SAVED_PROFILE_CONTENTS.set(vscode.Uri.file(layer.path).fsPath, fs.readFileSync(layer.path)); + } + } ZoweLogger.info(`Zowe Explorer is using the team configuration file "${mProfileInfo.getTeamConfig().configName}"`); const layers = mProfileInfo.getTeamConfig().layers || []; const layerSummary = layers.map( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2980afffeb..5a16f07ae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5126,8 +5126,8 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true - /cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + /cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} dev: true @@ -6521,7 +6521,7 @@ packages: fast-content-type-parse: 1.1.0 fast-json-stringify: 5.16.0 find-my-way: 8.2.2 - light-my-request: 5.13.0 + light-my-request: 5.14.0 pino: 9.2.0 process-warning: 3.0.0 proxy-addr: 2.0.7 @@ -8700,10 +8700,10 @@ packages: immediate: 3.0.6 dev: true - /light-my-request@5.13.0: - resolution: {integrity: sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==} + /light-my-request@5.14.0: + resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==} dependencies: - cookie: 0.6.0 + cookie: 0.7.2 process-warning: 3.0.0 set-cookie-parser: 2.6.0 dev: true