Skip to content

Commit

Permalink
Merge branch 'main' into refactor/expose-local-storage
Browse files Browse the repository at this point in the history
Signed-off-by: Fernando Rijo Cedeno <[email protected]>
  • Loading branch information
zFernand0 authored Nov 18, 2024
2 parents 346f63b + 7143336 commit 0016055
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 64 deletions.
2 changes: 1 addition & 1 deletion packages/zowe-explorer-api/src/fs/BaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ export class BaseProvider {
})
.then(async ({ userResponse }) => {
if (userResponse === "Retry" && opts?.retry?.fn != null) {
await opts.retry.fn(...(opts?.retry.args ?? []));
await opts.retry.fn(...(opts.retry.args ?? []));
}
})
.catch(() => {
Expand Down
2 changes: 2 additions & 0 deletions packages/zowe-explorer-ftp-extension/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to the "zowe-explorer-ftp-extension" extension will be docum

### Bug fixes

- Fixed issue where the MVS API `putContents` function did not support PDS members when the member was not specified in the data set name. [#3305](https://github.com/zowe/zowe-explorer-vscode/issues/3305)

## `3.0.2`

## `3.0.1`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Gui, imperative } from "@zowe/zowe-explorer-api";
import * as globals from "../../../src/globals";
import { ZoweFtpExtensionError } from "../../../src/ZoweFtpExtensionError";
import { mocked } from "../../../__mocks__/mockUtils";
import { ZosFilesUtils } from "@zowe/zos-files-for-zowe-sdk";

// 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");
Expand Down Expand Up @@ -100,7 +101,7 @@ describe("FtpMvsApi", () => {
expect((response._readableState.buffer.head?.data ?? response._readableState.buffer).toString()).toContain("Hello world");
});

it("should upload content to dataset.", async () => {
it("should upload content to dataset - sequential data set", async () => {
const localFile = tmp.tmpNameSync({ tmpdir: "/tmp" });
const tmpNameSyncSpy = jest.spyOn(tmp, "tmpNameSync");
const rmSyncSpy = jest.spyOn(fs, "rmSync");
Expand All @@ -114,7 +115,7 @@ describe("FtpMvsApi", () => {

const mockParams = {
inputFilePath: localFile,
dataSetName: " (IBMUSER).DS2",
dataSetName: "IBMUSER.DS2",
options: { encoding: "", returnEtag: true, etag: "utf8" },
};
jest.spyOn(MvsApi as any, "getContents").mockResolvedValueOnce({ apiResponse: { etag: "utf8" } });
Expand All @@ -130,6 +131,38 @@ describe("FtpMvsApi", () => {
expect(rmSyncSpy).toHaveBeenCalled();
});

it("should generate a member name for PDS upload if one wasn't provided", async () => {
const localFile = tmp.tmpNameSync({ tmpdir: "/tmp" });
const tmpNameSyncSpy = jest.spyOn(tmp, "tmpNameSync");
const rmSyncSpy = jest.spyOn(fs, "rmSync");

fs.writeFileSync(localFile, "helloPdsMember");
const response = TestUtils.getSingleLineStream();
const response2 = { success: true, commandResponse: "", apiResponse: { items: [{ dsname: "IBMUSER.PDS", dsorg: "PO", lrecl: 255 }] } };
const dataSetMock = jest.spyOn(MvsApi, "dataSet").mockResolvedValue(response2 as any);
const uploadDataSetMock = jest.spyOn(DataSetUtils, "uploadDataSet").mockResolvedValue(response);
jest.spyOn(MvsApi, "getContents").mockResolvedValue({ apiResponse: { etag: "123" } } as any);

const mockParams = {
inputFilePath: localFile,
dataSetName: "IBMUSER.PDS",
options: { encoding: "", returnEtag: true, etag: "utf8" },
};
const generateMemberNameSpy = jest.spyOn(ZosFilesUtils, "generateMemberName");
jest.spyOn(MvsApi as any, "getContents").mockResolvedValueOnce({ apiResponse: { etag: "utf8" } });
jest.spyOn(fs, "readFileSync").mockReturnValue("test");
jest.spyOn(Gui, "warningMessage").mockImplementation();
const result = await MvsApi.putContents(mockParams.inputFilePath, mockParams.dataSetName, mockParams.options);
expect(generateMemberNameSpy).toHaveBeenCalledWith(localFile);
expect(result.commandResponse).toContain("Data set uploaded successfully.");
expect(dataSetMock).toHaveBeenCalledTimes(1);
expect(uploadDataSetMock).toHaveBeenCalledTimes(1);
expect(MvsApi.releaseConnection).toHaveBeenCalled();
// check that correct function is called from node-tmp
expect(tmpNameSyncSpy).toHaveBeenCalled();
expect(rmSyncSpy).toHaveBeenCalled();
});

it("should upload single space to dataset when secureFtp is true and contents are empty", async () => {
const localFile = tmp.tmpNameSync({ tmpdir: "/tmp" });

Expand Down
20 changes: 17 additions & 3 deletions packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,26 @@ export class FtpMvsApi extends AbstractFtpApi implements MainframeInteraction.IM
const result = this.getDefaultResponse();
const profile = this.checkedProfile();

const dsorg = dsAtrribute.apiResponse.items[0]?.dsorg;
const isPds = dsorg === "PO" || dsorg === "PO-E";

/**
* Determine the data set name for uploading.
*
* For PDS: When the input is a file path and the provided data set name doesn't include the member name,
* we'll need to generate a member name.
*/
const uploadName =
isPds && openParens == -1 && typeof input === "string"
? `${dataSetName}(${zosfiles.ZosFilesUtils.generateMemberName(input)})`
: dataSetName;

const inputIsBuffer = input instanceof Buffer;

// Save-Save with FTP requires loading the file first
// (moved this block above connection request so only one connection is active at a time)
if (options.returnEtag && options.etag) {
const contentsTag = await this.getContentsTag(dataSetName, inputIsBuffer);
const contentsTag = await this.getContentsTag(uploadName, inputIsBuffer);
if (contentsTag && contentsTag !== options.etag) {
throw Error("Rest API failure with HTTP(S) status 412: Save conflict");
}
Expand Down Expand Up @@ -174,13 +188,13 @@ export class FtpMvsApi extends AbstractFtpApi implements MainframeInteraction.IM
return result;
}
}
await DataSetUtils.uploadDataSet(connection, dataSetName, transferOptions);
await DataSetUtils.uploadDataSet(connection, uploadName, transferOptions);
result.success = true;
if (options.returnEtag) {
// release this connection instance because a new one will be made with getContentsTag
this.releaseConnection(connection);
connection = null;
const etag = await this.getContentsTag(dataSetName, inputIsBuffer);
const etag = await this.getContentsTag(uploadName, inputIsBuffer);
result.apiResponse = {
etag,
};
Expand Down
3 changes: 3 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278)
- Fixed an issue to review inconsistent capitalization across translation strings. [#2935](https://github.com/zowe/zowe-explorer-vscode/issues/2935)
- Updated the test for the default credential manager for better compatibility with Cloud-based platforms such as Eclipse Che and Red Hat OpenShift Dev Spaces. [#3297](https://github.com/zowe/zowe-explorer-vscode/pull/3297)
- Fixed an issue where opening a PDS member after renaming an expanded PDS resulted in an error. [#3314](https://github.com/zowe/zowe-explorer-vscode/issues/3314)
- Fixed issue where users were not prompted to enter credentials if a 401 error was encountered when opening files, data sets or spools in the editor. [#3197](https://github.com/zowe/zowe-explorer-vscode/issues/3197)
- Fixed issue where profile credential updates or token changes were not reflected within the filesystem. [#3289](https://github.com/zowe/zowe-explorer-vscode/issues/3289)
- Fixed issue where persistent settings defined at the workspace level were migrated into global storage rather than workspace-specific storage. [#3180](https://github.com/zowe/zowe-explorer-vscode/issues/3180)
- Fixed issue to update the success message when changing authentication from token to basic through the 'Change Authentication' option.
- Fixed an issue where fetching a USS file using `UssFSProvider.stat()` with a `fetch=true` query would cause Zowe Explorer to get stuck in an infinite loop.

## `3.0.2`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,10 @@ describe("rename", () => {

it("renames a PDS", async () => {
const oldPds = new PdsEntry("USER.DATA.PDS");
oldPds.metadata = new DsEntryMetadata({ profile: testProfile, path: "/USER.DATA.PDS" });
const exampleMember = new DsEntry("TESTMEM", true);
exampleMember.metadata = new DsEntryMetadata({ profile: testProfile, path: "/USER.DATA.PDS/TESTMEM" });
oldPds.entries.set("TESTMEM", exampleMember);
oldPds.metadata = testEntries.pds.metadata;
const mockMvsApi = {
renameDataSet: jest.fn(),
Expand All @@ -1148,6 +1152,7 @@ describe("rename", () => {
.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory")
.mockReturnValueOnce({ ...testEntries.session });
await DatasetFSProvider.instance.rename(testUris.pds, testUris.pds.with({ path: "/USER.DATA.PDS2" }), { overwrite: true });
expect(exampleMember.metadata.path).toBe("/USER.DATA.PDS2/TESTMEM");
expect(mockMvsApi.renameDataSet).toHaveBeenCalledWith("USER.DATA.PDS", "USER.DATA.PDS2");
_lookupMock.mockRestore();
mvsApiMock.mockRestore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2456,6 +2456,47 @@ describe("Dataset Tree Unit Tests - Function rename", () => {
expect(refreshElementSpy).toHaveBeenCalledWith(child.getParent());
});

it("Checking function with PDS", async () => {
createGlobalMocks();
const blockMocks = createBlockMocks();
mocked(Profiles.getInstance).mockReturnValue(blockMocks.profileInstance);
mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView);
const testTree = new DatasetTree();
testTree.mSessionNodes.push(blockMocks.datasetSessionNode);
// Create nodes in Session section
const parent = new ZoweDatasetNode({
label: "HLQ.TEST.OLDNAME.NODE",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_PDS_CONTEXT,
parentNode: testTree.mSessionNodes[1],
profile: blockMocks.imperativeProfile,
session: blockMocks.session,
});
const child = new ZoweDatasetNode({
label: "mem1",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MEMBER_CONTEXT,
parentNode: parent,
});
// Simulate corresponding nodes in favorites
// Push test nodes to respective arrays
parent.children.push(child);
testTree.mSessionNodes[1].children.push(parent);

const refreshElementSpy = jest.spyOn(testTree, "refreshElement");

const renameDataSetSpy = jest.spyOn((DatasetTree as any).prototype, "renameDataSet");

mocked(Gui.showInputBox).mockImplementation((options) => {
return Promise.resolve("HLQ.TEST.NEWNAME.NODE");
});
await testTree.rename(parent);
expect(renameDataSetSpy).toHaveBeenLastCalledWith(parent);
expect(parent.resourceUri?.path).toBe("/sestest/HLQ.TEST.NEWNAME.NODE");
expect(child.resourceUri?.path).toBe("/sestest/HLQ.TEST.NEWNAME.NODE/mem1");
expect(refreshElementSpy).toHaveBeenCalled();
});

it("Checking function with PDS Member given in lowercase", async () => {
createGlobalMocks();
const blockMocks = createBlockMocks();
Expand Down
74 changes: 37 additions & 37 deletions packages/zowe-explorer/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,42 @@
"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",
"The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.",
"Failed to move {0}/File path": {
"message": "Failed to move {0}",
"comment": [
"File path"
]
},
"Profile does not exist for this file.": "Profile does not exist for this file.",
"Saving USS file...": "Saving USS file...",
"Failed to rename {0}/File path": {
"message": "Failed to rename {0}",
"comment": [
"File path"
]
},
"Failed to delete {0}/File name": {
"message": "Failed to delete {0}",
"comment": [
"File name"
]
},
"No error details given": "No error details given",
"Error fetching destination {0} for paste action: {1}/USS pathError message": {
"message": "Error fetching destination {0} for paste action: {1}",
"comment": [
"USS path",
"Error message"
]
},
"Failed to copy {0} to {1}/Source pathDestination path": {
"message": "Failed to copy {0} to {1}",
"comment": [
"Source path",
"Destination path"
]
},
"Downloaded: {0}/Download time": {
"message": "Downloaded: {0}",
"comment": [
Expand Down Expand Up @@ -266,42 +302,6 @@
"initializeUSSFavorites.error.buttonRemove": "initializeUSSFavorites.error.buttonRemove",
"File does not exist. It may have been deleted.": "File does not exist. It may have been deleted.",
"Pulling from Mainframe...": "Pulling from Mainframe...",
"The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.",
"Failed to move {0}/File path": {
"message": "Failed to move {0}",
"comment": [
"File path"
]
},
"Profile does not exist for this file.": "Profile does not exist for this file.",
"Saving USS file...": "Saving USS file...",
"Failed to rename {0}/File path": {
"message": "Failed to rename {0}",
"comment": [
"File path"
]
},
"Failed to delete {0}/File name": {
"message": "Failed to delete {0}",
"comment": [
"File name"
]
},
"No error details given": "No error details given",
"Error fetching destination {0} for paste action: {1}/USS pathError message": {
"message": "Error fetching destination {0} for paste action: {1}",
"comment": [
"USS path",
"Error message"
]
},
"Failed to copy {0} to {1}/Source pathDestination path": {
"message": "Failed to copy {0} to {1}",
"comment": [
"Source path",
"Destination path"
]
},
"{0} location/Node type": {
"message": "{0} location",
"comment": [
Expand Down Expand Up @@ -1039,7 +1039,7 @@
"Cannot switch to token-based authentication for profile {0}.": "Cannot switch to token-based authentication for profile {0}.",
"Login using token-based authentication service was successful for profile {0}.": "Login using token-based authentication service was successful for profile {0}.",
"Unable to switch to token-based authentication for profile {0}.": "Unable to switch to token-based authentication for profile {0}.",
"Login using basic authentication was successful for profile {0}.": "Login using basic authentication was successful for profile {0}.",
"Changing authentication to basic was successful for profile {0}.": "Changing authentication to basic was successful for profile {0}.",
"Unable to switch to basic authentication for profile {0}.": "Unable to switch to basic authentication for profile {0}.",
"Unable to switch authentication for profile {0}.": "Unable to switch authentication for profile {0}.",
"Logout from authentication service was successful for {0}./Service profile name": {
Expand Down
20 changes: 10 additions & 10 deletions packages/zowe-explorer/l10n/poeditor.json
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,15 @@
"Profile auth error": "",
"Profile is not authenticated, please log in to continue": "",
"Retrieving response from USS list API": "",
"The 'move' function is not implemented for this USS API.": "",
"Failed to move {0}": "",
"Profile does not exist for this file.": "",
"Saving USS file...": "",
"Failed to rename {0}": "",
"Failed to delete {0}": "",
"No error details given": "",
"Error fetching destination {0} for paste action: {1}": "",
"Failed to copy {0} to {1}": "",
"Downloaded: {0}": "",
"Encoding: {0}": "",
"Binary": "",
Expand Down Expand Up @@ -560,15 +569,6 @@
"initializeUSSFavorites.error.buttonRemove": "",
"File does not exist. It may have been deleted.": "",
"Pulling from Mainframe...": "",
"The 'move' function is not implemented for this USS API.": "",
"Failed to move {0}": "",
"Profile does not exist for this file.": "",
"Saving USS file...": "",
"Failed to rename {0}": "",
"Failed to delete {0}": "",
"No error details given": "",
"Error fetching destination {0} for paste action: {1}": "",
"Failed to copy {0} to {1}": "",
"{0} location": "",
"Choose a location to create the {0}": "",
"Name of file or directory": "",
Expand Down Expand Up @@ -854,7 +854,7 @@
"Cannot switch to token-based authentication for profile {0}.": "",
"Login using token-based authentication service was successful for profile {0}.": "",
"Unable to switch to token-based authentication for profile {0}.": "",
"Login using basic authentication was successful for profile {0}.": "",
"Changing authentication to basic was successful for profile {0}.": "",
"Unable to switch to basic authentication for profile {0}.": "",
"Unable to switch authentication for profile {0}.": "",
"Logout from authentication service was successful for {0}.": "",
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/src/configuration/Profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ export class Profiles extends ProfilesCache {

if (creds !== undefined) {
const successMsg = vscode.l10n.t(
"Login using basic authentication was successful for profile {0}.",
"Changing authentication to basic was successful for profile {0}.",
typeof profile === "string" ? profile : profile.name
);
ZoweLogger.info(successMsg);
Expand Down
Loading

0 comments on commit 0016055

Please sign in to comment.