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

Identical member names - PDS enhancement #2427

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to the Zowe CLI package will be documented in this file.

## Recent Changes

- Enhancement: When copying PDSs that have identical member names, the user is now prompted to confirm before the operation occurs in case of data loss. [#2349] (https://github.com/zowe/zowe-cli/issues/2349)
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
- Enhancement: Added `--recordRange` flag to `zowe jobs download output` command to allow users to select a specific range of records to output from a spool file. [#2411](https://github.com/zowe/zowe-cli/pull/2411)
- BugFix: The `zowe zos-files copy data-set` command overwrites the contents of the target data set without user confirmation. A `--safe-replace` option was added which prompts the user to confirm before overwriting the contents of the target data set. [#2369] (https://github.com/zowe/zowe-cli/issues/2369)

Expand Down Expand Up @@ -931,7 +932,7 @@ LTS Breaking: Removed the following previously deprecated items: [#1981](https:/

## `6.25.0`

- Enhancement: Added a `--replace` option to the `zowe zos-files copy data-set` command. Use this option if you want to replace like-named members in the target data set. [#808](https://github.com/zowe/zowe-cli/issues/808)
- Enhancement: Added a `--replace` option to the `zowe zos-files copy data-set` command. Use this option if you want to replace members with identical names in the target data set. [#808](https://github.com/zowe/zowe-cli/issues/808)
- Enhancement: Improved a cryptic error message that was shown if TSO address space failed to start for the `zowe zos-tso issue command` command. [#28](https://github.com/zowe/zowe-cli/issues/28)
- Bugfix: Removed "[object Object]" text that appeared in some error messages. The proper text "Imperative API Error" is now displayed. [#836](https://github.com/zowe/zowe-cli/pull/836)

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("DsHandler", () => {

},
response: {
console: { promptFn: jest.fn() }
console: { promptFn: jest.fn(), promptForLikeNamedMembers: jest.fn() }
}
};

Expand All @@ -68,7 +68,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -98,7 +99,7 @@ describe("DsHandler", () => {
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
console: { promptFn: jest.fn(), promptForLikeNamedMembers: jest.fn() }
}
};

Expand All @@ -116,7 +117,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -162,7 +164,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -198,7 +201,7 @@ describe("DsHandler", () => {
const result = await promptFn(commandParameters.arguments.toDataSetName);

expect(promptMock).toHaveBeenCalledWith(
`The dataset '${toDataSetName}' exists on the target system. This copy will result in data loss.` +
`The dataset '${toDataSetName}' exists on the target system. This copy may result in data loss.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(true);
Expand Down Expand Up @@ -234,7 +237,79 @@ describe("DsHandler", () => {
const result = await promptFn(commandParameters.arguments.toDataSetName);

expect(promptMock).toHaveBeenCalledWith(
`The dataset '${toDataSetName}' exists on the target system. This copy will result in data loss.` +
`The dataset '${toDataSetName}' exists on the target system. This copy may result in data loss.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(false);
});
it("should prompt the user about duplicate member names and return true when input is 'y", async () => {
const handler = new DsHandler();

expect(handler).toBeInstanceOf(ZosFilesBaseHandler);
const fromDataSetName = "ABCD";
const toDataSetName = "EFGH";
const enq = "SHR";
const replace = false;
const safeReplace = false;
const responseTimeout: any = undefined;

const commandParameters: any = {
Dismissed Show dismissed Hide dismissed
arguments: {
fromDataSetName,
toDataSetName,
enq,
replace,
safeReplace,
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
}
};
const promptMock = jest.fn();
promptMock.mockResolvedValue("y");

const promptForDuplicates = (handler as any)["promptForLikeNamedMembers"]({ prompt: promptMock });
const result = await promptForDuplicates();

expect(promptMock).toHaveBeenCalledWith(
`The source and target data sets have identical member names. The contents of those members will be overwritten.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(true);
});
it("should prompt the user about duplicate member names and return false when input is 'N'", async () => {
const handler = new DsHandler();

expect(handler).toBeInstanceOf(ZosFilesBaseHandler);
const fromDataSetName = "ABCD";
const toDataSetName = "EFGH";
const enq = "SHR";
const replace = false;
const safeReplace = false;
const responseTimeout: any = undefined;

const commandParameters: any = {
Dismissed Show dismissed Hide dismissed
arguments: {
fromDataSetName,
toDataSetName,
enq,
replace,
safeReplace,
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
}
};
const promptMock = jest.fn();
promptMock.mockResolvedValue("N");

const promptForDuplicates = (handler as any)["promptForLikeNamedMembers"]({ prompt: promptMock });
const result = await promptForDuplicates();

expect(promptMock).toHaveBeenCalledWith(
`The source and target data sets have identical member names. The contents of those members will be overwritten.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Array [
"aliases": Array [
"rep",
],
"description": "Specify this option as true if you wish to replace like-named members in the target data set",
"description": "Specify this option as true if you wish to replace members with identical names in the target data set",
"name": "replace",
"type": "boolean",
},
Expand All @@ -15,7 +15,7 @@ Array [
"safe-rep",
"sr",
],
"description": "Specify this option as true if you wish to replace like-named members or the content of the target data set. This option will prompt to confirm.",
"description": "Specify this option as true if you wish to replace members with identical names or the content of the target data set. This option will prompt to confirm.",
"name": "safe-replace",
"type": "boolean",
},
Expand All @@ -41,7 +41,7 @@ Array [
"options": "\\"USER.FROM.SET(mem1)\\" \\"USER.TO.SET\\"",
},
Object {
"description": "Copy the data set named 'USER.FROM.SET' to the data set named 'USER.TO.SET' and replace like-named members",
"description": "Copy the data set named 'USER.FROM.SET' to the data set named 'USER.TO.SET' and replace members with identical names",
"options": "\\"USER.FROM.SET\\" \\"USER.TO.SET\\" --replace",
},
Object {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/zosfiles/-strings-/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,16 @@ export default {
TODSNAME: "The name of the data set that you want to copy to"
},
OPTIONS: {
REPLACE: "Specify this option as true if you wish to replace like-named members in the target data set",
SAFE_REPLACE: "Specify this option as true if you wish to replace like-named members or the content of the target data set. " +
REPLACE: "Specify this option as true if you wish to replace members with identical names in the target data set",
SAFE_REPLACE: "Specify this option as true if you wish to replace members with identical names or the content of the target data set. " +
"This option will prompt to confirm."
},
EXAMPLES: {
EX1: "Copy the data set named 'USER.FROM.SET' to the data set named 'USER.TO.SET'",
EX2: "Copy the data set member named 'USER.FROM.SET(MEM1)' to the data set member named 'USER.TO.SET(MEM2)'",
EX3: "Copy the data set named 'USER.FROM.SET' to the data set member named 'USER.TO.SET(MEM2)'",
EX4: "Copy the data set member named 'USER.FROM.SET(MEM1)' to the data set named 'USER.TO.SET'",
EX5: "Copy the data set named 'USER.FROM.SET' to the data set named 'USER.TO.SET' and replace like-named members",
EX5: "Copy the data set named 'USER.FROM.SET' to the data set named 'USER.TO.SET' and replace members with identical names",
EX6: "Copy the partitioned data set named 'TEST.PDS1' to the partitioned data set named 'TEST.PDS2'",
EX7: "Copy the partitioned data set named 'EXISTING.PDS' to a non-existent target 'NEW.PDS'"
}
Expand Down
15 changes: 13 additions & 2 deletions packages/cli/src/zosfiles/copy/ds/Ds.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export default class DsHandler extends ZosFilesBaseHandler {
replace: commandParameters.arguments.replace,
responseTimeout: commandParameters.arguments.responseTimeout,
safeReplace: commandParameters.arguments.safeReplace,
promptFn: this.promptForSafeReplace(commandParameters.response.console)
promptFn: this.promptForSafeReplace(commandParameters.response.console),
promptForLikeNamedMembers: this.promptForLikeNamedMembers(commandParameters.response.console)
};

return Copy.dataSet(session, toDataSet, options);
Expand All @@ -35,10 +36,20 @@ export default class DsHandler extends ZosFilesBaseHandler {
private promptForSafeReplace(console: IHandlerResponseConsoleApi) {
return async (targetDSN: string) => {
const answer: string = await console.prompt(
`The dataset '${targetDSN}' exists on the target system. This copy will result in data loss.` +
`The dataset '${targetDSN}' exists on the target system. This copy may result in data loss.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
);
return answer != null && (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
};
}

private promptForLikeNamedMembers(console: IHandlerResponseConsoleApi) {
return async() => {
const answer: string = await console.prompt (
`The source and target data sets have identical member names. The contents of those members will be overwritten.` +
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
` Are you sure you want to continue? [y/N]: `
)
return answer != null && (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
};
}
}
1 change: 1 addition & 0 deletions packages/zosfiles/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi

## Recent Changes

- Enhancement: When copying PDSs that have identical member names, the user is now prompted to confirm before the operation occurs in case of data loss. [#2349] (https://github.com/zowe/zowe-cli/issues/2349)
pujal0909 marked this conversation as resolved.
Show resolved Hide resolved
- BugFix: Fixed an issue in the `Copy.dataSetCrossLPAR()` function where the `spacu` attribute of the copied data set was always set to `TRK`, regardless of the source data set's attributes. [#2412](https://github.com/zowe/zowe-cli/issues/2412)
- BugFix: The `Copy.data.set` function now prompts the user to confirm before overwriting the contents of the target data set with the addition of the `--safe-replace` option. [#2369] (https://github.com/zowe/zowe-cli/issues/2369)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,40 @@ describe("Copy", () => {
});
});

describe("hasIdenticalMemberNames", () => {
beforeEach(async () => {
try {
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, fromDataSetName);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, toDataSetName);
await Upload.fileToDataset(REAL_SESSION, fileLocation, fromDataSetName);
await Upload.fileToDataset(REAL_SESSION, fileLocation, toDataSetName);
}
catch (err) {
Imperative.console.info(`Error: ${inspect(err)}`);
}
});
afterEach(async () => {
try {
await Delete.dataSet(REAL_SESSION, fromDataSetName);
await Delete.dataSet(REAL_SESSION, toDataSetName);
} catch (err) {
Imperative.console.info(`Error: ${inspect(err)}`);
}
});
it("should return true if the source and target data sets have identical member names", async () => {
const response = await Copy["hasIdenticalMemberNames"](REAL_SESSION, fromDataSetName, toDataSetName);
expect(response).toBe(true);
});

it("should return false if the source and target data sets do not have identical member names", async () => {
await Delete.dataSet(REAL_SESSION, toDataSetName);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, toDataSetName);

const response = await Copy["hasIdenticalMemberNames"](REAL_SESSION, fromDataSetName, toDataSetName);
expect(response).toBe(false);
});
});

describe("Data Set Cross LPAR", () => {
describe("Common Failures", () => {
it("should fail if no fromDataSet data set name is supplied", async () => {
Expand Down
Loading