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

updated with local filtering of jobs #2476

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e7da1d0
updated with local filtering of jobs
SanthoshiBoyina1 Sep 22, 2023
f780957
updated changelog file
SanthoshiBoyina1 Sep 23, 2023
59737e9
Updated sample data in actions.unit.test.ts file
SanthoshiBoyina1 Sep 23, 2023
88024cc
Updated with error handling
SanthoshiBoyina1 Sep 28, 2023
936c909
resolved conflicts
SanthoshiBoyina1 Sep 28, 2023
2df66b6
updated with local filtering of jobs
SanthoshiBoyina1 Sep 22, 2023
ada826b
updated changelog file
SanthoshiBoyina1 Sep 23, 2023
b78a4f2
resolved conflicts
SanthoshiBoyina1 Sep 28, 2023
000c87f
updated use of tree provider
SanthoshiBoyina1 Sep 29, 2023
e88cbee
resolved latest conflicts
SanthoshiBoyina1 Sep 29, 2023
01f14b5
addressed the code smell
SanthoshiBoyina1 Sep 29, 2023
5098ac9
Improved code coverage in init.ts
SanthoshiBoyina1 Sep 29, 2023
0e0d4a3
updated with filter jobs
SanthoshiBoyina1 Oct 10, 2023
5da04b7
updated global.ts file
SanthoshiBoyina1 Oct 11, 2023
1126759
feat(ds): Sort by date modified, name, or user ID
traeok Oct 3, 2023
cf7e333
fix(ds): deconstruct date, time, seconds from API response
traeok Oct 3, 2023
378881c
fix(ds, jobs): update node children check; adjust sortJobsBy function…
traeok Oct 3, 2023
32b308c
tests(ds): Add test cases for DatasetTree.sortBy
traeok Oct 4, 2023
26df708
ds: move 'Sort by' options to session context menu
traeok Oct 5, 2023
0f48273
ds: Change sort UX to single menu option
traeok Oct 5, 2023
67411ef
ds: Update sorting UX to use inline icon beside session
traeok Oct 5, 2023
59a565f
fix(ds, jobs): Add session-level and per-PDS sort, cache job sort method
traeok Oct 5, 2023
841a82e
tests(ds, jobs): Fix test cases to work with UX changes
traeok Oct 6, 2023
f4d67e2
feat(ds): Filter PDS members (session-level and PDS-level); fix broke…
traeok Oct 9, 2023
7de9bb3
fix: some SonarCloud code smells
traeok Oct 9, 2023
0801cee
tests(ds): Bump patch coverage for DatasetTree.filterPdsMembers
traeok Oct 9, 2023
4b287a9
chore(ds/sort): code refactor, add typedoc, cleanup strings
traeok Oct 9, 2023
30ebd6e
fix(ds/sort): some SonarCloud code smells
traeok Oct 9, 2023
3750998
test(ds): Add test cases for filter/sort options
traeok Oct 10, 2023
5c7ac39
fix: sort direction should only apply to PDS members
traeok Oct 10, 2023
0c9acae
fix(test): Fix 'fake session node' sort options in test case
traeok Oct 10, 2023
b1ba6a7
fix, chore: remove unnecessary refresh in sortBy; update CHANGELOGS
traeok Oct 10, 2023
73b65ed
fix(ds/filter): Refresh PDS if session/PDS had a previous filter
traeok Oct 10, 2023
482d045
fix(ds): Add sort/filter support for FTP response; fix test cases
traeok Oct 10, 2023
f65cc2c
fix(jobs/sort): Proper handling for jobs with null retcodes
traeok Oct 12, 2023
74f6869
fix(jobs/sort): use nullish coalescing for retcode/status check
traeok Oct 12, 2023
1d657e6
feat(ds, jobs): Show status bar msg when sort/filter is applied
traeok Oct 17, 2023
4492f42
chore(deps): Bump @babel/traverse from 7.17.10 to 7.23.2
dependabot[bot] Oct 17, 2023
e6f988b
updated with local filtering of jobs
SanthoshiBoyina1 Sep 22, 2023
65c8cdc
Updated sample data in actions.unit.test.ts file
SanthoshiBoyina1 Sep 23, 2023
25839de
resolved conflicts
SanthoshiBoyina1 Sep 28, 2023
352f4ba
updated changelog file
SanthoshiBoyina1 Sep 23, 2023
e30a00b
updated use of tree provider
SanthoshiBoyina1 Sep 29, 2023
91ce7bb
resolved latest conflicts
SanthoshiBoyina1 Sep 29, 2023
a4e1e05
updated with filter jobs
SanthoshiBoyina1 Oct 10, 2023
f8559cc
updated with changes
SanthoshiBoyina1 Oct 18, 2023
14ebbc8
Resolved conflicts
SanthoshiBoyina1 Oct 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/zowe-explorer-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t

