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

[Issue #1449] Update removeJob command to refresh the profiles cache separately from refreshing a tree #1524

Merged
merged 17 commits into from Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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";

const activeTextEditorDocument = jest.fn();

Expand Down Expand Up @@ -1000,81 +1001,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
128 changes: 88 additions & 40 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";

// Set up localization
nls.config({
Expand Down Expand Up @@ -248,63 +249,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 nodes
if (nodes.length > 0) {
for (const node of nodes) {
await jobsProvider.delete(node);
deletedNodes.push(`${node.job.jobname}(${node.job.jobid})`);
}
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
if (job && nodes.length <= 0) {
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 @@ -69,6 +69,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