Skip to content

Commit

Permalink
Merge pull request #2118 from zowe/poc-1987-mvp
Browse files Browse the repository at this point in the history
MVP: Imperative Event Emitter
  • Loading branch information
zFernand0 authored May 15, 2024
2 parents 18208f8 + d68e05d commit ed5f71a
Show file tree
Hide file tree
Showing 33 changed files with 1,127 additions and 47 deletions.
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 client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/pull/1987)

## `8.0.0-next.202405061946`

- Enhancement: Consolidated the Zowe client log files into the same directory. [#2116](https://github.com/zowe/zowe-cli/issues/2116)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ describe("imperative-test-cli test logging command", () => {

// Set the ENV var for the script
const response = runCliScript(__dirname + "/__scripts__/test_logging_cmd.sh",
TEST_ENVIRONMENT.workingDir, [], { IMPERATIVE_TEST_CLI_IMPERATIVE_LOG_LEVEL: "OFF" });
TEST_ENVIRONMENT.workingDir, [], {
IMPERATIVE_TEST_CLI_IMPERATIVE_LOG_LEVEL: "OFF",
IMPERATIVE_TEST_CLI_APP_LOG_LEVEL: "OFF"
});
expect(response.stderr.toString()).toBe("");
expect(response.status).toBe(0);
expect(response.stdout.toString()).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { join } from "path";
jest.mock("../src/syntax/SyntaxValidator");
jest.mock("../src/utils/SharedOptions");
jest.mock("../../utilities/src/ImperativeConfig");
jest.mock("../../events/src/ImperativeEventEmitter");

// Persist the original definitions of process.write
const ORIGINAL_STDOUT_WRITE = process.stdout.write;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { IConfig } from "../src/doc/IConfig";
import { IConfigLayer } from "../src/doc/IConfigLayer";
import { IConfigProfile } from "../src/doc/IConfigProfile";

jest.mock("../../events/src/ImperativeEventEmitter");

const MY_APP = "my_app";

const mergeConfig: IConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Config } from "../src/Config";
import { IConfig } from "../src/doc/IConfig";
import { IConfigSecure } from "../src/doc/IConfigSecure";
import { IConfigVault } from "../src/doc/IConfigVault";
import { ImperativeEventEmitter } from "../../events";

const MY_APP = "my_app";

Expand Down Expand Up @@ -46,6 +47,9 @@ describe("Config secure tests", () => {
});

beforeEach(() => {
jest.spyOn(ImperativeEventEmitter, "initialize").mockImplementation();
Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }});

