diff --git a/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts index 3de900fd49..2aa2051574 100644 --- a/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts @@ -35,6 +35,7 @@ import * as globals from "../../../src/globals"; import { createDatasetSessionNode, createDatasetTree } from "../../../__mocks__/mockCreators/datasets"; import { Profiles } from "../../../src/Profiles"; import * as SpoolProvider from "../../../src/SpoolProvider"; +import * as refreshActions from "../../../src/shared/refresh"; import { UIViews } from "../../../src/shared/ui-views"; const activeTextEditorDocument = jest.fn(); @@ -1003,81 +1004,63 @@ describe("Jobs Actions Unit Tests - Function refreshJobsServer", () => { }); }); -describe("Jobs Actions Unit Tests - Function deleteCommand", () => { - function createBlockMocks() { - const newMocks = { - mockShowWarningMessage: jest.fn(), - session: createISession(), - treeView: createTreeView(), - iJob: createIJobObject(), - imperativeProfile: createIProfile(), - testJobsTree: null, - }; - newMocks.testJobsTree = createJobsTree( - newMocks.session, - newMocks.iJob, - newMocks.imperativeProfile, - newMocks.treeView - ); +describe("job deletion command", () => { + // general mocks + createGlobalMocks(); + const session = createISession(); + const profile = createIProfile(); + const job = createIJobObject(); + it("should delete a job from the jobs provider and refresh the current job session", async () => { + // arrange + const warningDialogStub = jest.fn(); Object.defineProperty(vscode.window, "showWarningMessage", { - value: newMocks.mockShowWarningMessage, + value: warningDialogStub, configurable: true, }); - - return newMocks; - } - it("Tests that delete informs the user that a job was deleted", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocks(); - const node = new Job( - "jobtest", - vscode.TreeItemCollapsibleState.Expanded, - null, - blockMocks.session, - blockMocks.iJob, - blockMocks.imperativeProfile - ); - blockMocks.mockShowWarningMessage.mockResolvedValueOnce("Delete"); - - await jobActions.deleteCommand(node, blockMocks.testJobsTree); - expect(mocked(vscode.window.showInformationMessage).mock.calls.length).toBe(1); - expect(mocked(vscode.window.showInformationMessage).mock.calls[0][0]).toEqual( - `Job ${node.job.jobname}(${node.job.jobid}) deleted.` - ); + warningDialogStub.mockResolvedValueOnce("Delete"); + jest.spyOn(refreshActions, "refreshAll"); + const jobsProvider = createJobsTree(session, job, profile, createTreeView()); + jobsProvider.delete.mockResolvedValueOnce(Promise.resolve()); + const jobNode = new Job("jobtest", vscode.TreeItemCollapsibleState.Expanded, null, session, job, profile); + // act + await jobActions.deleteCommand(jobsProvider, jobNode); + // assert + expect(mocked(jobsProvider.delete)).toBeCalledWith(jobNode); + expect(refreshActions.refreshAll).toHaveBeenCalledWith(jobsProvider); }); - it("Tests that delete informs the user that a job deletion was cancelled", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocks(); - const node = new Job( - "jobtest", - vscode.TreeItemCollapsibleState.Expanded, - null, - blockMocks.session, - blockMocks.iJob, - blockMocks.imperativeProfile - ); - blockMocks.mockShowWarningMessage.mockResolvedValueOnce("Cancel"); - await jobActions.deleteCommand(node, blockMocks.testJobsTree); - expect(mocked(vscode.window.showInformationMessage).mock.calls.length).toBe(1); - expect(mocked(vscode.window.showInformationMessage).mock.calls[0][0]).toEqual(`Delete action was cancelled.`); + it("should not delete a job in case user cancelled deletion", async () => { + // arrange + const warningDialogStub = jest.fn(); + Object.defineProperty(vscode.window, "showWarningMessage", { + value: warningDialogStub, + configurable: true, + }); + warningDialogStub.mockResolvedValueOnce("Cancel"); + const jobsProvider = createJobsTree(session, job, profile, createTreeView()); + jobsProvider.delete.mockResolvedValueOnce(Promise.resolve()); + const jobNode = new Job("jobtest", vscode.TreeItemCollapsibleState.Expanded, null, session, job, profile); + // act + await jobActions.deleteCommand(jobsProvider, jobNode); + // assert + expect(mocked(jobsProvider.delete)).not.toBeCalled(); }); - it("Tests that delete informs the user that a job deletion was cancelled if confirmation was exited", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocks(); - const node = new Job( - "jobtest", - vscode.TreeItemCollapsibleState.Expanded, - null, - blockMocks.session, - blockMocks.iJob, - blockMocks.imperativeProfile - ); - blockMocks.mockShowWarningMessage.mockResolvedValueOnce(undefined); - await jobActions.deleteCommand(node, blockMocks.testJobsTree); - expect(mocked(vscode.window.showInformationMessage).mock.calls.length).toBe(1); - expect(mocked(vscode.window.showInformationMessage).mock.calls[0][0]).toEqual(`Delete action was cancelled.`); + it("should not refresh the current job session after an error during job deletion", async () => { + // arrange + const warningDialogStub = jest.fn(); + Object.defineProperty(vscode.window, "showWarningMessage", { + value: warningDialogStub, + configurable: true, + }); + warningDialogStub.mockResolvedValueOnce("Delete"); + const jobsProvider = createJobsTree(session, job, profile, createTreeView()); + jobsProvider.delete.mockResolvedValueOnce(Promise.reject(new Error("something went wrong!"))); + const jobNode = new Job("jobtest", vscode.TreeItemCollapsibleState.Expanded, null, session, job, profile); + // act + await jobActions.deleteCommand(jobsProvider, jobNode); + // assert + expect(mocked(jobsProvider.delete)).toBeCalledWith(jobNode); }); }); diff --git a/packages/zowe-explorer/src/extension.ts b/packages/zowe-explorer/src/extension.ts index c1f8f017b2..7813ca9e79 100644 --- a/packages/zowe-explorer/src/extension.ts +++ b/packages/zowe-explorer/src/extension.ts @@ -138,6 +138,7 @@ export async function activate(context: vscode.ExtensionContext): Promise datasetProvider.addFavorite(node)); - vscode.commands.registerCommand("zowe.ds.refreshAll", () => refreshActions.refreshAll(datasetProvider)); + vscode.commands.registerCommand("zowe.ds.refreshAll", async () => { + await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); + await refreshActions.refreshAll(datasetProvider); + }); vscode.commands.registerCommand("zowe.ds.refreshNode", (node) => dsActions.refreshPS(node)); vscode.commands.registerCommand("zowe.ds.refreshDataset", (node) => dsActions.refreshDataset(node, datasetProvider) @@ -295,7 +299,10 @@ function initUSSProvider(context: vscode.ExtensionContext, ussFileProvider: IZow vscode.commands.registerCommand("zowe.uss.addSession", async () => ussFileProvider.createZoweSession(ussFileProvider) ); - vscode.commands.registerCommand("zowe.uss.refreshAll", () => refreshActions.refreshAll(ussFileProvider)); + vscode.commands.registerCommand("zowe.uss.refreshAll", async () => { + await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); + await refreshActions.refreshAll(ussFileProvider); + }); vscode.commands.registerCommand("zowe.uss.refreshUSS", (node: IZoweUSSTreeNode) => node.refreshUSS()); vscode.commands.registerCommand("zowe.uss.refreshUSSInTree", (node: IZoweUSSTreeNode) => ussActions.refreshUSSInTree(node, ussFileProvider) @@ -368,13 +375,18 @@ function initJobsProvider(context: vscode.ExtensionContext, jobsProvider: IZoweT vscode.commands.registerCommand("zowe.jobs.zosJobsOpenspool", (session, spool, refreshTimestamp) => jobActions.getSpoolContent(session, spool, refreshTimestamp) ); - vscode.commands.registerCommand("zowe.jobs.deleteJob", async (job) => jobActions.deleteCommand(job, jobsProvider)); + vscode.commands.registerCommand("zowe.jobs.deleteJob", async (job, jobs) => + jobActions.deleteCommand(jobsProvider, job, jobs) + ); vscode.commands.registerCommand("zowe.jobs.runModifyCommand", (job) => jobActions.modifyCommand(job)); vscode.commands.registerCommand("zowe.jobs.runStopCommand", (job) => jobActions.stopCommand(job)); vscode.commands.registerCommand("zowe.jobs.refreshJobsServer", async (job) => jobActions.refreshJobsServer(job, jobsProvider) ); - vscode.commands.registerCommand("zowe.jobs.refreshAllJobs", async () => refreshActions.refreshAll(jobsProvider)); + vscode.commands.registerCommand("zowe.jobs.refreshAllJobs", async () => { + await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); + await refreshActions.refreshAll(jobsProvider); + }); vscode.commands.registerCommand("zowe.jobs.refreshJob", async (job) => jobActions.refreshJob(job, jobsProvider)); vscode.commands.registerCommand("zowe.jobs.addJobsSession", () => jobsProvider.createZoweSession(jobsProvider)); vscode.commands.registerCommand("zowe.jobs.setOwner", (job) => jobActions.setOwner(job, jobsProvider)); diff --git a/packages/zowe-explorer/src/job/actions.ts b/packages/zowe-explorer/src/job/actions.ts index dfab0a94ac..3dd5bb9a0e 100644 --- a/packages/zowe-explorer/src/job/actions.ts +++ b/packages/zowe-explorer/src/job/actions.ts @@ -20,6 +20,7 @@ import * as nls from "vscode-nls"; import { toUniqueJobFileUri } from "../SpoolProvider"; import { IProfileLoaded } from "@zowe/imperative"; import * as globals from "../globals"; +import { refreshAll as refreshAllJobs } from "../shared/refresh"; import { UIViews } from "../shared/ui-views"; // Set up localization @@ -254,88 +255,110 @@ export async function setPrefix(job: IZoweJobTreeNode, jobsProvider: IZoweTree) { - const nodesToDelete: string[] = []; - const deletedNodes: string[] = []; - const selectedNodes: IZoweJobTreeNode[] = jobsProvider.getTreeView().selection; - const nodes: IZoweJobTreeNode[] = selectedNodes.filter( - (jobNode) => jobNode.job !== undefined && jobNode.job !== null - ); +export async function deleteCommand( + jobsProvider: IZoweTree, + job?: IZoweJobTreeNode, + jobs?: IZoweJobTreeNode[] +) { + if (jobs && jobs.length) { + await deleteMultipleJobs( + jobs.filter((jobNode) => jobNode.job !== undefined && jobNode.job !== null), + jobsProvider + ); + return; + } + if (job) { + await deleteSingleJob(job, jobsProvider); + return; + } +} - if (nodes.length > 0) { - for (const node of nodes) { - nodesToDelete.push(`${node.job.jobname}(${node.job.jobid})`); - } - } else if (job) { - nodesToDelete.push(`${job.job.jobname}(${job.job.jobid})`); +async function deleteSingleJob(job: IZoweJobTreeNode, jobsProvider: IZoweTree): Promise { + const jobName = `${job.job.jobname}(${job.job.jobid})`; + const message = localize( + "deleteJobPrompt.confirmation.message", + "Are you sure you want to delete the following item?\nThis will permanently remove the following job from your system.\n\n{0}", + jobName.replace(/(,)/g, "\n") + ); + const deleteButton = localize("deleteJobPrompt.confirmation.delete", "Delete"); + const result = await vscode.window.showWarningMessage(message, { modal: true }, deleteButton); + if (!result || result === "Cancel") { + globals.LOG.debug(localize("deleteJobPrompt.confirmation.cancel.log.debug", "Delete action was canceled.")); + vscode.window.showInformationMessage( + localize("deleteJobPrompt.deleteCancelled", "Delete action was cancelled.") + ); + return; } - // confirmation message for deletion + try { + await jobsProvider.delete(job); + } catch (error) { + await errorHandling(error.toString(), job.getProfile().name, error.message.toString()); + return; + } + await refreshAllJobs(jobsProvider); + vscode.window.showInformationMessage(localize("deleteCommand.job", "Job {0} deleted.", jobName)); +} + +async function deleteMultipleJobs( + jobs: ReadonlyArray, + jobsProvider: IZoweTree +): Promise { const deleteButton = localize("deleteJobPrompt.confirmation.delete", "Delete"); + const toJobname = (jobNode: IZoweJobTreeNode) => `${jobNode.job.jobname}(${jobNode.job.jobid})`; const message = localize( "deleteJobPrompt.confirmation.message", - "Are you sure you want to delete the following {0} item(s)?\nThis will permanently remove the following job(s) from your system.\n\n{1}", - nodesToDelete.length, - nodesToDelete.toString().replace(/(,)/g, "\n") + "Are you sure you want to delete the following {0} items?\nThis will permanently remove the following jobs from your system.\n\n{1}", + jobs.length, + jobs.map(toJobname).toString().replace(/(,)/g, "\n") ); - let cancelled = false; - await vscode.window.showWarningMessage(message, { modal: true }, ...[deleteButton]).then((selection) => { - if (!selection || selection === "Cancel") { - globals.LOG.debug(localize("deleteJobPrompt.confirmation.cancel.log.debug", "Delete action was canceled.")); - cancelled = true; - } - }); - if (cancelled) { + const deleteChoice = await vscode.window.showWarningMessage(message, { modal: true }, deleteButton); + if (!deleteChoice || deleteChoice === "Cancel") { + globals.LOG.debug(localize("deleteJobPrompt.confirmation.cancel.log.debug", "Delete action was canceled.")); vscode.window.showInformationMessage( localize("deleteJobPrompt.deleteCancelled", "Delete action was cancelled.") ); return; } - - // delete selected multiple nodes - if (nodes.length > 1) { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: localize("deleteJobPrompt.deleteCounter", "Deleting nodes"), - cancellable: true, - }, - async (progress, token) => { - const total = 100; - for (const [index, currNode] of nodes.entries()) { - if (token.isCancellationRequested) { - vscode.window.showInformationMessage( - localize("deleteJobPrompt.deleteCancelled", "Delete action was cancelled.") - ); - return; - } - progress.report({ - message: `Deleting ${index + 1} of ${nodes.length}`, - increment: total / nodes.length, - }); - try { - await jobsProvider.delete(currNode); - deletedNodes.push(`${currNode.job.jobname}(${currNode.job.jobid})`); - } catch (err) { - globals.LOG.error(err); - } - } + const deletionResult: ReadonlyArray = await Promise.all( + jobs.map(async (job) => { + try { + await jobsProvider.delete(job); + return job; + } catch (error) { + return error; } - ); + }) + ); + const deletedJobs: ReadonlyArray = deletionResult + .map((result) => { + if (result instanceof Error) { + return undefined; + } + return result; + }) + .filter((result) => result !== undefined); + if (deletedJobs.length) { + await refreshAllJobs(jobsProvider); vscode.window.showInformationMessage( localize( "deleteCommand.multipleJobs", "The following jobs were deleted: {0}", - deletedNodes.toString().replace(/(,)/g, ", ") + deletedJobs.map(toJobname).toString().replace(/(,)/g, ", ") ) ); } - // Delete a single job node - else { - job = job ? job : nodes[0]; - await jobsProvider.delete(job); - vscode.window.showInformationMessage( - localize("deleteCommand.job", "Job {0} deleted.", `${job.job.jobname}(${job.job.jobid})`) - ); + const deletionErrors: ReadonlyArray = deletionResult + .map((result) => { + if (result instanceof Error) { + const error = result; + return error; + } + return undefined; + }) + .filter((result) => result !== undefined); + if (deletionErrors.length) { + const errorMessages = deletionErrors.map((error) => error.message).join(", "); + const userMessage = `There were errors during jobs deletion: ${errorMessages}`; + await errorHandling(userMessage); } - await vscode.commands.executeCommand("zowe.jobs.refreshAllJobs"); } diff --git a/packages/zowe-explorer/src/shared/refresh.ts b/packages/zowe-explorer/src/shared/refresh.ts index c455f62024..d70e487f8c 100644 --- a/packages/zowe-explorer/src/shared/refresh.ts +++ b/packages/zowe-explorer/src/shared/refresh.ts @@ -25,7 +25,6 @@ import * as contextually from "../shared/context"; * @param {IZoweTree} treeProvider */ export async function refreshAll(treeProvider: IZoweTree) { - await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); treeProvider.mSessionNodes.forEach(async (sessNode) => { const setting = (await PersistentFilters.getDirectValue("Zowe-Automatic-Validation")) as boolean; if (contextually.isSessionNotFav(sessNode)) { diff --git a/packages/zowe-explorer/src/uss/actions.ts b/packages/zowe-explorer/src/uss/actions.ts index 41facc2865..5c6ae9cc4a 100644 --- a/packages/zowe-explorer/src/uss/actions.ts +++ b/packages/zowe-explorer/src/uss/actions.ts @@ -72,6 +72,7 @@ export async function createUSSNode( filePath = `${filePath}/${name}`; await ZoweExplorerApiRegister.getUssApi(node.getProfile()).create(filePath, nodeType); if (isTopLevel) { + await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance()); refreshAll(ussFileProvider); } else { ussFileProvider.refreshElement(node);