diff --git a/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts b/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts index 7bd2349f31..d2ff45e178 100644 --- a/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts +++ b/packages/zowe-explorer-api/src/tree/IZoweTreeNode.ts @@ -20,6 +20,17 @@ export enum NodeAction { Download = "download", } +export enum DatasetSort { + LastModified, + Name, + UserId, +} + +export type DatasetStats = { + user: string; + m4date: Date; +}; + /** * The base interface for Zowe tree nodes that are implemented by vscode.TreeItem. * @@ -135,6 +146,14 @@ export interface IZoweDatasetTreeNode extends IZoweTreeNode { * Search criteria for a Dataset member search */ memberPattern?: string; + /** + * Sort members of a node by the given sorting method + */ + sortMethod?: DatasetSort; + /** + * Additional statistics about this data set + */ + stats?: Partial; /** * Retrieves child nodes of this IZoweDatasetTreeNode * diff --git a/packages/zowe-explorer/i18n/sample/package.i18n.json b/packages/zowe-explorer/i18n/sample/package.i18n.json index 8bc5d8bf6c..c157c7a0a2 100644 --- a/packages/zowe-explorer/i18n/sample/package.i18n.json +++ b/packages/zowe-explorer/i18n/sample/package.i18n.json @@ -147,7 +147,9 @@ "createZoweSchema.reload.infoMessage": "Team Configuration file created. Location: {0}. \n Please reload your window.", "copyFile": "Copy", "pasteFile": "Paste", - "jobs.sortbyreturncode": "Sort by ReturnCode", - "jobs.sortbyname": "Sort by Name", - "jobs.sortbyid": "Sort by ID" + "jobs.sortbyreturncode": "Sort by Return Code", + "jobs.sortbyid": "Sort by ID", + "sortByName": "Sort by Name", + "ds.sortByUserId": "Sort by User ID", + "ds.sortByModified": "Sort by Date Modified" } diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index 2c95e16902..8d5a227b12 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -128,7 +128,7 @@ }, { "command": "zowe.jobs.sortbyname", - "title": "%jobs.sortbyname%", + "title": "%sortByName%", "category": "Zowe Explorer" }, { @@ -276,6 +276,21 @@ "title": "%deleteProfile%", "category": "Zowe Explorer" }, + { + "command": "zowe.ds.sortByName", + "title": "%sortByName%", + "category": "Zowe Explorer" + }, + { + "command": "zowe.ds.sortByModified", + "title": "%ds.sortByModified%", + "category": "Zowe Explorer" + }, + { + "command": "zowe.ds.sortByUserId", + "title": "%ds.sortByUserId%", + "category": "Zowe Explorer" + }, { "command": "zowe.cmd.deleteProfile", "title": "%cmd.deleteProfile%", @@ -1108,6 +1123,21 @@ "command": "zowe.ds.removeFavProfile", "group": "002_zowe_dsWorkspace@4" }, + { + "when": "view == zowe.ds.explorer && viewItem =~ /^(pds).*/ && !listMultiSelection", + "command": "zowe.ds.sortByModified", + "group": "003_zowe_dsSort@1" + }, + { + "when": "view == zowe.ds.explorer && viewItem =~ /^(pds).*/ && !listMultiSelection", + "command": "zowe.ds.sortByName", + "group": "003_zowe_dsSort@2" + }, + { + "when": "view == zowe.ds.explorer && viewItem =~ /^(pds).*/ && !listMultiSelection", + "command": "zowe.ds.sortByUserId", + "group": "003_zowe_dsSort@3" + }, { "when": "view == zowe.ds.explorer && viewItem =~ /^fileError.*/", "command": "zowe.ds.showFileErrorDetails", diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index 8bc5d8bf6c..c157c7a0a2 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -147,7 +147,9 @@ "createZoweSchema.reload.infoMessage": "Team Configuration file created. Location: {0}. \n Please reload your window.", "copyFile": "Copy", "pasteFile": "Paste", - "jobs.sortbyreturncode": "Sort by ReturnCode", - "jobs.sortbyname": "Sort by Name", - "jobs.sortbyid": "Sort by ID" + "jobs.sortbyreturncode": "Sort by Return Code", + "jobs.sortbyid": "Sort by ID", + "sortByName": "Sort by Name", + "ds.sortByUserId": "Sort by User ID", + "ds.sortByModified": "Sort by Date Modified" } diff --git a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts index 0321cad081..afbdfef2e2 100644 --- a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts +++ b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts @@ -85,6 +85,16 @@ export class ZoweTreeProvider { } } + /** + * Fire the "onDidChangeTreeData" event to signal that a node in the tree has changed. + * Unlike `refreshElement`, this function does NOT signal a refresh for the given node - + * it simply tells VS Code to repaint the node in the tree. + * @param node The node that should be repainted + */ + public nodeDataChanged(node: IZoweTreeNode): void { + this.mOnDidChangeTreeData.fire(node); + } + /** * Called whenever the tree needs to be refreshed, and fires the data change event * diff --git a/packages/zowe-explorer/src/dataset/DatasetTree.ts b/packages/zowe-explorer/src/dataset/DatasetTree.ts index 41e428768d..542587aef2 100644 --- a/packages/zowe-explorer/src/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/dataset/DatasetTree.ts @@ -23,6 +23,7 @@ import { PersistenceSchemaEnum, NodeInteraction, IZoweTreeNode, + DatasetSort, } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; @@ -1287,4 +1288,20 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree 0) { + // If children nodes already exist, sort now and avoid extra refresh + node.children.sort(ZoweDatasetNode.sortBy(method)); + this.nodeDataChanged(node); + } else { + this.refreshElement(node); + } + } } diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 8d369eb00d..a72a95b934 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -13,7 +13,7 @@ import * as zowe from "@zowe/cli"; import * as vscode from "vscode"; import * as globals from "../globals"; import { errorHandling } from "../utils/ProfilesUtils"; -import { Gui, NodeAction, IZoweDatasetTreeNode, ZoweTreeNode } from "@zowe/zowe-explorer-api"; +import { DatasetSort, DatasetStats, Gui, NodeAction, IZoweDatasetTreeNode, ZoweTreeNode } from "@zowe/zowe-explorer-api"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; import { getIconByNode } from "../generators/icons"; import * as contextually from "../shared/context"; @@ -43,6 +43,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod public errorDetails: zowe.imperative.ImperativeError; public ongoingActions: Record> = {}; public wasDoubleClicked: boolean = false; + public sortMethod: DatasetSort = DatasetSort.Name; + public stats: DatasetStats; /** * Creates an instance of ZoweDatasetNode @@ -77,8 +79,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod if (icon) { this.iconPath = icon.path; } - if (!globals.ISTHEIA && this.getParent() && contextually.isSession(this.getParent())) { - this.id = `${mParent?.id ?? mParent?.label?.toString() ?? ""}.${this.label as string}`; + if (!globals.ISTHEIA && contextually.isSession(this)) { + this.id = this.label as string; } } @@ -235,6 +237,12 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod msg: localize("getChildren.invalidMember", "Cannot access member with control characters in the name: {0}", item.member), }); } + if (item.m4date) { + temp.stats = { + user: item.user, + m4date: new Date(`${item.m4date.replace(/\//g, "-")}T${item.mtime as string}:${item.msec as string}`), + }; + } elementChildren[temp.label.toString()] = temp; } } @@ -253,16 +261,32 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod ]; } else { const newChildren = Object.keys(elementChildren) - .sort() .filter((label) => this.children.find((c) => (c.label as string) === label) == null) .map((label) => elementChildren[label]); - this.children = this.children.concat(newChildren).filter((c) => (c.label as string) in elementChildren); + this.children = this.children + .concat(newChildren) + .filter((c) => (c.label as string) in elementChildren) + .sort(ZoweDatasetNode.sortBy(this.sortMethod)); } return this.children; } + public static sortBy(method: DatasetSort): (a: IZoweDatasetTreeNode, b: IZoweDatasetTreeNode) => number { + return (a, b): number => { + switch (method) { + case DatasetSort.LastModified: + return a.stats?.m4date < b.stats?.m4date ? -1 : 1; + case DatasetSort.UserId: + return a.stats?.user < b.stats?.user ? -1 : 1; + case DatasetSort.Name: + default: + return (a.label as string) < (b.label as string) ? -1 : 1; + } + }; + } + public getSessionNode(): IZoweDatasetTreeNode { ZoweLogger.trace("ZoweDatasetNode.getSessionNode called."); return this.getParent() ? this.getParent().getSessionNode() : this; diff --git a/packages/zowe-explorer/src/dataset/init.ts b/packages/zowe-explorer/src/dataset/init.ts index 12e81d74eb..9668ee1040 100644 --- a/packages/zowe-explorer/src/dataset/init.ts +++ b/packages/zowe-explorer/src/dataset/init.ts @@ -13,7 +13,7 @@ import * as globals from "../globals"; import * as vscode from "vscode"; import * as dsActions from "./actions"; import * as refreshActions from "../shared/refresh"; -import { IZoweDatasetTreeNode, IZoweTreeNode, IZoweTree } from "@zowe/zowe-explorer-api"; +import { IZoweDatasetTreeNode, IZoweTreeNode, IZoweTree, DatasetSort } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import { createDatasetTree } from "./DatasetTree"; import { ZoweDatasetNode } from "./ZoweDatasetNode"; @@ -25,7 +25,7 @@ import { TreeViewUtils } from "../utils/TreeViewUtils"; export async function initDatasetProvider(context: vscode.ExtensionContext): Promise> { ZoweLogger.trace("dataset.init.initDatasetProvider called."); - const datasetProvider: IZoweTree = await createDatasetTree(globals.LOG); + const datasetProvider = await createDatasetTree(globals.LOG); if (datasetProvider == null) { return null; } @@ -37,10 +37,10 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro ); context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.addSession", async () => datasetProvider.createZoweSession(datasetProvider))); context.subscriptions.push( - vscode.commands.registerCommand("zowe.ds.addFavorite", (node, nodeList) => { + vscode.commands.registerCommand("zowe.ds.addFavorite", async (node, nodeList) => { const selectedNodes = getSelectedNodeList(node, nodeList); for (const item of selectedNodes) { - datasetProvider.addFavorite(item); + await datasetProvider.addFavorite(item); } }) ); @@ -67,7 +67,7 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro } }) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.pattern", (node): void => datasetProvider.filterPrompt(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.pattern", (node) => datasetProvider.filterPrompt(node))); context.subscriptions.push( vscode.commands.registerCommand("zowe.ds.editSession", async (node) => datasetProvider.editSession(node, datasetProvider)) ); @@ -126,10 +126,10 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro } }) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.saveSearch", (node): void => datasetProvider.addFavorite(node))); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.removeSavedSearch", (node): void => datasetProvider.removeFavorite(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.saveSearch", (node) => datasetProvider.addFavorite(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.removeSavedSearch", (node) => datasetProvider.removeFavorite(node))); context.subscriptions.push( - vscode.commands.registerCommand("zowe.ds.removeFavProfile", (node): void => datasetProvider.removeFavProfile(node.label, true)) + vscode.commands.registerCommand("zowe.ds.removeFavProfile", (node) => datasetProvider.removeFavProfile(node.label, true)) ); context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.submitJcl", async () => dsActions.submitJcl(datasetProvider))); context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.submitMember", async (node) => dsActions.submitMember(node))); @@ -143,7 +143,7 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro } }) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.renameDataSet", (node): void => datasetProvider.rename(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.renameDataSet", (node) => datasetProvider.rename(node))); context.subscriptions.push( vscode.commands.registerCommand("zowe.ds.copyDataSets", async (node, nodeList) => dsActions.copyDataSets(node, nodeList, datasetProvider)) ); @@ -156,7 +156,7 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro await dsActions.refreshDataset(node.getParent(), datasetProvider); }) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.renameDataSetMember", (node): void => datasetProvider.rename(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.renameDataSetMember", (node) => datasetProvider.rename(node))); context.subscriptions.push( vscode.commands.registerCommand("zowe.ds.hMigrateDataSet", async (node, nodeList) => { let selectedNodes = getSelectedNodeList(node, nodeList); @@ -196,11 +196,24 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro datasetProvider.refreshElement(node); }) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.ssoLogin", (node: IZoweTreeNode): void => datasetProvider.ssoLogin(node))); - context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.ssoLogout", (node: IZoweTreeNode): void => datasetProvider.ssoLogout(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.ssoLogin", (node: IZoweTreeNode) => datasetProvider.ssoLogin(node))); + context.subscriptions.push(vscode.commands.registerCommand("zowe.ds.ssoLogout", (node: IZoweTreeNode) => datasetProvider.ssoLogout(node))); context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration((e) => { - datasetProvider.onDidChangeConfiguration(e); + vscode.commands.registerCommand("zowe.ds.sortByName", (node: IZoweDatasetTreeNode): void => datasetProvider.sortBy(DatasetSort.Name, node)) + ); + context.subscriptions.push( + vscode.commands.registerCommand("zowe.ds.sortByModified", (node: IZoweDatasetTreeNode): void => + datasetProvider.sortBy(DatasetSort.LastModified, node) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand("zowe.ds.sortByUserId", (node: IZoweDatasetTreeNode): void => + datasetProvider.sortBy(DatasetSort.UserId, node) + ) + ); + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(async (e) => { + await datasetProvider.onDidChangeConfiguration(e); }) );