@zowe/imperative
Version:
framework for building configurable CLIs
480 lines • 19.4 kB
JavaScript
"use strict";
/*
* 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Operation = void 0;
const TaskStage_1 = require("./TaskStage");
const fs = require("fs");
const utilities_1 = require("../../utilities");
const TaskProgress_1 = require("./TaskProgress");
const logger_1 = require("../../logger");
/**
* @deprecated
*/
class Operation {
static summarizeResults(operationResults) {
let currentResults = operationResults;
let overallFailure = false;
const staticLogger = logger_1.Logger.getImperativeLogger();
staticLogger.info("***********************************************************************************");
staticLogger.info("**************************** Operation Results Summary ****************************");
if (currentResults == null) {
staticLogger.info("**************************************" +
"*********************************************");
staticLogger.info("No results to display");
}
else {
while (currentResults != null) {
staticLogger.info("*************************************************" +
"**********************************");
if (currentResults.operationFailed) {
staticLogger.error("Operation Failed: " + currentResults.operationName);
if (currentResults.critical) {
overallFailure = true;
}
}
else {
staticLogger.info("Operation Succeeded: " + currentResults.operationName);
}
currentResults.operationObject.logOperationResults();
staticLogger.info("Number of info messages for result: %d", currentResults.infoMessages.length);
for (const msg of currentResults.infoMessages) {
staticLogger.info(msg);
}
staticLogger.info("Number of error messages for result: %d", currentResults.errorMessages.length);
for (const msg of currentResults.errorMessages) {
staticLogger.error(msg);
}
currentResults = currentResults.nextOperationResult;
}
}
if (overallFailure) {
staticLogger.info("**************************************" +
"*********************************************");
staticLogger.error("************************The operation has failed. Sorry.***********" +
"***************");
staticLogger.info("*******************************************" +
"****************************************");
}
else {
staticLogger.info("***********************************************************************" +
"************");
staticLogger.info("**********************The operation has succeeded! Rejoice!*******" +
"*****************");
staticLogger.info(Operation.outputSeparator +
"****************");
}
return overallFailure;
}
get statusMessage() {
return this.mStatusMessage;
}
get percentComplete() {
return this.mPercentComplete;
}
set percentComplete(newPercent) {
if (newPercent > TaskProgress_1.TaskProgress.ONE_HUNDRED_PERCENT) {
this.log.warn("An attempt was made to set percent complete in Operation to a value above 100");
this.mPercentComplete = TaskProgress_1.TaskProgress.ONE_HUNDRED_PERCENT;
}
else {
this.mPercentComplete = Math.ceil(newPercent);
}
}
get totalOperations() {
return 1; // base case: 1 operation, no subops
}
/**
* Action constructor to build action object
* @param {string} opName: the name of the operation
* @param {boolean} criticalOperation the operation is critical - meaning if it fails the entire operation fails and
* the undo methods should be called.
*/
constructor(opName, criticalOperation) {
this.infoMessages = [];
this.errorMessages = [];
/**
* The full list of operation results
*/
this.mOperationResults = null;
/**
* The status of the operation
*/
this.mStageName = TaskStage_1.TaskStage.NOT_STARTED;
this.mPercentComplete = 0;
/**
* The list of all files that have been created by the service function
* @type {Array}
*/
this.mAllFilesCreated = [];
/**
* The list of files that will be cleaned during an undo operation.
* @type {Array}
*/
this.mFilesForUndo = [];
this.log = logger_1.Logger.getImperativeLogger();
this.mOperationResult = {
operationName: opName,
resultMessage: "",
operationFailed: false,
diverge: false,
divergeTo: null,
continuePath: true,
nextOperationResult: null,
operationObject: this,
operationUndoPossible: false,
operationUndoFailed: false,
operationUndoAttempted: false,
critical: criticalOperation != null ? criticalOperation : false,
output: null,
infoMessages: [],
errorMessages: []
};
}
/**
* Execute the operation. Set the status of the operation to IN_PROGRESS and invoke the implemented operations
* execute method.
*
* @param {any} inputParameters: The input parameters to this operation. This can be anything as defined by
* the operation.
* @param {IOperationResultReady} operationComplete: Operation has completed callback
*/
executeOperation(inputParameters, operationComplete) {
/**
* Set the status of the operation to in progress, log the operation begin message
* and save the callers callback method for after the operation completes.
*/
this.mStageName = TaskStage_1.TaskStage.IN_PROGRESS;
this.logBeginMessage();
this.mOperationCompleteCallback = operationComplete;
/**
* Execute the operation, the operation must invoke the callback method
* to continue the operation sequence. Results will be processed in the
* callback function below.
*/
this.execute(inputParameters, this.operationComplete.bind(this));
}
/**
* Set the status message for the operation
* @param message - the message, including any templates you want replaced like %s or "{{myObject}}"
* @param args - variable args as allowed by printf-like syntax or {myObject: this.myObject}
* @returns {string} the final translated and formatted string (in case you want to log it etc.)
*/
setStatusMessage(message, ...args) {
if (args != null) {
message = utilities_1.TextUtils.formatMessage(message, ...args);
}
this.mStatusMessage = message;
}
/**
* The perform undo method sets that an attempt was made to perform the undo and invokes the
* undo method of the implemented operation.
*
* Before invoking, any files that have been created and saved on the undo-able list will be cleaned.
*
* @param {IOperationUndoCompleted} undoCompletedCallback: the undo completed callback
*/
performUndo(undoCompletedCallback) {
this.log.info("Operation: " + this.mOperationResult.operationName + " attempting undo action.");
this.setOperationUndoAttempted();
this.deleteAllFilesMarkedForUndo();
this.undo(undoCompletedCallback.bind(this));
}
/**
* Accessor method for operation result
* @returns {IOperationResult}: The operation result
*/
get operationResult() {
const result = this.mOperationResult;
result.errorMessages = this.errorMessages;
result.infoMessages = this.infoMessages;
return result;
}
/**
* Accessor method for operation status
* @returns {TaskStage}: The operation status/ stage name e.g. FAILED
*/
get stageName() {
return this.mStageName;
}
/**
* Accessor method to obtain all the files created using the file creator service
* @return {string[]}
*/
get allFilesCreated() {
return this.mAllFilesCreated;
}
/**
* Accessor method to obtain all the files created that are marked as delete on undo
* @return {string[]}
*/
get fileToUndo() {
return this.mFilesForUndo;
}
/**
* Get the operation name for display and other purposes
* @return {string}
*/
get operationName() {
return this.operationResult.operationName;
}
/**
* Set method to indicate that the operation failed.
*/
setOperationFailed() {
this.mOperationResult.operationFailed = true;
}
/**
* Set method to indicate that the operation is "undoable".
*
* This means that if an operation fails, we will 'rollback' through the completed operations
* and undo any that have occurred.
*/
setOperationUndoable() {
this.mOperationResult.operationUndoPossible = true;
}
/**
* Append an additional message to the result message buffer
* @param {string} message: The result message you wish to append.
*/
set operationResultMessage(message) {
this.mOperationResult.resultMessage += " " + message;
}
/**
* Determine if the operation failed
*
* @return {boolean}: If the operation failed
*/
get operationSucceeded() {
return !(this.mStageName === TaskStage_1.TaskStage.FAILED);
}
/**
* Set that the operation undo failed. This is for diagnostic purposes.
*/
setOperationUndoFailed() {
this.mOperationResult.operationUndoFailed = true;
}
/**
* If the operation decides that a different path is required. You can set the next operation to complete
* and whether or not you should continue down the original path
*
* @param {Operation} operation: the operation you use to diverge to
* @param {boolean} continuePathAfterDiverge: Indicates that you want to continue down the normal path after
* the divergent path is complete.
*/
setOperationDiverge(operation, continuePathAfterDiverge) {
this.mOperationResult.diverge = true;
this.mOperationResult.divergeTo = operation;
this.mOperationResult.continuePath = continuePathAfterDiverge;
}
/**
* Set the operation undo attempted (whether it succeed or failed is up to the undo method to set)
*/
setOperationUndoAttempted() {
this.mOperationResult.operationUndoAttempted = true;
}
/**
* Add the result to the end of the results list
* @param {IOperationResult} result: the result from the last operation
*/
addResult(result) {
if (this.mOperationResults == null) {
this.mOperationResults = result;
this.log.debug("Queued first operation to result list: " + result.operationName);
}
else {
let prevResult;
let currentResult = this.mOperationResults;
do {
prevResult = currentResult;
currentResult = currentResult.nextOperationResult;
} while (currentResult != null);
prevResult.nextOperationResult = result;
this.log.debug("Queued additional operation to result list: " + result.operationName);
}
}
/**
* Use this method to create files and optionally push them onto the files created stack that will
* be referenced when an undo is required.
*
* @param {string} filePath
* @param {string} message: the error message to print.
* @param {boolean} saveFileNameForUndo: Whether you want to keep track of the files created for undo
* @param {boolean} isDir: if this is a file or directory
*/
createFile(filePath, message, saveFileNameForUndo, isDir) {
this.log.debug("Operation: " + this.operationName + " creating file/dir: " + filePath);
try {
if (isDir) {
fs.mkdirSync(filePath);
}
else {
fs.closeSync(fs.openSync(filePath, "w"));
}
this.mAllFilesCreated.push(filePath);
if (saveFileNameForUndo) {
this.mFilesForUndo.push(filePath);
}
}
catch (error) {
const msg = "An error occurred creating file: " + filePath +
" during operation: " + this.operationResult.operationName;
this.log.error(message);
this.log.error(msg);
throw new Error(msg);
}
}
/**
* Method to print all the files created by the file creator service.
*/
printFilesCreatedList() {
this.log.debug(this.allFilesCreated.length + " files created during "
+ this.operationResult.operationName);
for (let x = 0; x < this.allFilesCreated.length; x++) {
this.log.debug("File: " + this.mAllFilesCreated[x]);
}
}
/**
* Add a file created by the operation to the list of all files created and optionally mark this file
* as undoable, which will cause the undo operation to attempt to remove the file or directory.
*
* @param {boolean} fileUndoable: The file or directory should be automatically removed by the undo operation.
* @param {string} file: The file or directory created.
*/
addFileCreated(fileUndoable, file) {
this.log.debug("Adding files created: %s", file);
this.mAllFilesCreated.push(file);
if (fileUndoable) {
this.mFilesForUndo.push(file);
}
}
/**
* Method that is called after the implementing classes undo to delete all the files that have been
* saved during the operation (on the mFilesForUndo list).
*/
deleteAllFilesMarkedForUndo() {
this.log.info("Cleaning all files and directories marked as 'undo-able'.");
const order = [];
/**
* Reverse the order that the files/directories were created (in-case they are nested)
*/
for (let x = this.fileToUndo.length - 1; x > -1; x--) {
order.push(this.mFilesForUndo[x]);
}
for (let x = 0; x < order.length; x++) {
this.log.info("Cleaning file: " + this.fileToUndo[x]);
try {
fs.rmSync(order[x], { recursive: true, force: true });
}
catch (error) {
this.log.error("An error occurred deleting: " + order[x]);
this.log.error("Message: " + error.message);
}
}
}
/**
* This method logs a standard header for the operation
*/
logBeginMessage() {
const seperator = Operation.outputSeparator;
const text = "***Operation: " + this.mOperationResult.operationName + " is starting ";
const appendlength = seperator.length - text.length;
let append = "";
if (appendlength > 0) {
for (let x = 0; x < appendlength; x++) {
append += "*";
}
}
this.log.debug(Operation.outputSeparator);
this.log.debug(text + append);
this.log.debug(Operation.outputSeparator);
this.logOperationBeginMessages();
}
/**
* This method logs a standard header for the operation
*/
logEndMessage() {
this.log.debug("Operation: " + this.mOperationResult.operationName + " has ended.");
if (this.mOperationResult.operationFailed) {
this.log.error("Operation " + this.mOperationResult.operationName + " has failed.");
}
else {
this.log.debug("Operation has succeeded.");
}
}
/**
* Operation complete callback method. The operation must invoke the callback to indicate that it
* has finished. If it does not, the operation sequence will not continue.
*/
operationComplete(output) {
/**
* Set the status of the operation based on whether or not it failed.
*/
if (this.mOperationResult.operationFailed) {
this.mStageName = TaskStage_1.TaskStage.FAILED;
/**
* If the operation is marked as critical, then we cannot continue the set of operations
*/
if (this.mOperationResult.critical) {
this.mOperationResult.continuePath = false;
}
this.log.error("Operation: " + this.mOperationResult.operationName + " has failed.");
this.log.error("Will attempt undo operations where applicable.");
}
else {
this.mStageName = TaskStage_1.TaskStage.COMPLETE;
}
/**
* Add the result of this operation to the operation results chain.
*/
this.addResult(this.operationResult);
this.logEndMessage();
// /**
// * Determine if this operation has chosen to diverge to an arbitrary operation
// *
// * An operation can diverge to any operation it chooses, and the result is chained
// */
// if (this.mOperationResult.diverge && this.mOperationResult.continuePath) {
// this.addResult(this.mOperationResult.divergeTo.executeOperation());
// }
/**
* If the operation failed and undo is possible, attempt the undo now. Operations must be undone
* in the LIFO order to effectively rollback any changes.
*/
if (this.mOperationResult.operationFailed && this.mOperationResult.operationUndoPossible) {
this.performUndo(this.undoComplete.bind(this));
}
/**
* Invoke the callers operation complete callback if we are not performing an operation undo.
*/
if (!this.mOperationResult.operationUndoAttempted) {
this.log.debug("Calling operation complete callback for: " + this.mOperationResult.operationName);
this.mOperationCompleteCallback(output, this.mOperationResult);
}
}
/**
* The operation undo complete callback method, this must be called by the operations undo method
* in order for the operation 'undos' to continue.
*/
undoComplete() {
this.log.debug("Calling operation complete callback after undo: "
+ this.mOperationResult.operationName);
this.mOperationCompleteCallback(null, this.mOperationResult);
}
}
exports.Operation = Operation;
Operation.NO_PARMS = null;
Operation.NO_OUTPUT = null;
/**
* Traverses the results chain and prints results and gives overall failure status
*/
Operation.outputSeparator = "*******************************************************************";
//# sourceMappingURL=Operation.js.map