Skip to content

Commit

Permalink
Merge pull request #1524 from zowe/fix-job-deletion-command-refreshing
Browse files Browse the repository at this point in the history
[Issue #1449] Update removeJob command to refresh the profiles cache separately from refreshing a tree
  • Loading branch information
jellypuno committed Nov 29, 2021
2 parents fd7efaa + 092ebc4 commit bc6001c
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 137 deletions.
119 changes: 51 additions & 68 deletions packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
});
});
20 changes: 16 additions & 4 deletions packages/zowe-explorer/src/extension.ts
Expand Up @@ -138,6 +138,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<ZoweEx
preferencesTempPath = updatedPreferencesTempPath;
}
if (e.affectsConfiguration("Zowe-Automatic-Validation")) {
await Profiles.getInstance().refresh(ZoweExplorerApiRegister.getInstance());
await refreshActions.refreshAll(datasetProvider);
await refreshActions.refreshAll(ussFileProvider);
await refreshActions.refreshAll(jobsProvider);
Expand Down Expand Up @@ -228,7 +229,10 @@ function initDatasetProvider(context: vscode.ExtensionContext, datasetProvider:
datasetProvider.createZoweSession(datasetProvider)
);
vscode.commands.registerCommand("zowe.ds.addFavorite", async (node) => 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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand Down
151 changes: 87 additions & 64 deletions packages/zowe-explorer/src/job/actions.ts
Expand Up @@ -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
Expand Down Expand Up @@ -254,88 +255,110 @@ export async function setPrefix(job: IZoweJobTreeNode, jobsProvider: IZoweTree<I
*
* @param jobsProvider The tree to which the node belongs
*/
export async function deleteCommand(job: IZoweJobTreeNode, jobsProvider: IZoweTree<IZoweJobTreeNode>) {
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<IZoweJobTreeNode>,
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<IZoweJobTreeNode>): Promise<void> {
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<IZoweJobTreeNode>,
jobsProvider: IZoweTree<IZoweJobTreeNode>
): Promise<void> {
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<IZoweJobTreeNode | Error> = await Promise.all(
jobs.map(async (job) => {
try {
await jobsProvider.delete(job);
return job;
} catch (error) {
return error;
}
);
})
);
const deletedJobs: ReadonlyArray<IZoweJobTreeNode> = 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<Error> = 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");
}
1 change: 0 additions & 1 deletion packages/zowe-explorer/src/shared/refresh.ts
Expand Up @@ -25,7 +25,6 @@ import * as contextually from "../shared/context";
* @param {IZoweTree} treeProvider
*/
export async function refreshAll(treeProvider: IZoweTree<IZoweTreeNode>) {
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)) {
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer/src/uss/actions.ts
Expand Up @@ -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);
Expand Down

0 comments on commit bc6001c

Please sign in to comment.