mockSecureLoad = jest.fn();
mockSecureSave = jest.fn();
mockVault = {
Expand Down
3 changes: 3 additions & 0 deletions packages/imperative/src/config/__tests__/Config.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { ConfigConstants } from "../src/ConfigConstants";
import * as JSONC from "comment-json";
import { ConfigLayers, ConfigSecure } from "../src/api";


jest.mock("../../events/src/ImperativeEventEmitter");

const MY_APP = "my_app";

describe("Config tests", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/

jest.mock("../../logger/src/LoggerUtils");
jest.mock("../../events/src/ImperativeEventEmitter");

import { AbstractAuthHandler } from "../../imperative";
import { SessConstants } from "../../rest";
import { ImperativeConfig } from "../../utilities";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts";
import { ConfigSchema } from "../src/ConfigSchema";
import { Logger } from "../..";

jest.mock("../../events/src/ImperativeEventEmitter");

const testAppNm = "ProfInfoApp";
const testEnvPrefix = testAppNm.toUpperCase();
const profileTypes = ["zosmf", "tso", "base", "dummy"];
Expand Down
4 changes: 4 additions & 0 deletions packages/imperative/src/config/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { ConfigUtils } from "./ConfigUtils";
import { IConfigSchemaInfo } from "./doc/IConfigSchema";
import { JsUtils } from "../../utilities/src/JsUtils";
import { IConfigMergeOpts } from "./doc/IConfigMergeOpts";
import { ImperativeEventEmitter } from "../../events";
import { Logger } from "../../logger";

/**
* Enum used by Config class to maintain order of config layers
Expand Down Expand Up @@ -153,6 +155,8 @@ export class Config {
myNewConfig.mVault = opts.vault;
myNewConfig.mSecure = {};

ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() });

// Populate configuration file layers
await myNewConfig.reload(opts);

Expand Down
48 changes: 46 additions & 2 deletions packages/imperative/src/config/src/ConfigUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
*
*/

import { normalize as pathNormalize } from "path";
import { homedir as osHomedir } from "os";
import { normalize as pathNormalize, join as pathJoin } from "path";
import { existsSync as fsExistsSync } from "fs";

import { CredentialManagerFactory } from "../../security";
import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory";
import { ICommandArguments } from "../../cmd";
import { ImperativeConfig } from "../../utilities";
import { ImperativeError } from "../../error";
import { LoggerManager } from "../../logger/src/LoggerManager";
import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer";
import { Logger } from "../../logger/src/Logger";
import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings";

export class ConfigUtils {
/**
Expand Down Expand Up @@ -114,4 +119,43 @@ export class ConfigUtils {
additionalDetails: details
});
}


// _______________________________________________________________________
/**
* Perform a rudimentary initialization of some Imperative utilities.
* We must do this because VSCode apps do not typically call imperative.init.
* @internal
*/
public static initImpUtils(appName: string) {
// create a rudimentary ImperativeConfig if it has not been initialized
if (ImperativeConfig.instance.loadedConfig == null) {
let homeDir: string = null;
const envVarPrefix = appName.toUpperCase();
const envVarNm = envVarPrefix + EnvironmentalVariableSettings.CLI_HOME_SUFFIX;
if (process.env[envVarNm] === undefined) {
// use OS home directory
homeDir = pathJoin(osHomedir(), "." + appName.toLowerCase());
} else {
// use the available environment variable
homeDir = pathNormalize(process.env[envVarNm]);
}
ImperativeConfig.instance.loadedConfig = {
name: appName,
defaultHome: homeDir,
envVariablePrefix: envVarPrefix
};
ImperativeConfig.instance.rootCommandName = appName;
}

// initialize logging
if (LoggerManager.instance.isLoggerInit === false) {
const loggingConfig = LoggingConfigurer.configureLogger(
ImperativeConfig.instance.cliHome, ImperativeConfig.instance.loadedConfig
);
Logger.initLogger(loggingConfig);
}
return Logger.getImperativeLogger();
}

}
41 changes: 1 addition & 40 deletions packages/imperative/src/config/src/ProfileInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ import { ICommandProfileProperty, ICommandArguments } from "../../cmd";
import { IProfileLoaded, IProfileProperty, IProfileSchema } from "../../profiles";

// for imperative operations
import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings";
import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer";
import { CliUtils, ImperativeConfig } from "../../utilities";
import { ImperativeExpect } from "../../expect";
import { Logger, LoggerUtils } from "../../logger";
import { LoggerManager } from "../../logger/src/LoggerManager";
import {
IOptionsForAddConnProps, ISession, Session, SessConstants, ConnectionPropsForSessCfg
} from "../../rest";
Expand Down Expand Up @@ -180,7 +177,7 @@ export class ProfileInfo {
this.mCredentials = new ProfileCredentials(this, profInfoOpts);

// do enough Imperative stuff to let imperative utilities work
this.initImpUtils();
this.mImpLogger = ConfigUtils.initImpUtils(this.mAppName);
}

/**
Expand Down Expand Up @@ -969,42 +966,6 @@ export class ProfileInfo {
}
}

// _______________________________________________________________________
/**
* Perform a rudimentary initialization of some Imperative utilities.
* We must do this because VSCode apps do not typically call imperative.init.
*/
private initImpUtils() {
// create a rudimentary ImperativeConfig if it has not been initialized
if (ImperativeConfig.instance.loadedConfig == null) {
let homeDir: string = null;
const envVarPrefix = this.mAppName.toUpperCase();
const envVarNm = envVarPrefix + EnvironmentalVariableSettings.CLI_HOME_SUFFIX;
if (process.env[envVarNm] === undefined) {
// use OS home directory
homeDir = path.join(os.homedir(), "." + this.mAppName.toLowerCase());
} else {
// use the available environment variable
homeDir = path.normalize(process.env[envVarNm]);
}
ImperativeConfig.instance.loadedConfig = {
name: this.mAppName,
defaultHome: homeDir,
envVariablePrefix: envVarPrefix
};
ImperativeConfig.instance.rootCommandName = this.mAppName;
}

// initialize logging
if (LoggerManager.instance.isLoggerInit === false) {
const loggingConfig = LoggingConfigurer.configureLogger(
ImperativeConfig.instance.cliHome, ImperativeConfig.instance.loadedConfig
);
Logger.initLogger(loggingConfig);
}
this.mImpLogger = Logger.getImperativeLogger();
}

/**
* Load any profile schema objects found on disk and cache them. For team
* config, we check each config layer and load its schema JSON if there is
Expand Down
3 changes: 3 additions & 0 deletions packages/imperative/src/config/src/api/ConfigSecure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { ConfigConstants } from "../ConfigConstants";
import { IConfigProfile } from "../doc/IConfigProfile";
import { CredentialManagerFactory } from "../../../security";
import { ConfigUtils } from "../ConfigUtils";
import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter";
import { ImperativeUserEvents } from "../../../events/src/ImperativeEventConstants";

/**
* API Class for manipulating config layers.
Expand Down Expand Up @@ -130,6 +132,7 @@ export class ConfigSecure extends ConfigApi {
*/
public async directSave() {
await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure));
ImperativeEventEmitter.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED);
}

