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

MVP: Imperative Event Emitter #2118

Merged
merged 33 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f255eb6
feat(events): implement initial phase - Writers
zFernand0 Jan 23, 2024
b6b056c
forgot to export and to strip internal
zFernand0 Jan 23, 2024
a1f1b71
Add the ability to write custom events
zFernand0 Jan 24, 2024
81be315
ensure that imperative utilities are initialized
zFernand0 Jan 25, 2024
b6503a4
Refactor: changed class names, event names, event format to JSON
zFernand0 Jan 26, 2024
71bdc82
Merge branch 'next' of https://github.com/zowe/zowe-cli into poc-1987
zFernand0 Mar 20, 2024
3072c15
add an idea for singleton approach
zFernand0 Mar 20, 2024
328de2f
MVP
zFernand0 Mar 21, 2024
f0f17ed
fix: prevent multiple watchers for the same event
zFernand0 Mar 27, 2024
4f0fcf7
fix: logger of undefined
zFernand0 Mar 27, 2024
7730aba
fix: reuse the logger
zFernand0 Mar 27, 2024
4ee31c7
fixing ENOENT error upon initial subscription to event
Mar 27, 2024
dca31e4
adding method to unsubscribe from events
Mar 27, 2024
b77a0ee
removing subscription history upon deletion
Mar 27, 2024
f2fe260
fix: allow only one event per registration
zFernand0 Mar 27, 2024
c13a40b
outline for tests
Apr 2, 2024
59095c5
fix: properly mock eventemitter in old unit tests
zFernand0 Apr 6, 2024
4800dac
test: half-way through tests
zFernand0 Apr 8, 2024
5816bbd
test: finalize unit-tests on event emitter
zFernand0 Apr 12, 2024
7feae61
lint: fix lint issues
zFernand0 Apr 12, 2024
aa58853
Merge branch 'next' of https://github.com/zowe/zowe-cli into poc-1987…
zFernand0 Apr 12, 2024
a600acd
fix build by allowing other files in packages/imperative/__tests__/__…
zFernand0 Apr 22, 2024
6402b92
test: moved the integration tests odwn to events instead
zFernand0 Apr 22, 2024
dcd805d
refactor Imperative Event
zFernand0 Apr 26, 2024
95304a3
chore: add teardown stage
zFernand0 May 8, 2024
932c6c6
test: update integration test
zFernand0 May 8, 2024
3b24f11
Merge branch 'next' of https://github.com/zowe/zowe-cli into poc-1987…
zFernand0 May 8, 2024
f3a2e05
Merge branch 'next' of https://github.com/zowe/zowe-cli into poc-1987…
zFernand0 May 13, 2024
bd7e040
fix: address PR comments :yum:
zFernand0 May 13, 2024
44e7a80
tests: fix unit tests and add new ones for teardown and getContents
zFernand0 May 13, 2024
31b0591
tests: forgot to mock fs module
zFernand0 May 13, 2024
5e45fd8
chore: address PR feedback
zFernand0 May 13, 2024
d68e05d
chore: update inteface for EventCallback
zFernand0 May 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
*/

import { IImperativeEventJson, ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../../..";
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";
Expand All @@ -19,7 +19,6 @@

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

Expand Down Expand Up @@ -84,19 +83,19 @@

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 / lint

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 / lint

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 / lint

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 / lint

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 / lint

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 / lint

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 / lint

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 / lint

Test has no assertions
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { homedir } from "os";
import { Logger } from "../../../logger/src/Logger";
import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../..";

jest.mock("fs");

describe("Event Emitter", () => {
const iee = ImperativeEventEmitter;
const sharedDir = join(__dirname, ".zowe", ".events");
Expand Down Expand Up @@ -148,9 +150,9 @@ describe("Event Emitter", () => {
() => { iee.instance.emitCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); },

// Subscribing should fail if IEE is not initialized
() => { iee.instance.subscribe("dummy", jest.fn); },
() => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn); },
() => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn); },
() => { iee.instance.subscribe("dummy", jest.fn()); },
() => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn()); },
() => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn()); },
() => { iee.instance.unsubscribe("dummy"); },
() => { iee.instance.unsubscribe(ImperativeUserEvents.ON_VAULT_CHANGED); },
() => { iee.instance.unsubscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); },
Expand All @@ -167,7 +169,7 @@ describe("Event Emitter", () => {

const theEvent = ImperativeUserEvents.ON_VAULT_CHANGED;
try {
iee.instance.subscribe(theEvent, jest.fn);
iee.instance.subscribe(theEvent, jest.fn());
} catch (err) {
expect(err.message).toContain("Unable to create '.events' directory.");
}
Expand All @@ -177,7 +179,7 @@ describe("Event Emitter", () => {
jest.spyOn(fs, "closeSync").mockImplementation(() => { throw "FILE"; });

try {
iee.instance.subscribe(theEvent, jest.fn);
iee.instance.subscribe(theEvent, jest.fn());
} catch (err) {
expect(err.message).toContain("Unable to create event file.");
}
Expand Down Expand Up @@ -249,5 +251,40 @@ describe("Event Emitter", () => {
iee.instance.unsubscribe("dummy");
expect(closeWatcher).toHaveBeenCalled();
});

it("should teardown the Event Emitter instance successfully", () => {
expect((iee as any).initialized).toBeFalsy();
iee.teardown();
expect((iee as any).initialized).toBeFalsy();

iee.initialize("zowe", {logger: jest.fn() as any});
expect((iee as any).initialized).toBeTruthy();

const dummyMap = {
has: () => (true),
delete: jest.fn(),
keys: () => ["dummy"],
get: () => ([{ removeAllListeners }, jest.fn()])
};
// Mocked map of subscriptions
(iee.instance as any).subscriptions = dummyMap;
(iee.instance as any).eventTimes = dummyMap;

iee.teardown();
expect(closeWatcher).toHaveBeenCalled();
expect((iee as any).initialized).toBeFalsy();
});

it("should retrieve event contents successfully", () => {
jest.spyOn(fs, "existsSync").mockReturnValueOnce(false);
iee.initialize("zowe");
let contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED);
expect(contents).toEqual("");

jest.spyOn(fs, "existsSync").mockReturnValueOnce(true);
jest.spyOn(fs, "readFileSync").mockReturnValueOnce("dummy");
contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED);
expect(contents).toEqual("dummy");
});
});
});
12 changes: 6 additions & 6 deletions packages/imperative/src/events/src/ImperativeEventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
import { ImperativeEvent } from "./ImperativeEvent";
import { Logger } from "../../logger/src/Logger";
import { LoggerManager } from "../../logger/src/LoggerManager";
import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc";
import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson, ImperativeEventCallback } from "./doc";
import { ConfigUtils } from "../../config/src/ConfigUtils";

export class ImperativeEventEmitter {
private static mInstance: ImperativeEventEmitter;
private static initialized = false;
private subscriptions: Map<string, [fs.FSWatcher, Function[]]>;
private subscriptions: Map<string, [fs.FSWatcher, ImperativeEventCallback[]]>;
private eventTimes: Map<string, string>;
public appName: string;
public logger: Logger;
Expand Down Expand Up @@ -129,7 +129,7 @@
* @param callbacks list of all callbacks for this watcher
* @returns The FSWatcher instance created
*/
private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher {
private setupWatcher(eventName: string, callbacks: ImperativeEventCallback[] = []): fs.FSWatcher {
const dir = this.getEventDir(eventName);
this.ensureEventsDirExists(dir);
this.ensureEventFileExists(join(dir, eventName));
Expand All @@ -140,10 +140,10 @@
const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time;

if (this.eventTimes.get(eventName) !== eventTime) {
this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventName}`);

Check warning on line 143 in packages/imperative/src/events/src/ImperativeEventEmitter.ts

View check run for this annotation

Codecov / codecov/patch

packages/imperative/src/events/src/ImperativeEventEmitter.ts#L143

Added line #L143 was not covered by tests
// Promise.all(callbacks)
callbacks.forEach(cb => cb());
this.eventTimes.set(eventName, eventTime);

Check warning on line 146 in packages/imperative/src/events/src/ImperativeEventEmitter.ts

View check run for this annotation

Codecov / codecov/patch

packages/imperative/src/events/src/ImperativeEventEmitter.ts#L145-L146

Added lines #L145 - L146 were not covered by tests
}
});
this.subscriptions.set(eventName, [watcher, callbacks]);
Expand Down Expand Up @@ -181,14 +181,14 @@
/**
* ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged`
*/
public getSharedEventDir(): string {
private getSharedEventDir(): string {
return join(ImperativeConfig.instance.cliHome, ".events");
}

/**
* USER HOME directory to search for user specific ImperativeEvents like `vaultChanged`
*/
public getUserEventDir(): string {
private getUserEventDir(): string {
return join(homedir(), ".zowe", ".events");
}

Expand Down Expand Up @@ -254,7 +254,7 @@
* @param eventName Type of event to register
* @param callback Action to be registered to the given event
*/
public subscribe(eventName: string, callback: Function): IImperativeRegisteredAction {
public subscribe(eventName: string, callback: ImperativeEventCallback): IImperativeRegisteredAction {
this.ensureClassInitialized();

let watcher: fs.FSWatcher;
Expand All @@ -277,7 +277,7 @@
this.ensureClassInitialized();

if (this.subscriptions.has(eventName)) {
const [watcherToClose, _callbacks] = this.subscriptions.get(eventName);

Check warning on line 280 in packages/imperative/src/events/src/ImperativeEventEmitter.ts

View workflow job for this annotation

GitHub Actions / lint

'_callbacks' is assigned a value but never used
watcherToClose.removeAllListeners(eventName).close();
this.subscriptions.delete(eventName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ export interface IImperativeEventJson {
*/
user?: boolean;
}

export type ImperativeEventCallback = () => void | Promise<void>;
zFernand0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export interface IImperativeEventParms {
* @type {ImperativeEventType}
* @memberof IImperativeEventParms
*/
eventName: ImperativeEventType | string
eventName: ImperativeEventType | string;
/**
* Specifies whether this is a user event or not
* @type {ImperativeEventType}
* @memberof IImperativeEventParms
*/
isUser: boolean
isUser: boolean;
/**
* The logger to use when logging the imperative event that occurred
* @type {Logger}
Expand Down
Loading