Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: FileSystemProvider remote lookup & change detection #3040

Merged
merged 35 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9d086db
refactor: Ignore remote workspace folders for config/profile init
traeok Jul 31, 2024
db6abfd
feat(ds/fsp): Handle remote lookup for data set URIs
traeok Aug 1, 2024
6a9211a
wip(uss/fsp): Remote lookup support for USS URIs
traeok Aug 1, 2024
50133f8
refactor(fsp): Improve path and error handling
traeok Aug 2, 2024
5876b83
refactor(fsp): Use optional chaining for API items
traeok Aug 5, 2024
06c5794
feat: update 'stat' impl. to detect changes to data sets
traeok Aug 6, 2024
5a4fe89
wip: change detection for 'stat' in USS FSP, resolve tests
traeok Aug 6, 2024
1a0553d
refactor: make 'lookup' public in BaseProvider; fix failing tests
traeok Aug 7, 2024
71fce70
Merge branch 'next' into refactor/fsp-improvements
traeok Aug 7, 2024
e69f2dc
refactor: address SonarCloud issues
traeok Aug 7, 2024
e41e9a9
fix: Failing tests in Profiles
traeok Aug 7, 2024
d498334
chore: update changelogs
traeok Aug 7, 2024
15b07a7
refactor: fn to handle new remote workspaces; add postInit param to i…
traeok Aug 7, 2024
1e747a9
fix(pkg): Update lint scripts to work cross-platform
traeok Aug 7, 2024
7f12beb
tests: SharedInit.setupRemoteWorkspaceFolders, support custom descrip…
traeok Aug 7, 2024
69237a1
feat: support fetch query in DS/USS FSP stat calls
traeok Aug 7, 2024
e924c9e
tests: stat patch coverage for Data Set FSP
traeok Aug 8, 2024
e4ec266
tests: DataSet FSP cases; refactor FileNotFound error
traeok Aug 8, 2024
ee7af0d
lint: run eslint and prettier
traeok Aug 8, 2024
4d40cff
fix(uss/fsp): Remove instanceof check in fetchEntries
traeok Aug 8, 2024
24a22dc
chore: remove unused import
traeok Aug 8, 2024
f817f1e
tests: add jest-mock-vscode; use real FileSystemError class
traeok Aug 9, 2024
7dd407e
refactor: UssFSProvider.fetchEntries; tests: file cases for fn
traeok Aug 9, 2024
6783a46
Merge branch 'next' into refactor/fsp-improvements
traeok Aug 12, 2024
f22c379
refactor: remove unused import in FSP test file
traeok Aug 12, 2024
a8bcda0
fix(uss/fsp): update logic for fetchEntries folder detection
traeok Aug 12, 2024
0d09f22
tests(uss/fsp): [wip] resolve failing test cases
traeok Aug 12, 2024
1b61b4f
fix(tests): Resolve failing tests, add folder case for fetchEntries
traeok Aug 13, 2024
eb510e5
refactor: use workspaceRootPath fn, run lint/prettier
traeok Aug 14, 2024
6e3c95c
Merge branch 'next' into refactor/fsp-improvements
traeok Aug 14, 2024
d3411a1
fix: update remaining refs to workspaceFolders
traeok Aug 14, 2024
0a902b6
refactor: `workspaceRootPath` -> `workspaceRoot`
traeok Aug 14, 2024
1130366
Merge branch 'next' into refactor/fsp-improvements
traeok Aug 20, 2024
aa7f8d5
Merge branch 'next' into refactor/fsp-improvements
JillieBeanSim Aug 20, 2024
5a47fe4
tests: Bump patch coverage in ZoweUSSNode
traeok Aug 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"jest": "^29.3.1",
"jest-html-reporter": "^3.7.0",
"jest-junit": "^15.0.0",
"jest-mock-vscode": "^3.0.5",
"jest-stare": "^2.4.1",
"madge": "^7.0.0",
"mocha": "^10.2.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/zowe-explorer-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t
- **Breaking:** Consolidated WebView API options into a single object (`WebViewOpts` type), both for developer convenience and to support future options.
- Enhanced the `ZoweVsCodeExtension.loginWithBaseProfile` and `ZoweVsCodeExtension.logoutWithBaseProfile` methods to store SSO token in parent profile when nested profiles are in use. [#2264](https://github.com/zowe/zowe-explorer-vscode/issues/2264)
- **Next Breaking:** Changed return type of `ZoweVsCodeExtension.logoutWithBaseProfile` method from `void` to `boolean` to indicate whether logout was successful.
- Renamed the `_lookup` function to `lookup` in the `BaseProvider` class and updated its access to public, allowing extenders to look up resources outside of the provider implementations. The `_lookup` function is still accessible, but now deprecated in favor of the public `lookup` function. [#3040](https://github.com/zowe/zowe-explorer-vscode/pull/3040)
- **Breaking:** Removed the `MemberEntry` filesystem class, in favor of using the `DsEntry` class with `isMember` set to `true`.
- Changed `TableViewProvider.setTableView` function to be asynchronous for more optimized data updates.
- Updated `Table.Conditional` and `Table.Callback` types to support multi-row callbacks.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ describe("diffUseRemote", () => {
describe("exists", () => {
function getBlockMocks(): { [key: string]: jest.SpyInstance<any> } {
return {
lookupMock: jest.spyOn((BaseProvider as any).prototype, "_lookup"),
lookupMock: jest.spyOn((BaseProvider as any).prototype, "lookup"),
};
}

Expand Down Expand Up @@ -192,7 +192,7 @@ describe("getEncodingForFile", () => {
it("gets the encoding for a file entry", () => {
const prov = new (BaseProvider as any)();
const fileEntry = { ...globalMocks.fileFsEntry, encoding: { kind: "text" } };
const _lookupAsFileMock = jest.spyOn(prov, "_lookup").mockReturnValueOnce(fileEntry);
const _lookupAsFileMock = jest.spyOn(prov, "lookup").mockReturnValueOnce(fileEntry);
expect(prov.getEncodingForFile(globalMocks.testFileUri)).toStrictEqual({ kind: "text" });
_lookupAsFileMock.mockRestore();
});
Expand Down Expand Up @@ -233,7 +233,7 @@ describe("cacheOpenedUri", () => {
describe("invalidateFileAtUri", () => {
it("returns true if it was able to invalidate the URI", () => {
const fileEntry = { ...globalMocks.fileFsEntry };
jest.spyOn((BaseProvider as any).prototype, "_lookup").mockReturnValueOnce(fileEntry);
jest.spyOn((BaseProvider as any).prototype, "lookup").mockReturnValueOnce(fileEntry);
const prov = new (BaseProvider as any)();
prov.root = new DirEntry("");
prov.root.entries.set("file.txt", fileEntry);
Expand All @@ -256,7 +256,7 @@ describe("invalidateFileAtUri", () => {
describe("invalidateDirAtUri", () => {
it("returns true if it was able to invalidate the URI", () => {
const folderEntry = { ...globalMocks.folderFsEntry };
jest.spyOn((BaseProvider as any).prototype, "_lookup").mockReturnValueOnce(folderEntry);
jest.spyOn((BaseProvider as any).prototype, "lookup").mockReturnValueOnce(folderEntry);
const prov = new (BaseProvider as any)();
prov.root = new DirEntry("");
prov.root.entries.set("folder", folderEntry);
Expand All @@ -281,7 +281,7 @@ describe("setEncodingForFile", () => {
it("sets the encoding for a file entry", () => {
const prov = new (BaseProvider as any)();
const fileEntry = { ...globalMocks.fileFsEntry, encoding: undefined };
const _lookupAsFileMock = jest.spyOn(prov, "_lookup").mockReturnValueOnce(fileEntry);
const _lookupAsFileMock = jest.spyOn(prov, "lookup").mockReturnValueOnce(fileEntry);
prov.setEncodingForFile(globalMocks.testFileUri, { kind: "text" });
expect(fileEntry.encoding).toStrictEqual({ kind: "text" });
_lookupAsFileMock.mockRestore();
Expand Down Expand Up @@ -314,20 +314,20 @@ describe("_updateResourceInEditor", () => {
});
});

describe("_lookup", () => {
describe("lookup", () => {
it("returns a valid file entry if it exists in the file system", () => {
const prov = new (BaseProvider as any)();
prov.root = new DirEntry("");
prov.root.entries.set("file.txt", { ...globalMocks.fileFsEntry });
const entry = prov._lookup(globalMocks.testFileUri);
const entry = prov.lookup(globalMocks.testFileUri);
expect(entry).toStrictEqual(globalMocks.fileFsEntry);
});

it("returns a valid folder entry if it exists in the file system", () => {
const prov = new (BaseProvider as any)();
prov.root = new DirEntry("");
prov.root.entries.set("folder", { ...globalMocks.folderFsEntry });
const entry = prov._lookup(globalMocks.testFolderUri);
const entry = prov.lookup(globalMocks.testFolderUri);
expect(entry).toStrictEqual(globalMocks.folderFsEntry);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
*
*/

import { DsEntry, MemberEntry, PdsEntry, FsDatasetsUtils } from "../../../../src";
import { DsEntry, PdsEntry, FsDatasetsUtils } from "../../../../src";

describe("isDsEntry", () => {
it("returns true if value is a DsEntry", () => {
const entry = new DsEntry("TEST.DS");
const entry = new DsEntry("TEST.DS", false);
expect(FsDatasetsUtils.isDsEntry(entry)).toBe(true);
});

Expand All @@ -24,14 +24,14 @@ describe("isDsEntry", () => {
});

describe("isMemberEntry", () => {
it("returns true if value is a MemberEntry", () => {
const entry = new MemberEntry("TESTMEMB");
it("returns true if value is a PDS member", () => {
const entry = new DsEntry("TEST", true);
expect(FsDatasetsUtils.isMemberEntry(entry)).toBe(true);
});

it("returns false if value is not a MemberEntry", () => {
const pds = new PdsEntry("TEST.PDS");
expect(FsDatasetsUtils.isMemberEntry(pds)).toBe(false);
it("returns false if value is not a PDS member", () => {
const entry = new DsEntry("TEST.PS", false);
expect(FsDatasetsUtils.isMemberEntry(entry)).toBe(false);
});
});

Expand All @@ -42,7 +42,7 @@ describe("isPdsEntry", () => {
});

it("returns false if value is not a PdsEntry", () => {
const ds = new DsEntry("TEST.DS");
const ds = new DsEntry("TEST.DS", false);
expect(FsDatasetsUtils.isPdsEntry(ds)).toBe(false);
});
});
7 changes: 4 additions & 3 deletions packages/zowe-explorer-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@
"build": "pnpm check-cli && pnpm copy-secrets && pnpm clean && pnpm license && tsc -p ./ && pnpm madge",
"test:unit": "jest \".*__tests__.*\\.unit\\.test\\.ts\" --coverage",
"test": "pnpm test:unit",
"lint": "concurrently -n \"_eslint_,prettier\" \"pnpm lint:all\" \"pnpm pretty; pnpm pretty:check\"",
"lint:all": "pnpm lint:src; pnpm lint:test; pnpm lint:html",
"lint": "concurrently -n \"_eslint_,prettier\" \"pnpm lint:all\" \"pnpm lint:pretty\"",
"lint:all": "pnpm run \"/^lint:[hst].*/\"",
"lint:html": "(mkdir -p \"results\" || true) && eslint . --format html > results/eslint.html",
"lint:pretty": "prettier --write . && prettier --check .",
"lint:src": "eslint --format stylish src/**/*.ts",
"lint:test": "eslint --format stylish __tests__/**/*.ts",
"lint:html": "mkdir -p results && eslint . --format html > results/eslint.html",
"madge": "madge -c --no-color --no-spinner --exclude __mocks__ --extensions js,ts src/",
"pretty": "prettier --write .",
"pretty:check": "prettier --check .",
Expand Down
49 changes: 33 additions & 16 deletions packages/zowe-explorer-api/src/fs/BaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
}

public exists(uri: vscode.Uri): boolean {
const entry = this._lookup(uri, true);
const entry = this.lookup(uri, true);
return entry != null;
}

Expand Down Expand Up @@ -129,7 +129,7 @@
* @param uri the URI whose data should be invalidated
*/
public invalidateFileAtUri(uri: vscode.Uri): boolean {
const entry = this._lookup(uri, true);
const entry = this.lookup(uri, true);
if (!FsAbstractUtils.isFileEntry(entry)) {
return false;
}
Expand All @@ -146,7 +146,7 @@
* @param uri the URI whose data should be invalidated
*/
public invalidateDirAtUri(uri: vscode.Uri): boolean {
const entry = this._lookup(uri, true);
const entry = this.lookup(uri, true);
if (!FsAbstractUtils.isDirectoryEntry(entry)) {
return false;
}
Expand All @@ -162,7 +162,7 @@
* @returns The encoding for the file
*/
public getEncodingForFile(uri: vscode.Uri): ZosEncoding {
const entry = this._lookup(uri, false) as FileEntry | DirEntry;
const entry = this.lookup(uri, false) as FileEntry | DirEntry;
if (FsAbstractUtils.isDirectoryEntry(entry)) {
return undefined;
}
Expand All @@ -185,7 +185,7 @@
* @param uri The URI that is open in an editor tab
*/
protected async _updateResourceInEditor(uri: vscode.Uri): Promise<void> {
const entry = this._lookup(uri, true);
const entry = this.lookup(uri, true);
if (!FsAbstractUtils.isFileEntry(entry)) {
return;
}
Expand Down Expand Up @@ -239,7 +239,7 @@
* @param newUssPath The new path for this entry in USS
*/
protected async _relocateEntry(oldUri: vscode.Uri, newUri: vscode.Uri, newUssPath: string): Promise<void> {
const entry = this._lookup(oldUri, true);
const entry = this.lookup(oldUri, true);
if (!entry) {
return;
}
Expand Down Expand Up @@ -285,7 +285,7 @@
}

// get the entry data before deleting the URI
const entryToDelete = this._lookup(uri, false);
const entryToDelete = this.lookup(uri, false);

return {
entryToDelete,
Expand Down Expand Up @@ -367,8 +367,23 @@
* VScode utility functions for entries in the provider:
*/

/**
* @deprecated Use `lookup` instead.
* @param uri The resource URI to lookup within the provider.
* @param silent `true` to return falsy values if the resource doesn't exist; `false` to throw errors
* @returns The entry within the provider, or `undefined` if it doesn't exist
*/
protected _lookup(uri: vscode.Uri, silent: boolean = false): IFileSystemEntry | undefined {
if (uri.path === "/") {
return this.lookup(uri, silent);

Check warning on line 377 in packages/zowe-explorer-api/src/fs/BaseProvider.ts

View check run for this annotation

Codecov / codecov/patch

packages/zowe-explorer-api/src/fs/BaseProvider.ts#L377

Added line #L377 was not covered by tests
}

/**
* @param uri The resource URI to lookup within the provider.
* @param silent `true` to return falsy values if the resource doesn't exist; `false` to throw errors
* @returns The entry within the provider, or `undefined` if it doesn't exist
*/
public lookup(uri: vscode.Uri, silent: boolean = false): IFileSystemEntry | undefined {
if (uri.path === "/" || uri.path === ".") {
return this.root;
}

Expand Down Expand Up @@ -396,11 +411,12 @@
}

protected _lookupAsDirectory(uri: vscode.Uri, silent: boolean): DirEntry {
const entry = this._lookup(uri, silent);
if (FsAbstractUtils.isDirectoryEntry(entry)) {
return entry;
const entry = this.lookup(uri, silent);
if (entry != null && !FsAbstractUtils.isDirectoryEntry(entry) && !silent) {
throw vscode.FileSystemError.FileNotADirectory(uri);
}
throw vscode.FileSystemError.FileNotADirectory(uri);

return entry as DirEntry;
}

protected _createFile(uri: vscode.Uri, options?: { overwrite: boolean }): FileEntry {
Expand Down Expand Up @@ -429,11 +445,12 @@
}

protected _lookupAsFile(uri: vscode.Uri, opts?: { silent?: boolean }): FileEntry {
const entry = this._lookup(uri, opts?.silent ?? false);
if (FsAbstractUtils.isFileEntry(entry)) {
return entry;
const entry = this.lookup(uri, opts?.silent ?? false);
if (entry != null && !FsAbstractUtils.isFileEntry(entry) && !opts?.silent) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}
throw vscode.FileSystemError.FileIsADirectory(uri);

return entry;
}

protected _lookupParentDirectory(uri: vscode.Uri, silent?: boolean): DirEntry {
Expand Down
6 changes: 2 additions & 4 deletions packages/zowe-explorer-api/src/fs/types/datasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,15 @@ interface DsEntryProps {
export class DsEntry extends FileEntry implements DsEntryProps {
public metadata: DsEntryMetadata;

public constructor(name: string) {
public constructor(name: string, public isMember: boolean = false) {
super(name);
}

public stats: Types.DatasetStats;
}

export class MemberEntry extends DsEntry {}
traeok marked this conversation as resolved.
Show resolved Hide resolved

export class PdsEntry extends DirEntry implements DsEntryProps {
public entries: Map<string, MemberEntry>;
public entries: Map<string, DsEntry>;

public constructor(name: string) {
super(name);
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer-api/src/fs/utils/FsAbstractUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class FsAbstractUtils {
const startPathPos = isRoot ? uri.path.length : slashAfterProfilePos;

// Load profile that matches the parsed name
const profileName = uri.path.substring(1, startPathPos);
const profileName = uri.path.startsWith("/") ? uri.path.substring(1, startPathPos) : uri.path.substring(0, startPathPos);
const profile = profilesCache?.loadNamedProfile ? profilesCache.loadNamedProfile(profileName) : null;

return {
Expand Down
8 changes: 4 additions & 4 deletions packages/zowe-explorer-api/src/fs/utils/FsDatasetsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
*/

import { IFileSystemEntry } from "../types/abstract";
import { DsEntry, MemberEntry, PdsEntry } from "../types/datasets";
import { DsEntry, PdsEntry } from "../types/datasets";

export class FsDatasetsUtils {
public static isDsEntry(entry: IFileSystemEntry): entry is DsEntry {
return entry instanceof DsEntry;
return entry instanceof DsEntry && !entry.isMember;
}

public static isMemberEntry(entry: IFileSystemEntry): entry is MemberEntry {
return entry != null && entry instanceof MemberEntry;
public static isMemberEntry(entry: IFileSystemEntry): entry is DsEntry {
return entry != null && entry instanceof DsEntry && entry.isMember;
}

public static isPdsEntry(entry: IFileSystemEntry): entry is PdsEntry {
Expand Down
4 changes: 2 additions & 2 deletions packages/zowe-explorer-api/src/security/KeytarApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
*
*/

import * as vscode from "vscode";
import * as imperative from "@zowe/imperative";
import { ProfilesCache } from "../profiles";
import { KeytarCredentialManager } from "./KeytarCredentialManager";
import { Types } from "../Types";
import { Constants } from "../globals";
import { ZoweVsCodeExtension } from "../vscode";
export class KeytarApi {
public constructor(protected log: imperative.Logger) {}

// v1 specific
public async activateKeytar(initialized: boolean): Promise<void> {
const log = imperative.Logger.getAppLogger();
const profiles = new ProfilesCache(log, vscode.workspace.workspaceFolders?.[0]?.uri.fsPath);
const profiles = new ProfilesCache(log, ZoweVsCodeExtension.workspaceRoot?.uri.fsPath);
const isSecure = await profiles.isCredentialsSecured();
if (isSecure) {
let keytar: object;
Expand Down
7 changes: 6 additions & 1 deletion packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@
* Collection of utility functions for writing Zowe Explorer VS Code extensions.
*/
export class ZoweVsCodeExtension {
public static get workspaceRoot(): vscode.WorkspaceFolder | undefined {
return vscode.workspace.workspaceFolders?.find((f) => f.uri.scheme === "file");
zFernand0 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @internal
*/
public static get profilesCache(): ProfilesCache {
return new ProfilesCache(imperative.Logger.getAppLogger(), vscode.workspace.workspaceFolders?.[0]?.uri.fsPath);
const workspacePath = this.workspaceRoot?.uri.fsPath;
traeok marked this conversation as resolved.
Show resolved Hide resolved
return new ProfilesCache(imperative.Logger.getAppLogger(), workspacePath);

Check warning on line 34 in packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts

View check run for this annotation

Codecov / codecov/patch

packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts#L34

Added line #L34 was not covered by tests
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/zowe-explorer-ftp-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
"build": "pnpm clean && pnpm license && webpack --mode development && pnpm madge",
"test:unit": "jest \".*__tests__.*\\.unit\\.test\\.ts\" --coverage",
"test": "pnpm test:unit",
"lint": "concurrently -n \"_eslint_,prettier\" \"pnpm lint:all\" \"pnpm pretty; pnpm pretty:check\"",
"lint:all": "pnpm lint:src; pnpm lint:test; pnpm lint:html",
"lint": "concurrently -n \"_eslint_,prettier\" \"pnpm lint:all\" \"pnpm run \"/pretty.*/\"\"",
"lint:all": "pnpm run \"/^lint:[sth].*/\"",
"lint:html": "((mkdir -p \"results\") || true) && eslint . --format html > results/eslint.html",
"lint:src": "eslint . --format stylish src/**/*.ts",
"lint:test": "eslint . --format stylish __tests__/**/*.ts",
"lint:html": "mkdir -p results && eslint . --format html > results/eslint.html",
"madge": "madge -c --no-color --no-spinner --exclude __mocks__ --extensions js,ts src/",
"pretty": "prettier --write .",
"pretty:check": "prettier --check .",
Expand Down
2 changes: 2 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Implemented the `onCredMgrUpdate` VSCode events to notify extenders when the local PC's credential manager has been updated by other applications. [#2994](https://github.com/zowe/zowe-explorer-vscode/pull/2994)
- Implemented support for building, exposing and displaying table views within Zowe Explorer. Tables can be customized and exposed using the helper facilities (`TableBuilder` and `TableMediator`) for an extender's specific use case. For more information on how to configure and show tables, please refer to the [wiki article on Table Views](https://github.com/zowe/zowe-explorer-vscode/wiki/Table-Views). [#2258](https://github.com/zowe/zowe-explorer-vscode/issues/2258)
- Added support for logging in to multiple API ML instances per team config file. [#2264](https://github.com/zowe/zowe-explorer-vscode/issues/2264)
- Added remote lookup functionality for Data Sets and USS, allowing Zowe Explorer to locate and resolve mainframe resources on demand. [#3040](https://github.com/zowe/zowe-explorer-vscode/pull/3040)
- Implemented change detection in the Data Sets and USS filesystems, so that changes on the mainframe will be reflected in opened editors for Data Sets and USS files. [#3040](https://github.com/zowe/zowe-explorer-vscode/pull/3040)
- Implemented a "Show as Table" option for profile nodes in the Jobs tree, displaying lists of jobs in a tabular view. Jobs can be filtered and sorted within this view, and users can select jobs to cancel, delete or download. [#2258](https://github.com/zowe/zowe-explorer-vscode/issues/2258)

### Bug fixes
Expand Down
Loading
Loading