// _______________________________________________________________________
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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.
*
*/

import { IImperativeEventJson, ImperativeEventEmitter, ImperativeSharedEvents } from "../../..";
import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment";
import { TestLogger } from "../../../../__tests__/src/TestLogger";
import * as TestUtil from "../../../../__tests__/src/TestUtil";
import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment";
import * as fs from "fs";
import * as path from "path";

let TEST_ENVIRONMENT: ITestEnvironment;
const iee = ImperativeEventEmitter;
const iee_s = ImperativeSharedEvents;
let cwd = '';

describe("Event Emitter", () => {
const mainModule = process.mainModule;
const testLogger = TestLogger.getTestLogger();

beforeAll(async () => {
(process.mainModule as any) = {
filename: __filename
};

TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({
cliHomeEnvVar: "ZOWE_CLI_HOME",
testName: "event_emitter"
});
cwd = TEST_ENVIRONMENT.workingDir;
});

beforeEach(() => {
iee.initialize("zowe", { logger: testLogger });
});

afterEach(() => {
iee.teardown();
});

afterAll(() => {
process.mainModule = mainModule;
TestUtil.rimraf(cwd);
});

const doesEventFileExists = (eventType: string) => {
const eventDir = iee.instance.getEventDir(eventType);
if (!fs.existsSync(eventDir)) return false;
if (fs.existsSync(path.join(eventDir, eventType))) return true;
return false;
};

describe("Shared Events", () => {
it("should create an event file upon first subscription if the file does not exist", () => {
const theEvent = iee_s.ON_CREDENTIAL_MANAGER_CHANGED;

expect(doesEventFileExists(theEvent)).toBeFalsy();

const subSpy = jest.fn();
iee.instance.subscribe(theEvent, subSpy);

expect(subSpy).not.toHaveBeenCalled();
expect(doesEventFileExists(theEvent)).toBeTruthy();

expect(iee.instance.getEventContents(theEvent)).toBeFalsy();

iee.instance.emitEvent(theEvent);

(iee.instance as any).subscriptions.get(theEvent)[1][0](); // simulate FSWatcher called

expect(doesEventFileExists(theEvent)).toBeTruthy();
const eventDetails: IImperativeEventJson = JSON.parse(iee.instance.getEventContents(theEvent));
expect(eventDetails.type).toEqual(theEvent);
expect(eventDetails.user).toBeFalsy();

expect(subSpy).toHaveBeenCalled();
});
it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { });

Check warning on line 86 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
it("should not affect subscriptions from another instance when unsubscribing from events", () => { });

Check warning on line 87 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
});

describe("User Events", () => {
it("should create an event file upon first subscription if the file does not exist", () => { });

Check warning on line 91 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
it("should trigger subscriptions for all instances watching for onVaultChanged", () => { });

Check warning on line 92 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
it("should not affect subscriptions from another instance when unsubscribing from events", () => { });

Check warning on line 93 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
});

describe("Custom Events", () => {
it("should create an event file upon first subscription if the file does not exist", () => { });

Check warning on line 97 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => { });

Check warning on line 98 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
it("should not affect subscriptions from another instance when unsubscribing from events", () => { });

Check warning on line 99 in packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts

View workflow job for this annotation

GitHub Actions / release

Test has no assertions
});
});
Loading

0 comments on commit ed5f71a

Please sign in to comment.