const { data } = require('@proceed/system');
const {
getDefinitionsName,
getDefinitionsId,
getDeploymentMethod,
getProcessIds,
} = require('@proceed/bpmn-helper');
const { validateProcess } = require('./processInfoValidation.js');
const prefix = 'process_';
/**
* Function that filters the User Tasks HTML fileNames from the general process information object
*
* @param {Object} processInfo contains all known information for a process
* @param {Boolean} imported indicates if we want the HTML for the process or for the processes imported by the process
* @returns {Array} - array containing the fileNames for all User-Task data we have
*/
function filterHTML(processInfo, imported) {
let prefix = 'html_';
if (imported) {
prefix = `import_${prefix}`;
}
return Object.keys(processInfo)
.filter((key) => key.startsWith(prefix))
.map((key) => key.substring(prefix.length, key.length));
}
/**
* Function that filters the imported process descripions from the general process information object
*
* @param {Object} processInfo contains all known information for a process
* @returns {Object} - mapping from definitionId of imported process definition to actual definition
*/
function filterImportedProcesses(processInfo) {
return Object.keys(processInfo)
.filter((key) => key.startsWith('import_'))
.filter((key) => !key.includes('html_'))
.reduce((curr, key) => {
const importedDefinitionId = key.substring(7, key.length);
curr[importedDefinitionId] = processInfo[key];
return curr;
}, {});
}
module.exports = {
/**
* Checks if the file with process information exists
*
* @param {String} definitionId name of the file the definition of the process is stored in
* @returns {Boolean} - indicates if the file exists or not
*/
async isProcessExisting(definitionId) {
const result = await data.read(`processes/${definitionId}`);
if (result === null) {
return false;
}
return true;
},
/**
* Returns true if the process is valid and complete
* e.g. there are definitions for all imported processes
*
* @param {String} definitionId name of the file the definition of the process is stored in
* @returns {Boolean} - indicates if the process is valid and can be executed
*/
async isProcessValid(definitionId) {
//check if the process was already validated, yes? => just return true
const process = await data.read(definitionId);
if (process.validated && JSON.parse(process.validated)) {
return true;
}
// get all known assets for the process
const knownUserTaskFiles = filterHTML(process, false);
const knownImportedUserTaskFiles = filterHTML(process, true);
const knownImports = filterImportedProcesses(process);
// assert if we have all required assets for the process to be executed
const result = await validateProcess(
process.bpmn,
knownUserTaskFiles,
knownImports,
knownImportedUserTaskFiles
);
// set flag in the process file that the validity check was done and succesful
if (result === true) {
await data.write(`${definitionId}/validated`, JSON.stringify(true));
}
return result;
},
/**
* Saves a process definition under the given definitionid with some metadata
*
* @param {String} definitionId
* @param {String} bpmn the process definition
*/
async saveProcessDefinition(definitionId, bpmn) {
const processIds = await getProcessIds(bpmn);
if (processIds.length > 1) {
throw new Error('Only process definitions containing one process allowed!');
}
await data.write(`${definitionId}/bpmn`, bpmn);
await data.write(`${definitionId}/validated`, JSON.stringify(false));
await data.write(`${definitionId}/deploymentDate`, JSON.stringify(Date.now()));
await data.write(`processes/${definitionId}`, JSON.stringify(processIds[0]));
},
/**
* Get list with all processes known to this machine
*
* @returns {Object} - mapping from definitionIds to the process ids of the processes contained in the files
*/
async getAllProcesses() {
const processes = await data.read('processes');
return processes || {};
},
/**
* Gets the BPMN of the process
*
* @param {String} definitionId
* @returns {String} - the process definition
*/
async getProcess(definitionId) {
if (!(await this.isProcessExisting(definitionId))) {
throw new Error('Process with given definitionId does not exist!');
}
const process = await data.read(`${definitionId}/bpmn`);
return process;
},
/**
* Gets the definition and additional information (e.g. date of deployment, id, name, method of deployment) for a process
*
* @param {String} processDefinitionId
* @returns {Object} - { bpmn, deploymentDate, definitionId, definitionName, deploymentMethod }
*/
async getProcessInfo(processDefinitionId) {
const process = await data.read(`${processDefinitionId}`);
if (!process) {
throw new Error('Process with given definitionId does not exist!');
}
const { bpmn, deploymentDate } = {
bpmn: process.bpmn,
deploymentDate: JSON.parse(process.deploymentDate),
};
const definitionId = await getDefinitionsId(bpmn);
const definitionName = await getDefinitionsName(bpmn);
const deploymentMethod = await getDeploymentMethod(bpmn);
return { bpmn, deploymentDate, definitionId, definitionName, deploymentMethod };
},
/**
* Removes the information stored about a process
*
* @param {String} definitionId
*/
async deleteProcess(definitionId) {
await data.delete(definitionId);
await data.delete(`processes/${definitionId}`);
},
/**
* Saves the definition about a process imported by another process in the file of the importing process
*
* @param {String} definitionId name of the file the process that is importing is stored in
* @param {String} importedDefinitionId name of the file the imported process definition would be stored under
* @param {String} importedBpmn process definition of the imported process
*/
async saveImportedProcessDefinition(definitionId, importedDefinitionId, importedBpmn) {
if (!importedBpmn) {
throw new Error('Process definition content must not be empty!');
}
await data.write(`${definitionId}/import_${importedDefinitionId}`, importedBpmn);
},
/**
* Get all processes imported by the process stored in the file with the given name
*
* @param {String} definitionId
* @returns {Object} - mapping from imported process definitionids to the definitions of the processes
*/
async getImportedProcesses(definitionId) {
const process = await data.read(`${definitionId}`);
const importedProcesses = filterImportedProcesses(process);
return importedProcesses;
},
/**
* Gets a specific imported process with the given definitionId for the importing process with the given definitionId
*
* @param {String} definitionId
* @param {String} importedDefinitionId
* @returns {String} - the definition of the imported process
*/
async getImportedProcess(definitionId, importedDefinitionId) {
const importedProcesses = await this.getImportedProcesses(definitionId);
if (!importedProcesses) {
return;
}
return importedProcesses[importedDefinitionId];
},
/**
* Saves the HTML for a specific user task in a specific process stored in the file with the given definitionId
*
* @param {String} definitionId
* @param {String} fileName the fileName as given in the proceed:fileName attribute of the user task
* @param {String} html
* @param {Boolean} imported indicator if the user task is located in a process imported by the main process
*/
async saveHTMLString(definitionId, fileName, html, imported) {
if (!(await this.isProcessExisting(definitionId))) {
throw new Error('Process with given ID does not exist!');
}
if (!html) {
throw new Error('HTML content must not be empty!');
}
let prefix = 'html_';
if (imported) {
prefix = `import_${prefix}`;
}
await data.write(`${definitionId}/${prefix}${fileName}`, html);
},
/**
* Gets the html for a specific user task in the process stored under the given definitionId
*
* @param {String} definitionId
* @param {String} fileName the fileName as given in the proceed:fileName attribute of the user task
* @param {Boolean} imported indicator if the user task is located in a process imported by the main process
*/
async getHTML(definitionId, fileName, imported) {
let prefix = 'html_';
if (imported) {
prefix = `import_${prefix}`;
}
const html = await data.read(`${definitionId}/${prefix}${fileName}`);
if (!html) {
throw new Error("No HTML found. Either the process or the html doesn't seem to exist.");
}
return html;
},
/**
* Get the fileNames for all the user task data given for the process stored under the given definitionId
*
* @param {String} definitionId
* @param {String} imported indicates if we want to know the user tasks in the imported processes or the ones in the main process
* @returns {Array} - array containing the ids for all (imported) user tasks
*/
async getAllUserTasks(definitionId, imported) {
// TODO: use asterisk notation for html ids
const process = await data.read(`${definitionId}`);
if (!process) {
throw new Error('Process with given ID does not exist!');
}
const htmlFileNames = filterHTML(process, imported);
return htmlFileNames;
},
/**
* Stores the instance information for process instances that ended to make them available even after restarting the engine
*
* @param {String} definitionId
* @param {String} instanceInfo
*/
async archiveInstance(definitionId, instanceId, instanceInfo) {
data.write(`${definitionId}/archived_instance_${instanceId}`, JSON.stringify(instanceInfo));
},
async getArchivedInstances(definitionId) {
const process = await data.read(definitionId);
if (!process) {
throw new Error('Process with given ID does not exist!');
}
return Object.keys(process)
.filter((key) => key.startsWith('archived_instance_'))
.reduce((curr, key) => {
const instanceId = key.substring(18, key.length);
curr[instanceId] = JSON.parse(process[key]);
return curr;
}, {});
},
/**
* Adds an attribute to process data
*
* @param {String} definitionId id of the process to add the attribute to
* @param {String} key key under which the attribute is supposed to be stored
* @param {String} value attribute value
*/
async addProcessAttribute(definitionId, key, value) {
await data.write(`${definitionId}/${key}`, value);
},
/**
* Returns the value of a specific process attribute
*
* @param {String} definitionId id of the process
* @param {String} key key under which the attribute is supposed to be stored
* @returns {String} the attribute value
*/
async getProcessAttribute(definitionId, key) {
return await data.read(`${definitionId}/${key}`);
},
};