- Added optional `getTag` function to `ZoweExplorerAPI.IUss` for getting the tag of a file on USS.
- Added new API {ZE Extender MetaData} to allow extenders to have the metadata of registered extenders to aid in team configuration file creation from a view that isn't Zowe Explorer's. [#2394](https://github.com/zowe/vscode-extension-for-zowe/issues/2394)
- Add `sort` and `filter` optional variables for storing sort/filter options alongside tree nodes. [#2420](https://github.com/zowe/vscode-extension-for-zowe/issues/2420)
- Add `stats` optional variable for storing dataset stats (such as user, modified date, etc.)
- Add option enums and types for sorting, filtering and sort direction in tree nodes. [#2420](https://github.com/zowe/vscode-extension-for-zowe/issues/2420)

### Bug fixes

Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from "./tree/ZoweExplorerTreeApi";
export * from "./tree/ZoweTreeNode";
export * from "./tree/IZoweTree";
export * from "./tree/IZoweTreeNode";
export * from "./tree/sorting";
export * from "./utils";
export * from "./vscode/ZoweVsCodeExtension";
export * from "./vscode/ui";
Expand Down
19 changes: 19 additions & 0 deletions packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as vscode from "vscode";
import { IJob, imperative } from "@zowe/cli";
import { IZoweTree } from "./IZoweTree";
import { FileAttributes } from "../utils/files";
import { DatasetFilter, NodeSort } from "./sorting";

export type IZoweNodeType = IZoweDatasetTreeNode | IZoweUSSTreeNode | IZoweJobTreeNode;

Expand Down Expand Up @@ -78,6 +79,10 @@ export interface IZoweTreeNode {
* whether the node was double-clicked
*/
wasDoubleClicked?: boolean;
/**
* Sorting method for this node's children
*/
sort?: NodeSort;
/**
* Retrieves the node label
*/
Expand Down Expand Up @@ -120,6 +125,12 @@ export interface IZoweTreeNode {
setSessionToChoice(sessionObj: imperative.Session): void;
}

export type DatasetStats = {
user: string;
// built from "m4date", "mtime" and "msec" variables from z/OSMF API response
modifiedDate: Date;
};

/**
* Extended interface for Zowe Dataset tree nodes.
*
Expand All @@ -135,6 +146,14 @@ export interface IZoweDatasetTreeNode extends IZoweTreeNode {
* Search criteria for a Dataset member search
*/
memberPattern?: string;
/**
* Additional statistics about this data set
*/
stats?: Partial<DatasetStats>;
/**
* Filter method for this data set's children
*/
filter?: DatasetFilter;
/**
* Retrieves child nodes of this IZoweDatasetTreeNode
*
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/src/tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*
*/

export * from "./sorting";
export * from "./ZoweExplorerTreeApi";
export * from "./ZoweTreeNode";
export * from "./IZoweTree";
Expand Down
43 changes: 43 additions & 0 deletions packages/zowe-explorer-api/src/tree/sorting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/

export enum DatasetSortOpts {
Name,
LastModified,
UserId,
}

export enum SortDirection {
Ascending,
Descending,
}

export enum DatasetFilterOpts {
LastModified,
UserId,
}

export type DatasetFilter = {
method: DatasetFilterOpts;
value: string;
};

export type NodeSort = {
method: DatasetSortOpts | JobSortOpts;
direction: SortDirection;
};

export enum JobSortOpts {
Id,
DateSubmitted,
Name,
ReturnCode,
}
6 changes: 4 additions & 2 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ 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)
- Introduce a new user interface for managing profiles via right-click action "Manage Profile".
- Added "Sort Jobs" feature for job nodes in Jobs tree view: accessible via sort icon or right-clicking on session node. [#2257](https://github.com/zowe/vscode-extension-for-zowe/issues/2251)
- Added new edit feature on `Edit Attributes` view for changing file tags on USS [#2113](https://github.com/zowe/vscode-extension-for-zowe/issues/2113)
- Added new API {ZE Extender MetaData} to allow extenders to have the metadata of registered extenders to aid in team configuration file creation from a view that isn't Zowe Explorer's. [#2394](https://github.com/zowe/vscode-extension-for-zowe/issues/2394)
- Added "Sort PDS members" feature in Data Sets tree view: accessible via sort icon on session node, or by right-clicking a PDS or session. [#2420](https://github.com/zowe/vscode-extension-for-zowe/issues/2420)
- Added "Filter PDS members" feature in Data Sets tree view: accessible via filter icon on session node, or by right-clicking a PDS or session. [#2420](https://github.com/zowe/vscode-extension-for-zowe/issues/2420)
- Added "Local Filtering of Jobs Tree" feature for job nodes in Jobs tree view. [#2476](https://github.com/zowe/vscode-extension-for-zowe/issues/2476)

### Bug fixes

Expand Down
11 changes: 8 additions & 3 deletions packages/zowe-explorer/__tests__/__unit__/ZoweNode.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { List, imperative } from "@zowe/cli";
import { Profiles } from "../../src/Profiles";
import * as globals from "../../src/globals";
import { ZoweLogger } from "../../src/utils/LoggerUtils";
import { DatasetSortOpts, SortDirection } from "@zowe/zowe-explorer-api";

describe("Unit Tests (Jest)", () => {
// Globals
Expand Down Expand Up @@ -238,7 +239,6 @@ describe("Unit Tests (Jest)", () => {
undefined,
profileOne
);
infoChild.id = "root.Use the search button to display data sets";
rootNode.contextValue = globals.DS_SESSION_CONTEXT;
rootNode.dirty = false;
await expect(await rootNode.getChildren()).toEqual([infoChild]);
Expand All @@ -259,7 +259,6 @@ describe("Unit Tests (Jest)", () => {
undefined,
profileOne
);
infoChild.id = "root.Use the search button to display data sets";
rootNode.contextValue = globals.DS_SESSION_CONTEXT;
await expect(await rootNode.getChildren()).toEqual([infoChild]);
});
Expand Down Expand Up @@ -356,11 +355,16 @@ describe("Unit Tests (Jest)", () => {
};
}),
});
const sessionNode = {
getSessionNode: jest.fn(),
sort: { method: DatasetSortOpts.Name, direction: SortDirection.Ascending },
} as unknown as ZoweDatasetNode;
const getSessionNodeSpy = jest.spyOn(ZoweDatasetNode.prototype, "getSessionNode").mockReturnValue(sessionNode);
// Creating a rootNode
const pds = new ZoweDatasetNode(
"[root]: something",
vscode.TreeItemCollapsibleState.Collapsed,
{ getSessionNode: jest.fn() } as unknown as ZoweDatasetNode,
sessionNode,
session,
undefined,
undefined,
Expand All @@ -383,6 +387,7 @@ describe("Unit Tests (Jest)", () => {
expect(pdsChildren[0].contextValue).toEqual(globals.DS_FILE_ERROR_CONTEXT);
expect(pdsChildren[1].label).toEqual("GOODMEM1");
expect(pdsChildren[1].contextValue).toEqual(globals.DS_MEMBER_CONTEXT);
getSessionNodeSpy.mockRestore();
});

/*************************************************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as fs from "fs";
import * as zowe from "@zowe/cli";
import { DatasetTree } from "../../../src/dataset/DatasetTree";
import { ZoweDatasetNode } from "../../../src/dataset/ZoweDatasetNode";
import { Gui, IZoweDatasetTreeNode, ProfilesCache, ValidProfileEnum } from "@zowe/zowe-explorer-api";
import { DatasetFilterOpts, Gui, IZoweDatasetTreeNode, ProfilesCache, ValidProfileEnum } from "@zowe/zowe-explorer-api";
import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister";
import { Profiles } from "../../../src/Profiles";
import * as utils from "../../../src/utils/ProfilesUtils";
Expand Down Expand Up @@ -2696,3 +2696,205 @@ describe("Dataset Tree Unit Tests - Function initializeFavorites", () => {
expect(() => testTree.initializeFavorites(log)).not.toThrow();
});
});
describe("Dataset Tree Unit Tests - Sorting and Filtering operations", () => {
const tree = new DatasetTree();
const nodesForSuite = (): Record<string, IZoweDatasetTreeNode> => {
const session = new ZoweDatasetNode("testSession", vscode.TreeItemCollapsibleState.Collapsed, null, createISession());
session.contextValue = globals.DS_SESSION_CONTEXT;
const pds = new ZoweDatasetNode("testPds", vscode.TreeItemCollapsibleState.Collapsed, session, createISession());
pds.contextValue = globals.DS_PDS_CONTEXT;

const nodeA = new ZoweDatasetNode("A", vscode.TreeItemCollapsibleState.Collapsed, pds, createISession());
nodeA.stats = { user: "someUser", modifiedDate: new Date() };
const nodeB = new ZoweDatasetNode("B", vscode.TreeItemCollapsibleState.Collapsed, pds, createISession());
nodeB.stats = { user: "anotherUser", modifiedDate: new Date("2022-01-01T12:00:00") };
const nodeC = new ZoweDatasetNode("C", vscode.TreeItemCollapsibleState.Collapsed, pds, createISession());
nodeC.stats = { user: "someUser", modifiedDate: new Date("2022-03-15T16:30:00") };
pds.children = [nodeA, nodeB, nodeC];
session.children = [pds];

return {
session,
pds,
};
};

const getBlockMocks = (): Record<string, jest.SpyInstance> => ({
nodeDataChanged: jest.spyOn(DatasetTree.prototype, "nodeDataChanged"),
refreshElement: jest.spyOn(DatasetTree.prototype, "refreshElement"),
showQuickPick: jest.spyOn(Gui, "showQuickPick"),
showInputBox: jest.spyOn(Gui, "showInputBox"),
});

afterEach(() => {
const mocks = getBlockMocks();
for (const mock of Object.values(mocks)) {
mock.mockClear();
}
});

afterAll(() => {
const mocks = getBlockMocks();
for (const mock of Object.values(mocks)) {
mock.mockRestore();
}
});

describe("sortBy & sortPdsMembersDialog", () => {
// for sorting, we shouldn't need to refresh since all nodes
// should be intact, just in a different order
it("does nothing if no children exist", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
// case 1: called on PDS node
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(case-sensitive) Name (default)" });
nodes.pds.children = [];
await tree.sortPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).not.toHaveBeenCalled();

// case 2: called on session node
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(case-sensitive) Name (default)" });
nodes.session.children = [];
await tree.sortPdsMembersDialog(nodes.session);
expect(mocks.nodeDataChanged).not.toHaveBeenCalled();
});

it("sorts by name", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(case-sensitive) Name (default)" });
await tree.sortPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["A", "B", "C"]);
});

it("sorts by last modified date", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(calendar) Date Modified" });
await tree.sortPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["B", "C", "A"]);
});

it("sorts by user ID", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(account) User ID" });
await tree.sortPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["B", "A", "C"]);
});

it("returns to sort selection dialog when sort direction selection is canceled", async () => {
const sortPdsMembersDialog = jest.spyOn(tree, "sortPdsMembersDialog");
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce({ label: "$(fold) Sort Direction" });
mocks.showQuickPick.mockResolvedValueOnce(undefined);
await tree.sortPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).not.toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(sortPdsMembersDialog).toHaveBeenCalledTimes(2);
});
});

describe("filterBy & filterPdsMembersDialog", () => {
afterEach(() => {
const mocks = getBlockMocks();
for (const mock of Object.values(mocks)) {
mock.mockReset();
}
});

afterAll(() => {
const mocks = getBlockMocks();
for (const mock of Object.values(mocks)) {
mock.mockRestore();
}
});

it("calls refreshElement if PDS children were removed from a previous filter", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce("$(calendar) Date Modified" as any);
mocks.showInputBox.mockResolvedValueOnce("2022-01-01");

nodes.pds.filter = { method: DatasetFilterOpts.UserId, value: "invalidUserId" };
nodes.pds.children = [];
await tree.filterPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).not.toHaveBeenCalled();
expect(mocks.refreshElement).toHaveBeenCalledWith(nodes.pds);
});

it("returns to filter selection dialog when filter entry is canceled", async () => {
const filterPdsMembersSpy = jest.spyOn(tree, "filterPdsMembersDialog");
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce("$(calendar) Date Modified" as any);
mocks.showInputBox.mockResolvedValueOnce(undefined);
await tree.filterPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).not.toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(filterPdsMembersSpy).toHaveBeenCalledTimes(2);
});

it("filters single PDS by last modified date", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce("$(calendar) Date Modified" as any);
mocks.showInputBox.mockResolvedValueOnce("2022-03-15");
await tree.filterPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["C"]);
});

it("filters single PDS by user ID", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
mocks.showQuickPick.mockResolvedValueOnce("$(account) User ID" as any);
mocks.showInputBox.mockResolvedValueOnce("anotherUser");
await tree.filterPdsMembersDialog(nodes.pds);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["B"]);
});

it("filters PDS members using the session node filter", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
const uidString = "$(account) User ID" as any;
const anotherUser = "anotherUser";
mocks.showQuickPick.mockResolvedValueOnce(uidString).mockResolvedValueOnce(uidString);
mocks.showInputBox.mockResolvedValueOnce(anotherUser).mockResolvedValueOnce(anotherUser);

// case 1: old filter was set on session, just refresh PDS to use new filter
nodes.session.filter = {
method: DatasetFilterOpts.LastModified,
value: "2020-01-01",
};
await tree.filterPdsMembersDialog(nodes.session);
expect(mocks.refreshElement).toHaveBeenCalled();

// case 2: no old filter present, PDS has children to be filtered
nodes.session.filter = undefined;
await tree.filterPdsMembersDialog(nodes.session);
expect(mocks.nodeDataChanged).toHaveBeenCalled();
});

it("clears filter for a PDS when selected in dialog", async () => {
const mocks = getBlockMocks();
const nodes = nodesForSuite();
const resp = "$(clear-all) Clear filter for PDS" as any;
mocks.showQuickPick.mockResolvedValueOnce(resp);
const updateFilterForNode = jest.spyOn(DatasetTree.prototype, "updateFilterForNode");
await tree.filterPdsMembersDialog(nodes.pds);
expect(mocks.refreshElement).not.toHaveBeenCalled();
expect(updateFilterForNode).toHaveBeenCalledWith(nodes.pds, null, false);
});
});
});
Loading