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

Display messages before prompting for properties #2123

Merged
merged 10 commits into from
Apr 30, 2024
4 changes: 4 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the Zowe CLI package will be documented in this file.

## Recent Changes

- LTS Breaking: Add informative messages identifying why a user is being prompted for connection property values during a CLI command.

## `8.0.0-next.202404032038`

- BugFix: Fixed error in `zos-files list all-members` command that could occur when members contain control characters in the name. [#2104](https://github.com/zowe/zowe-cli/pull/2104)
Expand Down
4 changes: 4 additions & 0 deletions packages/imperative/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the Imperative package will be documented in this file.

## Recent Changes

- Enhancement: Add informative messages before prompting for connection property values in the CLI callback function getValuesBack.

## `8.0.0-next.202404191414`

- Enhancement: Added a new class named ConvertV1Profiles to enable other apps to better convert V1 profiles into a current Zowe config file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

import { IHandlerParameters } from "../../../../../cmd";
import { ImperativeConfig, ProcessUtils } from "../../../../../utilities";
import { ImperativeConfig, ProcessUtils, TextUtils } from "../../../../../utilities";
import { FakeAutoInitHandler } from "./__data__/FakeAutoInitHandler";
import * as lodash from "lodash";
import * as jestDiff from "jest-diff";
Expand Down Expand Up @@ -57,6 +57,20 @@ describe("BaseAutoInitHandler", () => {
}
};
stripAnsiSpy = (stripAnsi as any).mockImplementation(() => jest.requireActual("strip-ansi"));

// Pretend that wordWrap and chalk work. We do not care about their output in these tests.
jest.spyOn(TextUtils, "wordWrap").mockReturnValue("Fake wrapped text");

Object.defineProperty(TextUtils, "chalk", {
configurable: true,
get: jest.fn(() => {
return {
yellowBright: jest.fn(() => {
return "Fake yellow in some text";
})
};
})
});
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { join } from "path";
import { ConfigAutoStore } from "../../../config/src/ConfigAutoStore";
import { setupConfigToLoad } from "../../../../__tests__/src/TestUtil";
import { IOverridePromptConnProps } from "../../src/session/doc/IOverridePromptConnProps";
import { IOptionsForAddConnProps } from "../../src/session/doc/IOptionsForAddConnProps";
import { ImperativeConfig } from "../../../utilities";
import { ConfigUtils } from "../../../config/src/ConfigUtils";


const certFilePath = join(__dirname, "..", "..", "..", "..", "__tests__", "__integration__", "cmd",
"__tests__", "integration", "cli", "auth", "__resources__", "fakeCert.cert");
Expand Down Expand Up @@ -1417,4 +1421,117 @@ describe("ConnectionPropsForSessCfg tests", () => {
expect(sessCfgWithConnProps.cert).toBeUndefined();
expect(sessCfgWithConnProps.certKey).toBeUndefined();
});

describe("getValuesBack private function", () => {
// pretend that console.log works, but put data into a variable
let consoleMsgs = "";
const connOpts:IOptionsForAddConnProps = {
parms: {
response: {
console: {
log: jest.fn((logArgs) => {
consoleMsgs += "\n" + logArgs;
})
}
}
}
} as any;

let getValuesCallBack: any;
let clientPromptSpy: any;

beforeEach(() => {
// establish a callback function with our fake console.log
getValuesCallBack = ConnectionPropsForSessCfg["getValuesBack"](connOpts);

// pretend that clientPrompt returns an answer
clientPromptSpy = jest.spyOn(ConnectionPropsForSessCfg as any, "clientPrompt").mockResolvedValue(
Promise.resolve("Some fake answer")
);
// clear log messages from last test
consoleMsgs = "";
});

afterEach(() => {
// restore original app implementations
clientPromptSpy.mockRestore();
});

it("should state that you have no zowe config file", async () => {
// Pretend that we do not have a zowe config.
Object.defineProperty(ImperativeConfig.instance, "config", {
configurable: true,
get: jest.fn(() => {
return {
exists: false
};
})
});

// call the function that we want to test
await getValuesCallBack(["hostname"]);

expect(consoleMsgs).toContain("No Zowe client configuration exists.");
expect(consoleMsgs).toContain("Therefore, you will be asked for the");
expect(consoleMsgs).toContain("connection properties that are required to complete your command.");
});

it("should state that V1 profiles are not supported", async () => {
// Pretend that we do not have a zowe config.
Object.defineProperty(ImperativeConfig.instance, "config", {
configurable: true,
get: jest.fn(() => {
return {
exists: false
};
})
});

/* Pretend that we only have V1 profiles.
* onlyV1ProfilesExist is a getter property, so mock the property.
*/
Object.defineProperty(ConfigUtils, "onlyV1ProfilesExist", {
configurable: true,
get: jest.fn(() => {
return true;
})
});

// call the function that we want to test
await getValuesCallBack(["hostname"]);

expect(consoleMsgs).toContain("Only V1 profiles exist. V1 profiles are no longer supported. You should convert");
expect(consoleMsgs).toContain("your V1 profiles to a newer Zowe client configuration. Therefore, you will be");
expect(consoleMsgs).toContain("asked for the connection properties that are required to complete your command.");
});

it("should state that connection properties are missing from config", async () => {
// Pretend that we have a zowe config.
Object.defineProperty(ImperativeConfig.instance, "config", {
configurable: true,
get: jest.fn(() => {
return {
exists: true
};
})
});

/* Pretend that we do not have any V1 profiles.
* onlyV1ProfilesExist is a getter property, so mock the property.
*/
Object.defineProperty(ConfigUtils, "onlyV1ProfilesExist", {
configurable: true,
get: jest.fn(() => {
return false;
})
});

// call the function that we want to test
await getValuesCallBack(["hostname"]);

expect(consoleMsgs).toContain("Some required connection properties have not been specified in your Zowe client");
expect(consoleMsgs).toContain("configuration. Therefore, you will be asked for the connection properties that");
expect(consoleMsgs).toContain("are required to complete your command.");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
*/

import { CliUtils, ImperativeConfig } from "../../../utilities";
import { CliUtils, ImperativeConfig, TextUtils } from "../../../utilities";
import { ICommandArguments, IHandlerParameters } from "../../../cmd";
import { ImperativeError } from "../../../error";
import { IOptionsForAddConnProps } from "./doc/IOptionsForAddConnProps";
Expand Down Expand Up @@ -360,30 +360,29 @@ export class ConnectionPropsForSessCfg {
*/
private static getValuesBack(connOpts: IOptionsForAddConnProps): (properties: string[]) => Promise<{ [key: string]: any }> {
return async (promptForValues: string[]) => {
/* ToDo: Uncomment this code block to display an informative message before prompting
* a user for connection values. Because 219 unit test fails and 144 system tests
* fail due to a minor difference in output, we chose not to implement this
* minor enhancement until we have time to devote to correcting so many tests.
*
* The following 'if' statement is only needed for tests which do not create a mock for
* the connOpts.parms.response.console.log property. In the real world, that property
* always exists for this CLI-only path of logic.
*
if (connOpts?.parms?.response?.console?.log) {
// we want to prompt for connection values, but first complain if user only has V1 profiles.
connOpts.parms.response.console.log("No Zowe client configuration exists.");
if (ConfigUtils.onlyV1ProfilesExist) {
connOpts.parms.response.console.log(
"Only V1 profiles exist. V1 profiles are no longer supported.\n" +
"You should convert your V1 profiles to a newer Zowe client configuration."
);
/* The check for console.log in the following 'if' statement is only needed for tests
* which do not create a mock for the connOpts.parms.response.console.log property.
* In the real world, that property always exists for this CLI-only path of logic.
*/
if (promptForValues.length > 0 && connOpts?.parms?.response?.console?.log) {
// We need to prompt for some values. Determine why we need to prompt.
let reasonForPrompts: string = "";
if (ImperativeConfig.instance.config?.exists) {
reasonForPrompts += "Some required connection properties have not been specified " +
"in your Zowe client configuration. ";
} else if (ConfigUtils.onlyV1ProfilesExist) {
reasonForPrompts += "Only V1 profiles exist. V1 profiles are no longer supported. " +
"You should convert your V1 profiles to a newer Zowe client configuration. ";
} else {
reasonForPrompts += "No Zowe client configuration exists. ";
}
connOpts.parms.response.console.log(
"Therefore, you will be asked for the connection properties\n" +
"that are required to complete your command.\n"

reasonForPrompts += "Therefore, you will be asked for the connection properties " +
"that are required to complete your command.\n";
connOpts.parms.response.console.log(TextUtils.wordWrap(
TextUtils.chalk.yellowBright(reasonForPrompts))
);
}
*/

const answers: { [key: string]: any } = {};
const profileSchema = this.loadSchemaForSessCfgProps(connOpts.parms, promptForValues);
Expand Down
4 changes: 3 additions & 1 deletion packages/imperative/src/utilities/src/TextUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export class TextUtils {
* by the user's terminal
* @returns {number} - the width that will work best for the user's terminal
*/
public static getRecommendedWidth(preferredWidth: number = TextUtils.DEFAULT_WRAP_WIDTH): number {
public static getRecommendedWidth(
preferredWidth: number = process?.stdout?.columns ? process.stdout.columns : TextUtils.DEFAULT_WRAP_WIDTH
): number {
const widthSafeGuard = 8; // prevent partial words from continuing over lines
const yargs = require("yargs");
const maxWidth = !isNullOrUndefined(yargs.terminalWidth() && yargs.terminalWidth() > 0) ?
Expand Down
Loading