diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 5f57f98006..d6550315ec 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### New features and enhancements +- Added "Sort Jobs" feature for job nodes in Jobs tree view. [#2257](https://github.com/zowe/vscode-extension-for-zowe/issues/2251) + ### Bug fixes - Fixed submitting local JCL using command pallet option `Zowe Explorer: Submit JCL` by adding a check for chosen profile returned to continue the action. [#1625](https://github.com/zowe/vscode-extension-for-zowe/issues/1625) diff --git a/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts index 1b8e377e58..0f637993a6 100644 --- a/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts @@ -238,6 +238,9 @@ async function createGlobalMocks() { "zowe.jobs.startPolling", "zowe.jobs.stopPolling", "zowe.jobs.cancelJob", + "zowe.jobs.sortbyname", + "zowe.jobs.sortbyid", + "zowe.jobs.sortbyreturncode", "zowe.manualPoll", "zowe.updateSecureCredentials", "zowe.promptCredentials", 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 e4dce37dc0..85737a5c89 100644 --- a/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/job/actions.unit.test.ts @@ -41,6 +41,7 @@ import * as refreshActions from "../../../src/shared/refresh"; import * as sharedUtils from "../../../src/shared/utils"; import { ZoweLogger } from "../../../src/utils/LoggerUtils"; import { SpoolFile } from "../../../src/SpoolProvider"; +import { ZosJobsProvider } from "../../../src/job/ZosJobsProvider"; const activeTextEditorDocument = jest.fn(); @@ -85,6 +86,49 @@ function createGlobalMocks() { Object.defineProperty(ZoweLogger, "error", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "debug", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "trace", { value: jest.fn(), configurable: true }); + Object.defineProperty(vscode.window, "showInformationMessage", { value: jest.fn(), configurable: true }); + function settingJobObjects(job: zowe.IJob, setjobname: string, setjobid: string, setjobreturncode: string): zowe.IJob { + job.jobname = setjobname; + job.jobid = setjobid; + job.retcode = setjobreturncode; + return job; + } + const newMocks = jest.fn().mockReturnValue([ + (() => { + const JobNode1 = new Job( + "testProfile", + vscode.TreeItemCollapsibleState.None, + null, + createISession(), + settingJobObjects(createIJobObject(), "ZOWEUSR1", "JOB045123", "ABEND S222"), + createIProfile() + ); + return JobNode1; + })(), + (() => { + const JobNode2 = new Job( + "testProfile", + vscode.TreeItemCollapsibleState.None, + null, + createISession(), + settingJobObjects(createIJobObject(), "ZOWEUSR1", "JOB045120", "CC 0000"), + createIProfile() + ); + return JobNode2; + })(), + (() => { + const JobNode3 = new Job( + "testProfile", + vscode.TreeItemCollapsibleState.None, + null, + createISession(), + settingJobObjects(createIJobObject(), "ZOWEUSR2", "JOB045125", "CC 0000"), + createIProfile() + ); + return JobNode3; + })(), + ]); + return newMocks; } // Idea is borrowed from: https://github.com/kulshekhar/ts-jest/blob/master/src/util/testing.ts @@ -1300,3 +1344,60 @@ describe("Job Actions Unit Tests - Misc. functions", () => { expect(statusMsgSpy).toHaveBeenCalledWith(`$(sync~spin) Polling: ${testDoc.fileName}...`); }); }); +describe("sortjobsby function", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + it("if there are no jobs in the zosmf level yet", async () => { + createGlobalMocks(); + const testtree = new ZosJobsProvider(); + //act + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "jobname"); + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "jobid"); + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "retcode"); + //assert + expect(mocked(vscode.window.showInformationMessage)).toBeCalled(); + }); + it("sort by name if same sort by increasing id", async () => { + const globalMocks = createGlobalMocks(); + const testtree = new ZosJobsProvider(); + const expected = new ZosJobsProvider(); + testtree.mSessionNodes[0].children = [...[globalMocks()[2], globalMocks()[1], globalMocks()[0]]]; + expected.mSessionNodes[0].children = [...[globalMocks()[1], globalMocks()[0], globalMocks()[2]]]; + const sortbynamespy = jest.spyOn(jobActions, "sortJobsBy"); + //act + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "jobname"); + //asert + expect(sortbynamespy).toBeCalledWith(testtree.mSessionNodes[0], testtree, "jobname"); + expect(sortbynamespy).toHaveBeenCalled(); + expect(sortbynamespy.mock.calls[0][0].children).toStrictEqual(expected.mSessionNodes[0].children); + }); + it("sorts by increasing order of id", async () => { + const globalMocks = createGlobalMocks(); + const testtree = new ZosJobsProvider(); + const expected = new ZosJobsProvider(); + testtree.mSessionNodes[0].children = [...[globalMocks()[2], globalMocks()[1], globalMocks()[0]]]; + expected.mSessionNodes[0].children = [...[globalMocks()[1], globalMocks()[0], globalMocks()[2]]]; + const sortbyidspy = jest.spyOn(jobActions, "sortJobsBy"); + //act + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "jobid"); + //asert + expect(sortbyidspy).toBeCalledWith(testtree.mSessionNodes[0], testtree, "jobid"); + expect(sortbyidspy).toHaveBeenCalled(); + expect(sortbyidspy.mock.calls[0][0].children).toStrictEqual(expected.mSessionNodes[0].children); + }); + it("sort by retcode if same sort by increasing id", async () => { + const globalMocks = createGlobalMocks(); + const testtree = new ZosJobsProvider(); + const expected = new ZosJobsProvider(); + testtree.mSessionNodes[0].children = [...[globalMocks()[2], globalMocks()[1], globalMocks()[0]]]; + expected.mSessionNodes[0].children = [...[globalMocks()[0], globalMocks()[1], globalMocks()[2]]]; + const sortbyretcodespy = jest.spyOn(jobActions, "sortJobsBy"); + //act + await jobActions.sortJobsBy(testtree.mSessionNodes[0], testtree, "retcode"); + //asert + expect(sortbyretcodespy).toBeCalledWith(testtree.mSessionNodes[0], testtree, "retcode"); + expect(sortbyretcodespy).toHaveBeenCalled(); + expect(sortbyretcodespy.mock.calls[0][0].children).toStrictEqual(expected.mSessionNodes[0].children); + }); +}); diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index c999eba9aa..527726260b 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -121,6 +121,21 @@ } ], "commands": [ + { + "command": "zowe.jobs.sortbyreturncode", + "title": "%jobs.sortbyreturncode%", + "category": "Zowe Explorer" + }, + { + "command": "zowe.jobs.sortbyname", + "title": "%jobs.sortbyname%", + "category": "Zowe Explorer" + }, + { + "command": "zowe.jobs.sortbyid", + "title": "%jobs.sortbyid%", + "category": "Zowe Explorer" + }, { "command": "zowe.promptCredentials", "title": "%zowe.promptCredentials%", @@ -1357,6 +1372,21 @@ "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", "command": "zowe.jobs.deleteProfile", "group": "099_zowe_jobsProfileModification@99" + }, + { + "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", + "command": "zowe.jobs.sortbyid", + "group": "000_zowe_jobsProfileModification@1" + }, + { + "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", + "command": "zowe.jobs.sortbyname", + "group": "000_zowe_jobsProfileModification@2" + }, + { + "when": "view == zowe.jobs.explorer && viewItem =~ /^(?!.*_fav.*)server.*/ && !listMultiSelection", + "command": "zowe.jobs.sortbyreturncode", + "group": "000_zowe_jobsProfileModification@2" } ], "commandPalette": [ diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index 5930969b05..8bc5d8bf6c 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -146,5 +146,8 @@ "createZoweSchema.reload.button": "Reload Window", "createZoweSchema.reload.infoMessage": "Team Configuration file created. Location: {0}. \n Please reload your window.", "copyFile": "Copy", - "pasteFile": "Paste" + "pasteFile": "Paste", + "jobs.sortbyreturncode": "Sort by ReturnCode", + "jobs.sortbyname": "Sort by Name", + "jobs.sortbyid": "Sort by ID" } diff --git a/packages/zowe-explorer/src/globals.ts b/packages/zowe-explorer/src/globals.ts index 7bbba932b2..a503fe7332 100644 --- a/packages/zowe-explorer/src/globals.ts +++ b/packages/zowe-explorer/src/globals.ts @@ -35,7 +35,7 @@ export let DS_DIR: string; export let CONFIG_PATH; // set during activate export let ISTHEIA = false; // set during activate export let LOG: imperative.Logger; -export const COMMAND_COUNT = 110; +export const COMMAND_COUNT = 113; export const MAX_SEARCH_HISTORY = 5; export const MAX_FILE_HISTORY = 10; export const MS_PER_SEC = 1000; diff --git a/packages/zowe-explorer/src/job/actions.ts b/packages/zowe-explorer/src/job/actions.ts index 06fecac4c5..b7dd81ef9e 100644 --- a/packages/zowe-explorer/src/job/actions.ts +++ b/packages/zowe-explorer/src/job/actions.ts @@ -14,7 +14,7 @@ import * as zowe from "@zowe/cli"; import { errorHandling } from "../utils/ProfilesUtils"; import { Profiles } from "../Profiles"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; -import { Gui, ValidProfileEnum, IZoweTree, IZoweJobTreeNode } from "@zowe/zowe-explorer-api"; +import { Gui, IZoweTree, IZoweJobTreeNode } from "@zowe/zowe-explorer-api"; import { Job, Spool } from "./ZoweJobNode"; import * as nls from "vscode-nls"; import SpoolProvider, { encodeJobFile, getSpoolFiles, matchSpool } from "../SpoolProvider"; @@ -528,3 +528,16 @@ export async function cancelJobs(jobsProvider: IZoweTree, node await Gui.showMessage(localize("cancelJobs.succeeded", "Cancelled selected jobs successfully.")); } } +export async function sortJobsBy(jobs: IZoweJobTreeNode, jobsProvider: IZoweTree, key: keyof zowe.IJob): Promise { + if (jobs["children"].length == 0) { + await vscode.window.showInformationMessage("No jobs are present in the profile."); + } + jobs["children"].sort((x, y) => { + if (key !== "jobid" && x["job"][key] == y["job"][key]) { + return x["job"]["jobid"] > y["job"]["jobid"] ? 1 : -1; + } else { + return x["job"][key] > y["job"][key] ? 1 : -1; + } + }); + jobsProvider.refresh(); +} diff --git a/packages/zowe-explorer/src/job/init.ts b/packages/zowe-explorer/src/job/init.ts index 73b3f0ebb8..55cb416fa2 100644 --- a/packages/zowe-explorer/src/job/init.ts +++ b/packages/zowe-explorer/src/job/init.ts @@ -169,7 +169,11 @@ export async function initJobsProvider(context: vscode.ExtensionContext): Promis await jobActions.cancelJobs(jobsProvider, getSelectedNodeList(node, nodeList)); }) ); - + context.subscriptions.push(vscode.commands.registerCommand("zowe.jobs.sortbyname", (job) => jobActions.sortJobsBy(job, jobsProvider, "jobname"))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.jobs.sortbyid", (job) => jobActions.sortJobsBy(job, jobsProvider, "jobid"))); + context.subscriptions.push( + vscode.commands.registerCommand("zowe.jobs.sortbyreturncode", (job) => jobActions.sortJobsBy(job, jobsProvider, "retcode")) + ); initSubscribers(context, jobsProvider); return jobsProvider; }