diff --git a/__mocks__/mockCreators/datasets.ts b/__mocks__/mockCreators/datasets.ts index ba32a2849d..38d756af09 100644 --- a/__mocks__/mockCreators/datasets.ts +++ b/__mocks__/mockCreators/datasets.ts @@ -44,7 +44,7 @@ export function createDatasetTree(sessionNode: ZoweDatasetNode, treeView: any): createFilterString: jest.fn(), setItem: jest.fn(), getTreeView: jest.fn().mockImplementation(() => treeView), - searchInLoadedItems: jest.fn(), + getAllLoadedItems: jest.fn(), removeFavorite: jest.fn(), deleteSession: jest.fn(), removeFileHistory: jest.fn(), diff --git a/__mocks__/mockCreators/uss.ts b/__mocks__/mockCreators/uss.ts index ab0ba67b50..c73d4f4c48 100644 --- a/__mocks__/mockCreators/uss.ts +++ b/__mocks__/mockCreators/uss.ts @@ -31,7 +31,7 @@ export function createUSSTree(favoriteNodes: ZoweUSSNode[], sessionNodes: ZoweUS newTree.removeFavorite = jest.fn().mockImplementation((badFavorite) => removeNodeFromArray(badFavorite, newTree.mFavorites)); newTree.openItemFromPath = jest.fn(); newTree.deleteSession = jest.fn().mockImplementation((badSession) => removeNodeFromArray(badSession, newTree.mSessionNodes)); - newTree.searchInLoadedItems = jest.fn(); + newTree.getAllLoadedItems = jest.fn(); newTree.getTreeView = jest.fn().mockImplementation(() => treeView); newTree.getTreeItem = jest.fn().mockImplementation(() => new vscode.TreeItem('test')); newTree.getTreeType = jest.fn().mockImplementation(() => globals.PersistenceSchemaEnum.USS); diff --git a/__tests__/__unit__/dataset/DatasetTree.unit.test.ts b/__tests__/__unit__/dataset/DatasetTree.unit.test.ts index 460b5b877f..c8caaf66c7 100644 --- a/__tests__/__unit__/dataset/DatasetTree.unit.test.ts +++ b/__tests__/__unit__/dataset/DatasetTree.unit.test.ts @@ -863,7 +863,7 @@ describe("Dataset Tree Unit Tests - Function editSession", () => { expect(node.getProfile().profile).toBe("testProfile"); }); }); -describe("Dataset Tree Unit Tests - Function searchInLoadedItems", () => { +describe("Dataset Tree Unit Tests - Function getAllLoadedItems", () => { function createBlockMocks() { const session = createISession(); const imperativeProfile = createIProfile(); @@ -888,7 +888,7 @@ describe("Dataset Tree Unit Tests - Function searchInLoadedItems", () => { testTree.mSessionNodes[1], blockMocks.session, globals.DS_DS_CONTEXT); testTree.mSessionNodes[1].children.push(node); - const items = await testTree.searchInLoadedItems(); + const items = await testTree.getAllLoadedItems(); expect(items).toEqual([node]); }); diff --git a/__tests__/__unit__/dataset/actions.unit.test.ts b/__tests__/__unit__/dataset/actions.unit.test.ts index 8c100c6db1..295e0b9fc5 100644 --- a/__tests__/__unit__/dataset/actions.unit.test.ts +++ b/__tests__/__unit__/dataset/actions.unit.test.ts @@ -15,7 +15,8 @@ import { createBasicZosmfSession, createInstanceOfProfile, createIProfile, createISession, createISessionWithoutCredentials, createTextDocument, - createTreeView + createTreeView, + createQuickPickContent } from "../../../__mocks__/mockCreators/shared"; import { createDatasetAttributes, @@ -56,6 +57,7 @@ function createGlobalMocks() { Object.defineProperty(vscode.workspace, "getConfiguration", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.window, "showTextDocument", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.window, "showQuickPick", { value: jest.fn(), configurable: true }); + Object.defineProperty(vscode.window, "createQuickPick", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.commands, "executeCommand", { value: jest.fn(), configurable: true }); Object.defineProperty(globals, "LOG", { value: jest.fn(), configurable: true }); Object.defineProperty(globals.LOG, "debug", { value: jest.fn(), configurable: true }); @@ -66,6 +68,7 @@ function createGlobalMocks() { Object.defineProperty(zowe.Delete, "dataSet", { value: jest.fn(), configurable: true }); Object.defineProperty(zowe, "Create", { value: jest.fn(), configurable: true }); Object.defineProperty(zowe.Create, "dataSet", { value: jest.fn(), configurable: true }); + Object.defineProperty(zowe.Create, "dataSetLike", { value: jest.fn(), configurable: true }); Object.defineProperty(fs, "unlinkSync", { value: jest.fn(), configurable: true }); Object.defineProperty(fs, "existsSync", { value: jest.fn(), configurable: true }); Object.defineProperty(sharedUtils, "concatChildNodes", { value: jest.fn(), configurable: true }); @@ -1968,3 +1971,113 @@ describe("Dataset Actions Unit Tests - Function openPS", () => { expect(mocked(vscode.workspace.openTextDocument)).toBeCalledWith(sharedUtils.getDocumentFilePath(child.label, child)); }); }); + +describe("Dataset Actions Unit Tests - Function allocateLike", () => { + function createBlockMocks() { + const session = createISession(); + const imperativeProfile = createIProfile(); + const treeView = createTreeView(); + const datasetSessionNode = createDatasetSessionNode(session, imperativeProfile); + const testDatasetTree = createDatasetTree(datasetSessionNode, treeView); + const testNode = new ZoweDatasetNode("nodePDS", vscode.TreeItemCollapsibleState.None, datasetSessionNode, null); + const testSDSNode = new ZoweDatasetNode("nodeSDS", vscode.TreeItemCollapsibleState.None, datasetSessionNode, null); + const profileInstance = createInstanceOfProfile(imperativeProfile); + const mvsApi = createMvsApi(imperativeProfile); + const quickPickItem = new utils.FilterDescriptor(datasetSessionNode.label); + const quickPickContent = createQuickPickContent("", [quickPickItem], ""); + + bindMvsApi(mvsApi); + testNode.contextValue = globals.DS_PDS_CONTEXT; + testSDSNode.contextValue = globals.DS_DS_CONTEXT; + + mocked(vscode.window.createQuickPick).mockReturnValue(quickPickContent); + mocked(Profiles.getInstance).mockReturnValue(profileInstance); + mocked(vscode.window.showInputBox).mockResolvedValue("test"); + jest.spyOn(datasetSessionNode, "getChildren").mockResolvedValue([testNode, testSDSNode]); + testDatasetTree.createFilterString.mockResolvedValue("test"); + jest.spyOn(utils, "resolveQuickPickHelper").mockResolvedValue(quickPickItem); + jest.spyOn(dsActions, "openPS").mockImplementation(() => null); + + return { + session, + treeView, + testNode, + quickPickContent, + testSDSNode, + quickPickItem, + profileInstance, + imperativeProfile, + datasetSessionNode, + mvsApi, + testDatasetTree + }; + } + + it("Tests that allocateLike works if called from the command palette", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + const errorHandlingSpy = jest.spyOn(utils, "errorHandling"); + + await dsActions.allocateLike(blockMocks.testDatasetTree); + + expect(errorHandlingSpy).toHaveBeenCalledTimes(0); + expect(blockMocks.quickPickContent.show).toHaveBeenCalledTimes(1); + }); + it("Tests that allocateLike works if called from the context menu", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + const errorHandlingSpy = jest.spyOn(utils, "errorHandling"); + + await dsActions.allocateLike(blockMocks.testDatasetTree, blockMocks.testNode); + + expect(errorHandlingSpy).toHaveBeenCalledTimes(0); + expect(blockMocks.quickPickContent.show).toHaveBeenCalledTimes(0); + }); + it("Tests that the dataset filter string is updated on the session, to include the new node's name", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + await dsActions.allocateLike(blockMocks.testDatasetTree, blockMocks.testNode); + + expect(blockMocks.datasetSessionNode.pattern).toEqual("TEST"); + }); + it("Tests that allocateLike fails if no profile is selected", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + jest.spyOn(utils, "resolveQuickPickHelper").mockResolvedValueOnce(null); + + await dsActions.allocateLike(blockMocks.testDatasetTree); + + expect(mocked(vscode.window.showInformationMessage)).toHaveBeenCalledWith("You must select a profile."); + }); + it("Tests that allocateLike fails if no new dataset name is provided", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + mocked(vscode.window.showInputBox).mockResolvedValueOnce(null); + + await dsActions.allocateLike(blockMocks.testDatasetTree, blockMocks.testNode); + + expect(mocked(vscode.window.showInformationMessage)).toHaveBeenCalledWith("You must enter a new data set name."); + }); + it("Tests that allocateLike fails if error is thrown", async () => { + createGlobalMocks(); + const blockMocks = createBlockMocks(); + + const errorHandlingSpy = jest.spyOn(utils, "errorHandling"); + const errorMessage = new Error("Test error"); + jest.spyOn(blockMocks.mvsApi, "allocateLikeDataSet").mockRejectedValue(errorMessage); + + try { + await dsActions.allocateLike(blockMocks.testDatasetTree); + } catch (err) { + // do nothing + } + + expect(errorHandlingSpy).toHaveBeenCalledTimes(1); + expect(errorHandlingSpy).toHaveBeenCalledWith(errorMessage, "test", "Unable to create data set: Test error"); + }); +}); diff --git a/__tests__/__unit__/extension.unit.test.ts b/__tests__/__unit__/extension.unit.test.ts index f8d44f2664..c321f8dab8 100644 --- a/__tests__/__unit__/extension.unit.test.ts +++ b/__tests__/__unit__/extension.unit.test.ts @@ -110,6 +110,7 @@ async function createGlobalMocks() { "zowe.createMember", "zowe.deleteDataset", "zowe.deletePDS", + "zowe.allocateLike", "zowe.uploadDialog", "zowe.deleteMember", "zowe.editMember", diff --git a/__tests__/__unit__/shared/actions.unit.test.ts b/__tests__/__unit__/shared/actions.unit.test.ts index 4c5c8993ee..ec758814e1 100644 --- a/__tests__/__unit__/shared/actions.unit.test.ts +++ b/__tests__/__unit__/shared/actions.unit.test.ts @@ -82,8 +82,8 @@ describe("Shared Actions Unit Tests - Function searchForLoadedItems", () => { const testNode = new ZoweDatasetNode("HLQ.PROD2.STUFF", null, blockMocks.datasetSessionNode, blockMocks.session, globals.DS_PDS_CONTEXT); testNode.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - blockMocks.testDatasetTree.searchInLoadedItems.mockResolvedValueOnce([testNode]); - blockMocks.testUssTree.searchInLoadedItems.mockResolvedValueOnce([]); + blockMocks.testDatasetTree.getAllLoadedItems.mockResolvedValueOnce([testNode]); + blockMocks.testUssTree.getAllLoadedItems.mockResolvedValueOnce([]); blockMocks.testDatasetTree.getChildren.mockImplementation((arg) => { if (arg) { return Promise.resolve([testNode]); @@ -115,8 +115,8 @@ describe("Shared Actions Unit Tests - Function searchForLoadedItems", () => { blockMocks.testDatasetTree.getChildren.mockReturnValue([blockMocks.datasetSessionNode]); jest.spyOn(dsActions, "openPS").mockResolvedValueOnce(null); - blockMocks.testDatasetTree.searchInLoadedItems.mockResolvedValueOnce([testMember]); - blockMocks.testUssTree.searchInLoadedItems.mockResolvedValueOnce([]); + blockMocks.testDatasetTree.getAllLoadedItems.mockResolvedValueOnce([testMember]); + blockMocks.testUssTree.getAllLoadedItems.mockResolvedValueOnce([]); blockMocks.testDatasetTree.getChildren.mockImplementation((arg) => { if (arg === testNode) { return Promise.resolve([testMember]); @@ -142,8 +142,8 @@ describe("Shared Actions Unit Tests - Function searchForLoadedItems", () => { const folder = new ZoweUSSNode("folder", vscode.TreeItemCollapsibleState.Collapsed, blockMocks.ussSessionNode, null, "/"); blockMocks.testDatasetTree.getChildren.mockReturnValue([blockMocks.ussSessionNode]); - blockMocks.testDatasetTree.searchInLoadedItems.mockResolvedValueOnce([]); - blockMocks.testUssTree.searchInLoadedItems.mockResolvedValueOnce([folder]); + blockMocks.testDatasetTree.getAllLoadedItems.mockResolvedValueOnce([]); + blockMocks.testUssTree.getAllLoadedItems.mockResolvedValueOnce([folder]); jest.spyOn(folder, "getProfileName").mockImplementationOnce(() => "firstName"); jest.spyOn(blockMocks.ussSessionNode, "getChildren").mockResolvedValueOnce([folder]); @@ -166,8 +166,8 @@ describe("Shared Actions Unit Tests - Function searchForLoadedItems", () => { const file = new ZoweUSSNode("file", vscode.TreeItemCollapsibleState.None, folder, null, "/folder"); blockMocks.testDatasetTree.getChildren.mockReturnValue([blockMocks.ussSessionNode]); - blockMocks.testDatasetTree.searchInLoadedItems.mockResolvedValueOnce([]); - blockMocks.testUssTree.searchInLoadedItems.mockResolvedValueOnce([file]); + blockMocks.testDatasetTree.getAllLoadedItems.mockResolvedValueOnce([]); + blockMocks.testUssTree.getAllLoadedItems.mockResolvedValueOnce([file]); jest.spyOn(blockMocks.ussSessionNode, "getChildren").mockResolvedValueOnce([folder]); jest.spyOn(folder, "getChildren").mockResolvedValueOnce([file]); @@ -187,8 +187,8 @@ describe("Shared Actions Unit Tests - Function searchForLoadedItems", () => { const globalMocks = await createGlobalMocks(); const blockMocks = createBlockMocks(); - blockMocks.testDatasetTree.searchInLoadedItems.mockResolvedValueOnce([]); - blockMocks.testUssTree.searchInLoadedItems.mockResolvedValueOnce([]); + blockMocks.testDatasetTree.getAllLoadedItems.mockResolvedValueOnce([]); + blockMocks.testUssTree.getAllLoadedItems.mockResolvedValueOnce([]); const qpItem = null; const quickPickContent = createQuickPickContent(qpItem, qpItem, globalMocks.qpPlaceholder); quickPickContent.placeholder = "Select a filter"; diff --git a/__tests__/__unit__/uss/USSTree.unit.test.ts b/__tests__/__unit__/uss/USSTree.unit.test.ts index eaea258966..abfc7b8bdf 100644 --- a/__tests__/__unit__/uss/USSTree.unit.test.ts +++ b/__tests__/__unit__/uss/USSTree.unit.test.ts @@ -540,8 +540,8 @@ describe("USSTree Unit Tests - Function USSTree.filterPrompt()", () => { }); }); -describe("USSTree Unit Tests - Function USSTree.searchInLoadedItems()", () => { - it("Testing that searchInLoadedItems() returns the correct array", async () => { +describe("USSTree Unit Tests - Function USSTree.getAllLoadedItems()", () => { + it("Testing that getAllLoadedItems() returns the correct array", async () => { const globalMocks = await createGlobalMocks(); const folder = new ZoweUSSNode("folder", vscode.TreeItemCollapsibleState.Collapsed, globalMocks.testTree.mSessionNodes[1], null, "/"); @@ -556,7 +556,7 @@ describe("USSTree Unit Tests - Function USSTree.searchInLoadedItems()", () => { () => Promise.resolve(globalMocks.testTree.mSessionNodes[1].children) ); - const loadedItems = await globalMocks.testTree.searchInLoadedItems(); + const loadedItems = await globalMocks.testTree.getAllLoadedItems(); expect(loadedItems).toStrictEqual([file, folder]); }); }); diff --git a/i18n/sample/package.i18n.json b/i18n/sample/package.i18n.json index cdab571fed..924a2d520c 100644 --- a/i18n/sample/package.i18n.json +++ b/i18n/sample/package.i18n.json @@ -19,6 +19,7 @@ "deleteDataset": "Delete Data Set", "deleteMember": "Delete Member", "deletePDS": "Delete PDS", + "allocateLike": "Allocate Like (New File with Same Attributes)", "editMember": "Edit", "issueCmd": "Issue Command", "uploadDialog": "Upload Member...", diff --git a/i18n/sample/src/dataset/actions.i18n.json b/i18n/sample/src/dataset/actions.i18n.json index f6f53f5795..8623962fae 100644 --- a/i18n/sample/src/dataset/actions.i18n.json +++ b/i18n/sample/src/dataset/actions.i18n.json @@ -1,4 +1,11 @@ { + "allocateLike.options.prompt": "Select the profile to which the original data set belongs", + "allocateLike.noSelection": "You must select a profile.", + "allocateLike.enterLikePattern": "Enter the name of the data set to copy attributes from", + "allocateLike.enterPattern": "Enter a name for the new data set", + "allocateLike.noNewName": "You must enter a new data set name.", + "createDataSet.log.error": "Error encountered when creating data set! ", + "createDataSet.error": "Error encountered when creating data set! ", "enterPattern.pattern": "You must enter a pattern.", "createMember.inputBox": "Name of Member", "createMember.log.debug.createNewDataSet": "creating new data set member of name ", @@ -8,16 +15,15 @@ "openPS.error.invalidNode": "openPS() called from invalid node. ", "openPS.log.debug.openDataSet": "opening physical sequential data set from label ", "openPS.log.error.openDataSet": "Error encountered when opening data set! ", - "createFile.quickPickOption.dataSetType": "Type of Data Set to be Created", "createFile.dataSetBinary": "Data Set Binary", "createFile.dataSetC": "Data Set C", "createFile.dataSetClassic": "Data Set Classic", "createFile.dataSetPartitioned": "Data Set Partitioned", "createFile.dataSetSequential": "Data Set Sequential", + "createFile.quickPickOption.dataSetType": "Type of Data Set to be Created", "createFile.log.debug.noValidTypeSelected": "No valid data type selected", "createFile.log.debug.creatingNewDataSet": "Creating new data set", "dataset.name": "Name of Data Set", - "createDataSet.error": "Error encountered when creating data set! ", "showDSAttributes.debug": "showing attributes of data set ", "showDSAttributes.lengthError": "No matching data set names found for query: ", "showDSAttributes.log.error": "Error encountered when listing attributes! ", diff --git a/i18n/sample/src/uss/USSTree.i18n.json b/i18n/sample/src/uss/USSTree.i18n.json index e75bdf2e09..a6ce33d301 100644 --- a/i18n/sample/src/uss/USSTree.i18n.json +++ b/i18n/sample/src/uss/USSTree.i18n.json @@ -1,5 +1,7 @@ { "Favorites": "Favorites", + "renameUSSNode.enterName": "Enter a new name for the {0}", + "renameUSSNode.duplicateName": "A {0} already exists with this name. Please choose a different one.", "renameUSSNode.error": "Unable to rename node: ", "enterPattern.log.debug.prompt": "Prompting the user to choose a member from the filtered list", "filterPrompt.log.debug.promptUSSPath": "Prompting the user for a USS path", diff --git a/package-lock.json b/package-lock.json index b231a38e41..77e81a90e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2969,9 +2969,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.155", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz", - "integrity": "sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg==" + "version": "4.14.159", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz", + "integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==" }, "@types/lodash-deep": { "version": "2.0.0", @@ -3322,11 +3322,11 @@ "dev": true }, "@zowe/cli": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@zowe/cli/-/cli-6.15.0.tgz", - "integrity": "sha512-4I452508oEd9+oxLQjGTqo+weiI7U2ZsHdN+sTl4rXldz/GR0JoO49uj2+y4B5hZZt3ZBqDToSrtjrvn4HHH3A==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/@zowe/cli/-/cli-6.21.1.tgz", + "integrity": "sha512-Kp7/Wjhig7vBt70qyETgwd9wNuPJeRWAYtgxYquw0CKXIbB6kJzRvLuyk5oym6STy6LNqicGp6CFamP98CENBQ==", "requires": { - "@zowe/imperative": "4.6.4", + "@zowe/imperative": "4.8.0", "@zowe/perf-timing": "1.0.7", "get-stdin": "7.0.0", "js-yaml": "3.13.1", @@ -3336,9 +3336,9 @@ } }, "@zowe/imperative": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/@zowe/imperative/-/imperative-4.6.4.tgz", - "integrity": "sha512-8QQ/S1Gx0Q3yqwLK2f0SMWUkJzv5IHB6z5s2RSO/VTuUtF6e4vPCCZ84dGxQlkbFCWhlNtq9pDHRu8DClzcQOA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@zowe/imperative/-/imperative-4.8.0.tgz", + "integrity": "sha512-1OILs5n22yLZTd98EuCAYs52owwjOsl67XX9goLyju1is8fjMoViRas1ShY2UlH7XnP7y7iN0JEcB74KmTwL3w==", "requires": { "@types/lodash-deep": "2.0.0", "@types/yargs": "13.0.4", diff --git a/package.json b/package.json index 3089e34aab..8327a7e40d 100644 --- a/package.json +++ b/package.json @@ -183,6 +183,10 @@ "command": "zowe.deletePDS", "title": "%deletePDS%" }, + { + "command": "zowe.allocateLike", + "title": "%allocateLike%" + }, { "command": "zowe.uploadDialog", "title": "%uploadDialog%" @@ -745,15 +749,20 @@ "command": "zowe.removeSavedSearch", "group": "5_workspace@3" }, + { + "when": "view == zowe.explorer && viewItem =~ /^(ds|pds).*/", + "command": "zowe.allocateLike", + "group": "6_cutCopyPaste@0" + }, { "when": "view == zowe.explorer && viewItem =~ /^(member|ds).*/", "command": "zowe.copyDataSet", - "group": "6_cutCopyPaste@0" + "group": "6_cutCopyPaste@1" }, { "when": "view == zowe.explorer && viewItem =~ /^(ds.*|^pds.*)/", "command": "zowe.pasteDataSet", - "group": "6_cutCopyPaste@1" + "group": "6_cutCopyPaste@2" }, { "when": "view == zowe.explorer && viewItem =~ /^(?!.*_fav.*)session.*/", @@ -936,6 +945,9 @@ "command": "zowe.deletePDS", "when": "never" }, + { + "command": "zowe.allocateLike" + }, { "command": "zowe.deleteMember", "when": "never" @@ -1255,7 +1267,7 @@ "webpack-cli": "^3.3.11" }, "dependencies": { - "@zowe/cli": "6.15.0", + "@zowe/cli": "^6.20.0", "fs-extra": "8.0.1", "isbinaryfile": "4.0.4", "js-yaml": "3.13.1", diff --git a/package.nls.json b/package.nls.json index b7cbaef150..aa1f171e35 100644 --- a/package.nls.json +++ b/package.nls.json @@ -19,6 +19,7 @@ "deleteDataset": "Delete Data Set", "deleteMember": "Delete Member", "deletePDS": "Delete PDS", + "allocateLike": "Allocate Like (New File with Same Attributes)", "editMember": "Edit", "issueCmd": "Issue Command", "uploadDialog": "Upload Member...", diff --git a/src/__mocks__/USSTree.ts b/src/__mocks__/USSTree.ts index a9974dfadb..786b7ccf9d 100644 --- a/src/__mocks__/USSTree.ts +++ b/src/__mocks__/USSTree.ts @@ -61,7 +61,7 @@ export class USSTree implements vscode.TreeDataProvider { * @memberof USSTree */ @MockMethod() - public searchInLoadedItems(): IZoweUSSTreeNode[] { + public getAllLoadedItems(): IZoweUSSTreeNode[] { return null; } diff --git a/src/api/IZoweTree.ts b/src/api/IZoweTree.ts index 4407e252fb..79f00556ab 100644 --- a/src/api/IZoweTree.ts +++ b/src/api/IZoweTree.ts @@ -171,7 +171,7 @@ export interface IZoweTree extends vscode.TreeDataProvider { /** * Lets the user open a dataset by filtering the currently-loaded list */ - searchInLoadedItems?(): Promise; + getAllLoadedItems?(): Promise; /** * Retrieves the vscode tree container */ diff --git a/src/api/ZoweExplorerApi.ts b/src/api/ZoweExplorerApi.ts index 68c11201e1..042153869b 100644 --- a/src/api/ZoweExplorerApi.ts +++ b/src/api/ZoweExplorerApi.ts @@ -267,6 +267,19 @@ export namespace ZoweExplorerApi { options?: zowe.IUploadOptions ): Promise; + /** + * Allocates a copy of a data set with the specified options. + * + * @param {zowe.CreateDataSetTypeEnum} dataSetType + * @param {string} dataSetName + * @param {Partial} [options] + * @returns {Promise} + */ + allocateLikeDataSet( + dataSetName: string, + likeDataSetName: string + ): Promise; + /** * Copies a data set member. * diff --git a/src/api/ZoweExplorerZosmfApi.ts b/src/api/ZoweExplorerZosmfApi.ts index f63f5cb45e..be75018c00 100644 --- a/src/api/ZoweExplorerZosmfApi.ts +++ b/src/api/ZoweExplorerZosmfApi.ts @@ -170,6 +170,10 @@ export class ZosmfMvsApi extends ZosmfApiCommon implements ZoweExplorerApi.IMvs return zowe.Upload.bufferToDataSet(this.getSession(), Buffer.from(""), dataSetName, options); } + public async allocateLikeDataSet(dataSetName: string, likeDataSetName: string): Promise { + return zowe.Create.dataSetLike(this.getSession(), dataSetName, likeDataSetName); + } + public async copyDataSetMember( { dataSetName: fromDataSetName, memberName: fromMemberName }: zowe.IDataSet, { dataSetName: toDataSetName, memberName: toMemberName }: zowe.IDataSet, diff --git a/src/dataset/DatasetTree.ts b/src/dataset/DatasetTree.ts index c9577234f0..d400d38a50 100644 --- a/src/dataset/DatasetTree.ts +++ b/src/dataset/DatasetTree.ts @@ -26,6 +26,7 @@ import * as fs from "fs"; import * as contextually from "../shared/context"; import { closeOpenedTextFile } from "../utils/workspace"; import * as nls from "vscode-nls"; +import { ZoweTreeNode } from "../abstract/ZoweTreeNode"; // Set up localization nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); @@ -515,7 +516,7 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree, node?: IZoweDatasetTreeNode) { + let profile: IProfileLoaded; + let likeDSName: string; + let currSession: IZoweDatasetTreeNode; + + // User called allocateLike from the command palette + if (!node) { + // The user must choose a session + const qpItems = []; + const quickpick = vscode.window.createQuickPick(); + quickpick.placeholder = localize("allocateLike.options.prompt", "Select the profile to which the original data set belongs"); + quickpick.ignoreFocusOut = true; + + for (const thisSession of datasetProvider.mSessionNodes) { qpItems.push(new FilterItem(thisSession.label.trim())); } + quickpick.items = [...qpItems]; + + quickpick.show(); + const selection = await resolveQuickPickHelper(quickpick); + if (!selection) { + vscode.window.showInformationMessage(localize("allocateLike.noSelection", "You must select a profile.")); + return; + } else { + currSession = datasetProvider.mSessionNodes.find((thisSession) => thisSession.label === selection.label); + profile = currSession.getProfile(); + } + quickpick.dispose(); + + // The user must enter the name of a data set to copy + likeDSName = await vscode.window.showInputBox({ ignoreFocusOut: true, + placeHolder: localize("allocateLike.enterLikePattern", "Enter the name of the data set to copy attributes from") }); + } else { + // User called allocateLike by right-clicking a node + profile = node.getProfile(); + likeDSName = node.label.replace(/\[.*\]: /g, ""); + } + + // Get new data set name + const newDSName = await vscode.window.showInputBox({ ignoreFocusOut: true, + placeHolder: localize("allocateLike.enterPattern", "Enter a name for the new data set") + }); + if (!newDSName) { + vscode.window.showInformationMessage(localize("allocateLike.noNewName", "You must enter a new data set name.")); + return; + } else { + // Allocate the data set, or throw an error + try { + await (ZoweExplorerApiRegister.getMvsApi(profile).allocateLikeDataSet(newDSName.toUpperCase(), likeDSName)); + } catch (err) { + globals.LOG.error(localize("createDataSet.log.error", "Error encountered when creating data set! ") + JSON.stringify(err)); + errorHandling(err, newDSName, localize("createDataSet.error", "Unable to create data set: ") + err.message); + throw (err); + } + } + + // Refresh tree and open new node, if applicable + if (!currSession) { currSession = datasetProvider.mSessionNodes.find((thisSession) => thisSession.label.trim() === profile.name); } + const theFilter = await datasetProvider.createFilterString(newDSName, currSession); + currSession.tooltip = currSession.pattern = theFilter.toUpperCase(); + datasetProvider.addSearchHistory(theFilter); + datasetProvider.refresh(); + currSession.dirty = true; + datasetProvider.refreshElement(currSession); + const newNode = (await currSession.getChildren()).find((child) => child.label.trim() === newDSName.toUpperCase()); + await datasetProvider.getTreeView().reveal(currSession, { select: true, focus: true }); + datasetProvider.getTreeView().reveal(newNode, { select: true, focus: true }); +} + export async function uploadDialog(node: ZoweDatasetNode, datasetProvider: IZoweTree) { const fileOpenOptions = { canSelectFiles: true, @@ -139,12 +210,6 @@ export async function createMember(parent: IZoweDatasetTreeNode, datasetProvider * @param {IZoweDatasetTreeNode} node */ export async function openPS(node: IZoweDatasetTreeNode, previewMember: boolean, datasetProvider?: IZoweTree) { - // let sesNamePrompt: string; - // if (node.contextValue.endsWith(globals.FAV_SUFFIX)) { - // sesNamePrompt = node.getLabel().substring(1, node.getLabel().indexOf("]")); - // } else { - // sesNamePrompt = node.getLabel(); - // } if (datasetProvider) { await datasetProvider.checkCurrentProfile(node); } if (Profiles.getInstance().validProfile === ValidProfileEnum.VALID) { try { @@ -198,6 +263,37 @@ export async function openPS(node: IZoweDatasetTreeNode, previewMember: boolean, } } +export function getDataSetTypeAndOptions(type: string) { + let typeEnum; + let createOptions; + switch (type) { + case localize("createFile.dataSetBinary", "Data Set Binary"): + typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_BINARY; + createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-Binary"); + break; + case localize("createFile.dataSetC", "Data Set C"): + typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_C; + createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-C"); + break; + case localize("createFile.dataSetClassic", "Data Set Classic"): + typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_CLASSIC; + createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-Classic"); + break; + case localize("createFile.dataSetPartitioned", "Data Set Partitioned"): + typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_PARTITIONED; + createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-PDS"); + break; + case localize("createFile.dataSetSequential", "Data Set Sequential"): + typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL; + createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-PS"); + break; + } + return { + typeEnum, + createOptions + }; +} + /** * Creates a new file and uploads to the server * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -220,12 +316,6 @@ export async function createFile(node: IZoweDatasetTreeNode, datasetProvider: IZ localize("createFile.dataSetPartitioned", "Data Set Partitioned"), localize("createFile.dataSetSequential", "Data Set Sequential") ]; - // let sesNamePrompt: string; - // if (node.contextValue.endsWith(globals.FAV_SUFFIX)) { - // sesNamePrompt = node.label.substring(1, node.label.indexOf("]")); - // } else { - // sesNamePrompt = node.label; - // } datasetProvider.checkCurrentProfile(node); if (Profiles.getInstance().validProfile === ValidProfileEnum.VALID) { @@ -238,30 +328,7 @@ export async function createFile(node: IZoweDatasetTreeNode, datasetProvider: IZ globals.LOG.debug(localize("createFile.log.debug.creatingNewDataSet", "Creating new data set")); } - let typeEnum; - let createOptions; - switch (type) { - case localize("createFile.dataSetBinary", "Data Set Binary"): - typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_BINARY; - createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-Binary"); - break; - case localize("createFile.dataSetC", "Data Set C"): - typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_C; - createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-C"); - break; - case localize("createFile.dataSetClassic", "Data Set Classic"): - typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_CLASSIC; - createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-Classic"); - break; - case localize("createFile.dataSetPartitioned", "Data Set Partitioned"): - typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_PARTITIONED; - createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-PDS"); - break; - case localize("createFile.dataSetSequential", "Data Set Sequential"): - typeEnum = zowe.CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL; - createOptions = vscode.workspace.getConfiguration("Zowe-Default-Datasets-PS"); - break; - } + const typeEnumAndOptions = this.getDataSetTypeAndOptions(type); // get name of data set let name = await vscode.window.showInputBox({placeHolder: localize("dataset.name", "Name of Data Set")}); @@ -269,7 +336,9 @@ export async function createFile(node: IZoweDatasetTreeNode, datasetProvider: IZ name = name.trim().toUpperCase(); try { - await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).createDataSet(typeEnum, name, createOptions); + await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).createDataSet(typeEnumAndOptions.typeEnum, + name, + typeEnumAndOptions.createOptions); node.dirty = true; const theFilter = await datasetProvider.createFilterString(name, node); @@ -514,15 +583,16 @@ export async function deleteDataset(node: IZoweTreeNode, datasetProvider: IZoweT let label = ""; let fav = false; try { - if (node.getParent().contextValue.includes(globals.FAVORITE_CONTEXT)) { + const parentContext = node.getParent().contextValue; + if (parentContext.includes(globals.FAVORITE_CONTEXT)) { label = node.label.substring(node.label.indexOf(":") + 1).trim(); fav = true; - } else if (node.getParent().contextValue.includes(globals.DS_PDS_CONTEXT + globals.FAV_SUFFIX)) { + } else if (parentContext.includes(globals.DS_PDS_CONTEXT + globals.FAV_SUFFIX)) { label = node.getParent().getLabel().substring(node.getParent().getLabel().indexOf(":") + 1).trim() + "(" + node.getLabel() + ")"; fav = true; - } else if (node.getParent().contextValue.includes(globals.DS_SESSION_CONTEXT)) { + } else if (parentContext.includes(globals.DS_SESSION_CONTEXT)) { label = node.getLabel(); - } else if (node.getParent().contextValue.includes(globals.DS_PDS_CONTEXT)) { + } else if (parentContext.includes(globals.DS_PDS_CONTEXT)) { label = node.getParent().getLabel() + "(" + node.getLabel() + ")"; } else { throw Error(localize("deleteDataSet.invalidNode.error", "deleteDataSet() called from invalid node.")); diff --git a/src/extension.ts b/src/extension.ts index d423846760..21f6557166 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -195,6 +195,7 @@ function initDatasetProvider(context: vscode.ExtensionContext, datasetProvider: vscode.commands.registerCommand("zowe.createMember", (node) => dsActions.createMember(node, datasetProvider)); vscode.commands.registerCommand("zowe.deleteDataset", (node) => dsActions.deleteDataset(node, datasetProvider)); vscode.commands.registerCommand("zowe.deletePDS", (node) => dsActions.deleteDataset(node, datasetProvider)); + vscode.commands.registerCommand("zowe.allocateLike", (node) => dsActions.allocateLike(datasetProvider, node)); vscode.commands.registerCommand("zowe.uploadDialog", (node) => dsActions.uploadDialog(node, datasetProvider)); vscode.commands.registerCommand("zowe.deleteMember", (node) => dsActions.deleteDataset(node, datasetProvider)); vscode.commands.registerCommand("zowe.editMember", (node) => dsActions.openPS(node, false, datasetProvider)); diff --git a/src/globals.ts b/src/globals.ts index be9d7d0bcb..7da1304586 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -21,7 +21,7 @@ export let USS_DIR; export let DS_DIR; export let ISTHEIA: boolean = false; // set during activate export let LOG: Logger; -export const COMMAND_COUNT = 77; +export const COMMAND_COUNT = 78; export const CONTEXT_PREFIX = "_"; export const FAV_SUFFIX = CONTEXT_PREFIX + "fav"; export const RC_SUFFIX = CONTEXT_PREFIX + "rc="; diff --git a/src/shared/actions.ts b/src/shared/actions.ts index 7804b7bacc..c3bf62c146 100644 --- a/src/shared/actions.ts +++ b/src/shared/actions.ts @@ -44,11 +44,11 @@ export async function searchInAllLoadedItems(datasetProvider?: IZoweTree - (temp.shortLabel === oldLabel) && (temp.fullPath.substr(0, temp.fullPath.indexOf(oldLabel)) === parentPath) - ); - const newName = await vscode.window.showInputBox({value: oldLabel.replace(/^\[.+\]:\s/, "")}); - if (newName && newName !== oldLabel) { - try { - let newNamePath = path.join(parentPath + newName); - newNamePath = newNamePath.replace(/\\/g, "/"); // Added to cover Windows backslash issue - const oldNamePath = originalNode.fullPath; + // Could be a favorite or regular entry always deal with the regular entry + const oldLabel = originalNode.label; + const parentPath = originalNode.fullPath.substr(0, originalNode.fullPath.indexOf(oldLabel)); + // Check if an old favorite exists for this node + const oldFavorite: IZoweUSSTreeNode = contextually.isFavorite(originalNode) ? originalNode : this.mFavorites.find((temp: ZoweUSSNode) => + (temp.shortLabel === oldLabel) && (temp.fullPath.substr(0, temp.fullPath.indexOf(oldLabel)) === parentPath) + ); + const loadedNodes = await this.getAllLoadedItems(); + const nodeType = contextually.isFolder(originalNode) ? "folder" : "file"; + const options: vscode.InputBoxOptions = { + prompt: localize("renameUSSNode.enterName", + "Enter a new name for the {0}", nodeType), + value: oldLabel.replace(/^\[.+\]:\s/, ""), + ignoreFocusOut: true, + validateInput: (value) => { + for (const node of loadedNodes) { + if (value === node.label.trim() && contextually.isFolder(node)) { + return localize("renameUSSNode.duplicateName", + "A {0} already exists with this name. Please choose a different one.", + nodeType); + } + } + return null; + } + }; + const newName = await vscode.window.showInputBox(options); + if (newName && newName !== oldLabel) { + try { + let newNamePath = path.join(parentPath + newName); + newNamePath = newNamePath.replace(/\\/g, "/"); // Added to cover Windows backslash issue + const oldNamePath = originalNode.fullPath; - const hasClosedTab = await originalNode.rename(newNamePath); - await ZoweExplorerApiRegister.getUssApi( - originalNode.getProfile()).rename(oldNamePath, newNamePath); - await originalNode.refreshAndReopen(hasClosedTab); + const hasClosedTab = await originalNode.rename(newNamePath); + await ZoweExplorerApiRegister.getUssApi( + originalNode.getProfile()).rename(oldNamePath, newNamePath); + await originalNode.refreshAndReopen(hasClosedTab); - if (oldFavorite) { - this.removeFavorite(oldFavorite); - oldFavorite.rename(newNamePath); - this.addFavorite(oldFavorite); + if (oldFavorite) { + this.removeFavorite(oldFavorite); + oldFavorite.rename(newNamePath); + this.addFavorite(oldFavorite); + } + } catch (err) { + errorHandling(err, originalNode.mProfileName, localize("renameUSSNode.error", "Unable to rename node: ") + err.message); + throw (err); } - } catch (err) { - errorHandling(err, originalNode.mProfileName, localize("renameUSSNode.error", "Unable to rename node: ") + err.message); - throw (err); } } - } public open(node: IZoweUSSTreeNode, preview: boolean) { throw new Error("Method not implemented."); } @@ -281,10 +299,10 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree