Skip to content

Commit

Permalink
fix: refactor initialisation of Terminal UI (hashgraph#560)
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Yanev <[email protected]>
  • Loading branch information
victor-yanev authored Mar 14, 2024
1 parent e2f0a7b commit e9e73a6
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 69 deletions.
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ yargs(hideBin(process.argv))
},
async () => await new StateController("debug").startStateMachine()
)
.middleware(function (argv) {
Bootstrapper.Initiailze(argv);
})
.middleware(Bootstrapper.initialize)
.demandCommand()
.strictCommands()
.recommendCommands()
Expand Down
5 changes: 2 additions & 3 deletions src/services/Bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ import { ServiceLocator } from './ServiceLocator';
*/
export class Bootstrapper {
/**
* Initialize the application.
* Initializes all the services and registers them in the service locator.
* @param {yargs.ArgumentsCamelCase<{}>} argv - The command line arguments.
* @returns {Promise<void>} A promise that resolves when the initialization is complete.
*/
public static async Initiailze(argv: yargs.ArgumentsCamelCase<{}>): Promise<void> {
public static initialize(argv: yargs.ArgumentsCamelCase<{}>): void {
const verbose = CLIService.resolveVerboseLevel(argv.verbose as string)
ServiceLocator.Current.register(new LoggerService(verbose));
ServiceLocator.Current.register(new CLIService(argv));
Expand Down
104 changes: 61 additions & 43 deletions src/services/LoggerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,36 @@
*
*/

import { Widgets, screen, log } from 'blessed';
import { Widgets, log, screen } from 'blessed';
import terminal from 'blessed-terminal';
import { IService } from './IService';
import { ServiceLocator } from './ServiceLocator';
import { CLIService } from './CLIService';
import {
COLOR_DIM,
COLOR_RESET,
CONSENSUS_NODE_LABEL,
CONTAINERS,
DEBUG_COLOR,
ERROR_COLOR,
INFO_COLOR,
MIRROR_NODE_LABEL,
COLOR_RESET,
RELAY_LABEL,
TRACE_COLOR,
WARNING_COLOR,
COLOR_DIM
WARNING_COLOR
} from '../constants';
import { AccountCreationState } from '../state/AccountCreationState';
import { VerboseLevel } from '../types/VerboseLevel';
import { CLIService } from './CLIService';
import { ConnectionService } from './ConnectionService';
import { DockerService } from './DockerService';
import { VerboseLevel } from '../types/VerboseLevel';
import { IService } from './IService';
import { ServiceLocator } from './ServiceLocator';


export enum LogBoard {
CONSENSUS = 'CONSENSUS',
MIRROR = 'MIRROR',
RELAY = 'RELAY',
ACCOUNT = 'ACCOUNT'
}

/**
* LoggerService is a service class that handles logging.
Expand Down Expand Up @@ -149,6 +158,19 @@ export class LoggerService implements IService{
}
}

/**
* Get the log board based on the given module (service class name).
* @param module - The module where the message originates.
* @returns The {@link LogBoard} where the message should be printed.
*/
private static getLogLocation(module: string): LogBoard {
if (module === AccountCreationState.name) {
return LogBoard.ACCOUNT;
} else {
return LogBoard.CONSENSUS;
}
}

/**
* Builds the message to log.
* @param msg - The message to log.
Expand Down Expand Up @@ -231,18 +253,6 @@ export class LoggerService implements IService{
this.writeToLog(msgToLog, module);
}

/**
* Logs an empty line.
*/
public emptyLine(): void {
const detached = this.getLogMode();
if (detached) {
this.logger.log('');
} else {
this.logToTUI('', '');
}
}

/**
* Attaches a terminal user interface to the logger.
* @param msg - The message to log.
Expand Down Expand Up @@ -272,19 +282,19 @@ export class LoggerService implements IService{
* @param module - The module where the message originates.
*/
private writeToLog(msg: string, module: string): void {
const detached = this.getLogMode();
const detached = this.isDetachedMode();
if (detached) {
this.logger.log(msg);
} else {
this.logToTUI(msg, module);
const logBoard = LoggerService.getLogLocation(module);
this.logToTUI(msg, logBoard);
}
}

/**
* Returns the log mode.
* @returns {boolean} True if the log mode is detached, false otherwise.
*/
private getLogMode(): boolean {
private isDetachedMode(): boolean {
let isDetached = true;
try {
isDetached = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv().detached;
Expand All @@ -297,37 +307,38 @@ export class LoggerService implements IService{
/**
* Logs a message to the terminal user interface.
* @param msg - The message to log.
* @param module - The module where the message originates.
* @param logBoard - The log board where the message should be printed.
*/
private logToTUI(msg: string, module: string): void {
if (this.screen === undefined) {
this.initiliazeTerminalUI();
private logToTUI(msg: string, logBoard: LogBoard): void {
if (!this.isTerminalUIInitialized()) {
this.logger.log(msg);
return;
}
const consensusLog = this.consensusLog as terminal.Widgets.LogElement;
const accountBoard = this.accountBoard as terminal.Widgets.LogElement;
switch (module) {
case "InitState":
consensusLog.log(msg);
break;
case "NetworkPrepState":
consensusLog.log(msg);

switch (logBoard) {
case LogBoard.ACCOUNT:
this.accountBoard?.log(msg);
break;
case "StartState":
consensusLog.log(msg);
case LogBoard.RELAY:
this.relayLog?.log(msg);
break;
case "AccountCreationState":
accountBoard.log(msg);
case LogBoard.MIRROR:
this.mirrorLog?.log(msg);
break;
default:
consensusLog.log(msg);
case LogBoard.CONSENSUS:
this.consensusLog?.log(msg);
break;
}
}

/**
* Initializes the terminal user interface.
*/
private initiliazeTerminalUI(): void {
public initializeTerminalUI(): void {
if (this.isDetachedMode() || this.isTerminalUIInitialized()) {
return;
}

const window: Widgets.Screen = screen({
smartCSR: true,
});
Expand Down Expand Up @@ -387,6 +398,13 @@ export class LoggerService implements IService{
this.accountBoard = accountBoard;
}

/**
* @returns true if the terminal user interface is initialized.
*/
private isTerminalUIInitialized(): boolean {
return !!this.screen;
}

/**
* Initializes the info board.
* @param {terminal.grid} grid - The grid where the info board is placed.
Expand Down
11 changes: 5 additions & 6 deletions src/state/StartState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,17 @@
*/

import shell from 'shelljs';
import path from 'path';
import fs from 'fs';
import { LocalNodeErrors } from '../Errors/LocalNodeErrors';
import { START_STATE_INIT_MESSAGE, START_STATE_STARTED_DETECTING, START_STATE_STARTED_MESSAGE, START_STATE_STARTING_MESSAGE } from '../constants';
import { IOBserver } from '../controller/IObserver';
import { CLIService } from '../services/CLIService';
import { ConnectionService } from '../services/ConnectionService';
import { DockerService } from '../services/DockerService';
import { LoggerService } from '../services/LoggerService';
import { ServiceLocator } from '../services/ServiceLocator';
import { CLIOptions } from '../types/CLIOptions';
import { EventType } from '../types/EventType';
import { IState } from './IState';
import { ConnectionService } from '../services/ConnectionService';
import { LocalNodeErrors } from '../Errors/LocalNodeErrors';
import { DockerService } from '../services/DockerService';
import { START_STATE_INIT_MESSAGE, START_STATE_STARTED_DETECTING, START_STATE_STARTED_MESSAGE, START_STATE_STARTING_MESSAGE } from '../constants';

export class StartState implements IState{
/**
Expand Down Expand Up @@ -96,6 +94,7 @@ export class StartState implements IState{
*/
public async onStart(): Promise<void> {
this.logger.info(START_STATE_STARTING_MESSAGE, this.stateName);
this.logger.initializeTerminalUI();

const rootPath = process.cwd();

Expand Down
15 changes: 9 additions & 6 deletions test/unit/states/InitState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
import { expect } from 'chai';
import fs from 'fs';
import yaml from 'js-yaml';
import { join } from 'path';
import { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
import { LoggerService } from '../../../src/services/LoggerService';
import { InitState } from '../../../src/state/InitState';
import { getTestBed } from '../testBed';
import {
APPLICATION_YML_RELATIVE_PATH,
INIT_STATE_BOOTSTRAPPED_PROP_SET,
Expand All @@ -41,12 +39,14 @@ import {
OPTIONAL_PORTS,
RECORD_PARSER_SOURCE_REL_PATH,
} from '../../../src/constants';
import { ConfigurationData } from '../../../src/data/ConfigurationData';
import { CLIService } from '../../../src/services/CLIService';
import { DockerService } from '../../../src/services/DockerService';
import { LoggerService } from '../../../src/services/LoggerService';
import { InitState } from '../../../src/state/InitState';
import { EventType } from '../../../src/types/EventType';
import { CLIService } from '../../../src/services/CLIService';
import { ConfigurationData } from '../../../src/data/ConfigurationData';
import { FileSystemUtils } from '../../../src/utils/FileSystemUtils';
import { join } from 'path';
import { getTestBed } from '../testBed';

describe('InitState tests', () => {
let initState: InitState,
Expand Down Expand Up @@ -159,6 +159,8 @@ describe('InitState tests', () => {
testSandbox.assert.calledOnce(configureEnvVariablesStub);
testSandbox.assert.calledOnce(configureNodePropertiesStub);
testSandbox.assert.calledOnce(configureMirrorNodePropertiesStub);

testSandbox.assert.notCalled(loggerService.initializeTerminalUI);
})

it('should execute onStart and finish with UnresolvableError on docker checks (Compose Version, Docker Running, Resources)', async () => {
Expand All @@ -176,6 +178,7 @@ describe('InitState tests', () => {
testSandbox.assert.calledWith(observerSpy, EventType.UnresolvableError);

testSandbox.assert.notCalled(dockerService.isPortInUse);
testSandbox.assert.notCalled(loggerService.initializeTerminalUI);
})

describe('private functions', () => {
Expand Down
22 changes: 15 additions & 7 deletions test/unit/states/startState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
*/

import { expect } from 'chai';
import { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
import { resolve } from 'path';
import { ShellString } from 'shelljs';
import { LoggerService } from '../../../src/services/LoggerService';
import { StartState } from '../../../src/state/StartState';
import { getTestBed } from '../testBed';
import { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
import { LocalNodeErrors } from '../../../src/Errors/LocalNodeErrors';
import {
START_STATE_INIT_MESSAGE,
START_STATE_STARTED_DETECTING,
START_STATE_STARTED_MESSAGE,
START_STATE_STARTING_MESSAGE
} from '../../../src/constants';
import { resolve } from 'path';
import { ConnectionService } from '../../../src/services/ConnectionService';
import { DockerService } from '../../../src/services/DockerService';
import { LoggerService } from '../../../src/services/LoggerService';
import { StartState } from '../../../src/state/StartState';
import { EventType } from '../../../src/types/EventType';
import { ConnectionService } from '../../../src/services/ConnectionService';
import { LocalNodeErrors } from '../../../src/Errors/LocalNodeErrors';
import { getTestBed } from '../testBed';

describe('StartState tests', () => {
let startState: StartState,
Expand Down Expand Up @@ -117,6 +117,8 @@ describe('StartState tests', () => {

testSandbox.assert.calledOnce(processTestBed.processCWDStub);
testSandbox.assert.calledWith(observerSpy, EventType.Finish);

testSandbox.assert.calledOnce(loggerService.initializeTerminalUI);
})

it('should execute onStart and send DockerError event (when dockerComposeUp status code eq 1)', async () => {
Expand All @@ -128,6 +130,8 @@ describe('StartState tests', () => {
testSandbox.assert.match(observerSpy.callCount, 2);
testSandbox.assert.match(observerSpy.args[0], EventType.DockerError);
testSandbox.assert.match(observerSpy.args[1], EventType.Finish);

testSandbox.assert.calledOnce(loggerService.initializeTerminalUI);
})

it('should execute onStart and handle connectionService error (LocalNodeError)', async () => {
Expand All @@ -140,6 +144,8 @@ describe('StartState tests', () => {
testSandbox.assert.match(observerSpy.callCount, 1);
testSandbox.assert.match(observerSpy.args[0], EventType.UnknownError);
testSandbox.assert.calledWith(loggerService.error, 'message', StartState.name);

testSandbox.assert.calledOnce(loggerService.initializeTerminalUI);
})

it('should execute onStart and handle connectionService error (generic error)', async () => {
Expand All @@ -152,5 +158,7 @@ describe('StartState tests', () => {
testSandbox.assert.match(observerSpy.callCount, 1);
testSandbox.assert.match(observerSpy.args[0], EventType.UnknownError);
testSandbox.assert.notCalled(loggerService.error);

testSandbox.assert.calledOnce(loggerService.initializeTerminalUI);
})
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build", /* Specify an output folder for all emitted files. */
Expand Down

0 comments on commit e9e73a6

Please sign in to comment.