From 548a8df61c4a8befcb066324ba175fa6b6284e2c Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:07:57 -0400 Subject: [PATCH 1/7] move to new PR against main Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .../__unit__/utils/ProfilesUtils.unit.test.ts | 18 +++++++++++++ .../zowe-explorer/src/utils/ProfilesUtils.ts | 26 ++++++++++++------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index a0a75e8549..6bd720ab07 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -485,6 +485,7 @@ describe("ProfilesUtils unit tests", () => { it("should skip creating directories and files that already exist", async () => { const blockMocks = createBlockMocks(); + jest.spyOn(profUtils.ProfilesUtils, "getCredentialManagerOverride").mockReturnValue("@zowe/cli"); blockMocks.mockGetDirectValue.mockReturnValue("@zowe/cli"); blockMocks.mockExistsSync.mockReturnValue(true); const fileJson = blockMocks.mockFileRead; @@ -777,6 +778,23 @@ describe("ProfilesUtils unit tests", () => { expect(profUtils.ProfilesUtils.getCredentialManagerOverride()).toBe("My Custom Credential Manager"); expect(zoweLoggerTraceSpy).toBeCalledTimes(1); }); + + it("should return default manager if the override file does not exist", () => { + const zoweLoggerTraceSpy = jest.spyOn(ZoweLogger, "trace"); + const zoweLoggerInfoSpy = jest.spyOn(ZoweLogger, "info"); + + jest.spyOn(fs, "readFileSync").mockImplementation(() => { + throw new Error("test"); + }); + try { + profUtils.ProfilesUtils.getCredentialManagerOverride(); + } catch (err) { + expect(err).toBe("test"); + } + + expect(zoweLoggerTraceSpy).toBeCalledTimes(1); + expect(zoweLoggerInfoSpy).toBeCalledTimes(1); + }); }); describe("setupCustomCredentialManager", () => { diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 9d5418d4e9..d5557c7e14 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -233,13 +233,19 @@ export class ProfilesUtils { */ public static getCredentialManagerOverride(): string { ZoweLogger.trace("ProfilesUtils.getCredentialManagerOverride called."); - const settingsFile = path.join(getZoweDir(), "settings", "imperative.json"); - const imperativeConfig = JSON.parse(fs.readFileSync(settingsFile).toString()); - const credentialManagerOverride = imperativeConfig?.overrides[imperative.CredentialManagerOverride.CRED_MGR_SETTING_NAME]; - if (typeof credentialManagerOverride === "string") { - return credentialManagerOverride; + try { + const settingsFilePath = path.join(getZoweDir(), "settings", "imperative.json"); + const settingsFile = fs.readFileSync(settingsFilePath); + const imperativeConfig = JSON.parse(settingsFile.toString()); + const credentialManagerOverride = imperativeConfig?.overrides[imperative.CredentialManagerOverride.CRED_MGR_SETTING_NAME]; + if (typeof credentialManagerOverride === "string") { + return credentialManagerOverride; + } + return imperative.CredentialManagerOverride.DEFAULT_CRED_MGR_NAME; + } catch (err) { + ZoweLogger.info("imperative.json does not exist, returning the default override of @zowe/cli"); + return imperative.CredentialManagerOverride.DEFAULT_CRED_MGR_NAME; } - return imperative.CredentialManagerOverride.DEFAULT_CRED_MGR_NAME; } /** @@ -542,6 +548,10 @@ export class ProfilesUtils { public static async initializeZoweFolder(): Promise { ZoweLogger.trace("ProfilesUtils.initializeZoweFolder called."); + // ensure the Secure Credentials Enabled value is read + // set globals.PROFILE_SECURITY value accordingly + const credentialManagerMap = ProfilesUtils.getCredentialManagerOverride(); + await globals.setGlobalSecurityValue(credentialManagerMap ?? globals.ZOWE_CLI_SCM); // Ensure that ~/.zowe folder exists // Ensure that the ~/.zowe/settings/imperative.json exists // TODO: update code below once this imperative issue is resolved. @@ -555,9 +565,6 @@ export class ProfilesUtils { fs.mkdirSync(settingsPath); } ProfilesUtils.writeOverridesFile(); - // set global variable of security value to existing override - // this will later get reverted to default in getProfilesInfo.ts if user chooses to - await ProfilesUtils.updateCredentialManagerSetting(ProfilesUtils.getCredentialManagerOverride()); // If not using team config, ensure that the ~/.zowe/profiles directory // exists with appropriate types within if (!imperative.ImperativeConfig.instance.config?.exists) { @@ -609,6 +616,7 @@ export class ProfilesUtils { } else { settings = { ...defaultImperativeJson }; } + settings.overrides.CredentialManager = globals.PROFILE_SECURITY; const newData = JSON.stringify(settings, null, 2); ZoweLogger.debug( localize("writeOverridesFile.updateFile", "Updating imperative.json Credential Manager to {0}.\n{1}", globals.PROFILE_SECURITY, newData) From ab7284cc8be063e87028d10acd3e19240c0f22a8 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 09:33:37 -0400 Subject: [PATCH 2/7] fix(ds/sort): Fallback to sorting by name when values are equal Signed-off-by: Trae Yelovich --- .../__unit__/dataset/DatasetTree.unit.test.ts | 30 +++++++++++++- .../src/dataset/ZoweDatasetNode.ts | 40 +++++++++++++------ 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts index 698fb29f2d..55d8ebf0c4 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts @@ -15,7 +15,15 @@ import * as fs from "fs"; import * as zowe from "@zowe/cli"; import { DatasetTree } from "../../../src/dataset/DatasetTree"; import { ZoweDatasetNode } from "../../../src/dataset/ZoweDatasetNode"; -import { DatasetFilterOpts, Gui, IZoweDatasetTreeNode, ProfilesCache, ValidProfileEnum } from "@zowe/zowe-explorer-api"; +import { + DatasetFilterOpts, + DatasetSortOpts, + Gui, + IZoweDatasetTreeNode, + ProfilesCache, + SortDirection, + ValidProfileEnum, +} from "@zowe/zowe-explorer-api"; import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister"; import { Profiles } from "../../../src/Profiles"; import * as utils from "../../../src/utils/ProfilesUtils"; @@ -2716,6 +2724,10 @@ describe("Dataset Tree Unit Tests - Sorting and Filtering operations", () => { 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]; + pds.sort = { + method: DatasetSortOpts.Name, + direction: SortDirection.Ascending, + }; session.children = [pds]; return { @@ -2785,6 +2797,22 @@ describe("Dataset Tree Unit Tests - Sorting and Filtering operations", () => { expect(nodes.pds.children?.map((c: IZoweDatasetTreeNode) => c.label)).toStrictEqual(["B", "C", "A"]); }); + it("sorts by last modified date: handling 2 nodes with same date", async () => { + const mocks = getBlockMocks(); + const nodes = nodesForSuite(); + mocks.showQuickPick.mockResolvedValueOnce({ label: "$(fold) Sort Direction" }); + mocks.showQuickPick.mockResolvedValueOnce({ label: "Descending" }); + mocks.showQuickPick.mockResolvedValueOnce({ label: "$(calendar) Date Modified" }); + // insert node with same date modified + const nodeD = new ZoweDatasetNode("D", vscode.TreeItemCollapsibleState.Collapsed, nodes.pds, createISession()); + nodeD.stats = { user: "someUser", modifiedDate: new Date("2022-03-15T16:30:00") }; + nodes.pds.children = [...(nodes.pds.children ?? []), nodeD]; + 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", "D", "C", "B"]); + }); + it("sorts by user ID", async () => { const mocks = getBlockMocks(); const nodes = nodesForSuite(); diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index f46de2ba3e..845039b713 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -332,22 +332,38 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod const sortLessThan = sort.direction == SortDirection.Ascending ? -1 : 1; const sortGreaterThan = sortLessThan * -1; + const sortByName = (nodeA: IZoweDatasetTreeNode, nodeB: IZoweDatasetTreeNode): number => + (nodeA.label as string) < (nodeB.label as string) ? sortLessThan : sortGreaterThan; + if (!a.stats && !b.stats) { - return (a.label as string) < (b.label as string) ? sortLessThan : sortGreaterThan; + return sortByName(a, b); } - switch (sort.method) { - case DatasetSortOpts.LastModified: - a.description = dayjs(a.stats?.modifiedDate).format("YYYY/MM/DD HH:mm:ss"); - b.description = dayjs(b.stats?.modifiedDate).format("YYYY/MM/DD HH:mm:ss"); - return a.stats?.modifiedDate < b.stats?.modifiedDate ? sortLessThan : sortGreaterThan; - case DatasetSortOpts.UserId: - a.description = a.stats?.user; - b.description = b.stats?.user; - return a.stats?.user < b.stats?.user ? sortLessThan : sortGreaterThan; - case DatasetSortOpts.Name: - return (a.label as string) < (b.label as string) ? sortLessThan : sortGreaterThan; + if (sort.method === DatasetSortOpts.LastModified) { + const dateA = dayjs(a.stats?.modifiedDate); + const dateB = dayjs(b.stats?.modifiedDate); + + a.description = dateA.format("YYYY/MM/DD HH:mm:ss"); + b.description = dateB.format("YYYY/MM/DD HH:mm:ss"); + + // for dates that are equal down to the second, fallback to sorting by name + if (dateA.isSame(dateB, "second")) { + return sortByName(a, b); + } + + return dateA.isBefore(dateB, "second") ? sortLessThan : sortGreaterThan; + } else if (sort.method === DatasetSortOpts.UserId) { + a.description = a.stats?.user; + b.description = b.stats?.user; + + if (a.stats?.user === b.stats?.user) { + return sortByName(a, b); + } + + return a.stats?.user < b.stats?.user ? sortLessThan : sortGreaterThan; } + + return sortByName(a, b); }; } From db09e31878238f283bafd3a75cdf00910694c67c Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 10:13:47 -0400 Subject: [PATCH 3/7] fix(ds/sort): Remove node description if date from API is invalid (unlikely, but possible) Signed-off-by: Trae Yelovich --- packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 845039b713..bb758876a6 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -343,8 +343,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod const dateA = dayjs(a.stats?.modifiedDate); const dateB = dayjs(b.stats?.modifiedDate); - a.description = dateA.format("YYYY/MM/DD HH:mm:ss"); - b.description = dateB.format("YYYY/MM/DD HH:mm:ss"); + a.description = dateA.isValid() ? dateA.format("YYYY/MM/DD HH:mm:ss") : undefined; + b.description = dateB.isValid() ? dateB.format("YYYY/MM/DD HH:mm:ss") : undefined; // for dates that are equal down to the second, fallback to sorting by name if (dateA.isSame(dateB, "second")) { From bc94caf0705c313460d3bb6dc638cb3e1402435b Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 10:31:34 -0400 Subject: [PATCH 4/7] fix(ds/sort): update stats on existing nodes Signed-off-by: Trae Yelovich --- .../src/dataset/ZoweDatasetNode.ts | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index bb758876a6..cb9e4d6085 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -155,6 +155,22 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod return; } + const updateStats = (node: IZoweDatasetTreeNode, item: any): void => { + if ("m4date" in item) { + const { m4date, mtime, msec }: { m4date: string; mtime: string; msec: string } = item; + node.stats = { + user: item.user, + modifiedDate: dayjs(`${m4date} ${mtime}:${msec}`).toDate(), + }; + } else if ("id" in item || "changed" in item) { + // missing keys from API response; check for FTP keys + node.stats = { + user: item.id, + modifiedDate: item.changed ? dayjs(item.changed).toDate() : undefined, + }; + } + }; + // push nodes to an object with property names to avoid duplicates const elementChildren: { [k: string]: ZoweDatasetNode } = {}; for (const response of responses) { @@ -169,6 +185,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod for (const item of response.apiResponse.items ?? response.apiResponse) { const existing = this.children.find((element) => element.label.toString() === item.dsname); if (existing) { + updateStats(existing, item); elementChildren[existing.label.toString()] = existing; // Creates a ZoweDatasetNode for a PDS } else if (item.dsorg === "PO" || item.dsorg === "PO-E") { @@ -262,19 +279,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod } // get user and last modified date for sorting, if available - if ("m4date" in item) { - const { m4date, mtime, msec }: { m4date: string; mtime: string; msec: string } = item; - temp.stats = { - user: item.user, - modifiedDate: dayjs(`${m4date} ${mtime}:${msec}`).toDate(), - }; - } else if ("id" in item || "changed" in item) { - // missing keys from API response; check for FTP keys - temp.stats = { - user: item.id, - modifiedDate: item.changed ? dayjs(item.changed).toDate() : undefined, - }; - } + updateStats(temp, item); elementChildren[temp.label.toString()] = temp; } } From 9235f28b8bff80ad1d6816c881c1f7d2385888cc Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 11:16:28 -0400 Subject: [PATCH 5/7] fix(ds/sort): Handle single nodes with empty user ID Signed-off-by: Trae Yelovich --- packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index cb9e4d6085..8497910964 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -358,14 +358,17 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod return dateA.isBefore(dateB, "second") ? sortLessThan : sortGreaterThan; } else if (sort.method === DatasetSortOpts.UserId) { - a.description = a.stats?.user; - b.description = b.stats?.user; + const userA = a.stats?.user ?? ""; + const userB = b.stats?.user ?? ""; - if (a.stats?.user === b.stats?.user) { + a.description = userA; + b.description = userB; + + if (userA === userB) { return sortByName(a, b); } - return a.stats?.user < b.stats?.user ? sortLessThan : sortGreaterThan; + return userA < userB ? sortLessThan : sortGreaterThan; } return sortByName(a, b); From 19f031cc45605eb41d25e29b06a00a240347831a Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 12:16:17 -0400 Subject: [PATCH 6/7] test(sort): Add patch coverage for updateStats fn Signed-off-by: Trae Yelovich --- .../__unit__/dataset/DatasetTree.unit.test.ts | 37 +++++++++++++++++++ .../src/dataset/ZoweDatasetNode.ts | 36 +++++++++--------- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts index 55d8ebf0c4..d4f853238b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts @@ -1769,6 +1769,43 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { expect(await testTree.datasetFilterPrompt(testTree.mSessionNodes[1])).not.toBeDefined(); }); + + it("updates stats with modified date and user ID if provided in API", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = await createBlockMocks(globalMocks); + + const testTree = new DatasetTree(); + testTree.mSessionNodes.push(blockMocks.datasetSessionNode); + const newNode = new ZoweDatasetNode("TEST.PDS", vscode.TreeItemCollapsibleState.Collapsed, testTree.mSessionNodes[1], blockMocks.session); + testTree.mSessionNodes[1].children = [newNode]; + const updateStatsSpy = jest.spyOn(ZoweDatasetNode.prototype, "updateStats"); + const getDatasetsSpy = jest.spyOn((ZoweDatasetNode as any).prototype, "getDatasets"); + getDatasetsSpy.mockResolvedValueOnce([ + { + success: true, + commandResponse: null, + apiResponse: { + items: [ + { + m4date: "2023-10-31", + mtime: "12:00", + msec: "30", + member: "HI", + user: "SOMEUSR", + }, + { + changed: "2023-10-31 03:00:00", + member: "BYE", + id: "SOMEUSR", + }, + ], + }, + }, + ]); + await testTree.mSessionNodes[1].children[0].getChildren(); + + expect(updateStatsSpy).toHaveBeenCalled(); + }); }); describe("Dataset Tree Unit Tests - Function editSession", () => { async function createBlockMocks() { diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 8497910964..808b90bf9e 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -118,6 +118,22 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod return this.getProfile() ? this.getProfile().name : undefined; } + public updateStats(item: any): void { + if ("m4date" in item) { + const { m4date, mtime, msec }: { m4date: string; mtime: string; msec: string } = item; + this.stats = { + user: item.user, + modifiedDate: dayjs(`${m4date} ${mtime}:${msec}`).toDate(), + }; + } else if ("id" in item || "changed" in item) { + // missing keys from API response; check for FTP keys + this.stats = { + user: item.id, + modifiedDate: item.changed ? dayjs(item.changed).toDate() : undefined, + }; + } + } + /** * Retrieves child nodes of this ZoweDatasetNode * @@ -155,22 +171,6 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod return; } - const updateStats = (node: IZoweDatasetTreeNode, item: any): void => { - if ("m4date" in item) { - const { m4date, mtime, msec }: { m4date: string; mtime: string; msec: string } = item; - node.stats = { - user: item.user, - modifiedDate: dayjs(`${m4date} ${mtime}:${msec}`).toDate(), - }; - } else if ("id" in item || "changed" in item) { - // missing keys from API response; check for FTP keys - node.stats = { - user: item.id, - modifiedDate: item.changed ? dayjs(item.changed).toDate() : undefined, - }; - } - }; - // push nodes to an object with property names to avoid duplicates const elementChildren: { [k: string]: ZoweDatasetNode } = {}; for (const response of responses) { @@ -185,7 +185,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod for (const item of response.apiResponse.items ?? response.apiResponse) { const existing = this.children.find((element) => element.label.toString() === item.dsname); if (existing) { - updateStats(existing, item); + existing.updateStats(item); elementChildren[existing.label.toString()] = existing; // Creates a ZoweDatasetNode for a PDS } else if (item.dsorg === "PO" || item.dsorg === "PO-E") { @@ -279,7 +279,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod } // get user and last modified date for sorting, if available - updateStats(temp, item); + temp.updateStats(item); elementChildren[temp.label.toString()] = temp; } } From 2da3995bbe5bbccc6cb7846a45cdab407e680f02 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 14:32:20 -0400 Subject: [PATCH 7/7] fix(ds): Fix 'existing node' check in getChildren for PDS members Signed-off-by: Trae Yelovich --- packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 808b90bf9e..30242cb10c 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -183,7 +183,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod // Loops through all the returned dataset members and creates nodes for them for (const item of response.apiResponse.items ?? response.apiResponse) { - const existing = this.children.find((element) => element.label.toString() === item.dsname); + const dsEntry = item.dsname ?? item.member; + const existing = this.children.find((element) => element.label.toString() === dsEntry); if (existing) { existing.updateStats(item); elementChildren[existing.label.toString()] = existing;