import eventHandler from '../../../frontend/backend-api/event-system/EventHandler.js';
import store from './store.js';
import logger from '../logging.js';
import {
saveProcess,
deleteProcess,
getUserTaskIds,
getUserTaskHTML,
getUserTasksHTML,
saveUserTaskHTML,
deleteUserTaskHTML,
getBPMN,
updateProcess as overwriteProcess,
getUpdatedProcessesJSON,
} from './fileHandling.js';
import helperEx from '../../../shared-frontend-backend/helpers/javascriptHelpers.js';
const { mergeIntoObject } = helperEx;
import processHelperEx from '../../../shared-frontend-backend/helpers/processHelpers.js';
const { getProcessInfo } = processHelperEx;
export let processMetaObjects = {};
/**
* Returns all known processes in form of an array
*
* @returns {Array} - array containing all known processes
*/
export function getProcesses() {
return Object.values(processMetaObjects);
}
/**
* Throws if process with given id doesn't exist
*
* @param {String} processDefinitionsId
*/
export function checkIfProcessExists(processDefinitionsId) {
if (!processMetaObjects[processDefinitionsId]) {
throw new Error(`Process with id ${processDefinitionsId} does not exist!`);
}
}
/**
* Handles adding a process, makes sure all necessary information gets parsed from bpmn
*
* @param {String} bpmn the xml description of the process to create
* @returns {Object} - returns an object containing the intitial process information
*/
export async function addProcess(processData) {
const { bpmn } = processData;
delete processData.bpmn;
if (!bpmn) {
throw new Error("Can't create a process without a bpmn!");
}
const date = new Date().toUTCString();
// create meta info object
const metadata = {
inEditingBy: [],
departments: [],
variables: [],
createdOn: date,
lastEdited: date,
type: 'process',
...processData,
...(await getProcessInfo(bpmn)),
};
const { id: processDefinitionsId } = metadata;
// check if there is an id collision
if (processMetaObjects[processDefinitionsId]) {
throw new Error('Tried to add process with id of an existing process!');
}
// save process info
processMetaObjects[processDefinitionsId] = metadata;
// write meta data to store
store.add('processes', removeExcessiveInformation(metadata));
// save bpmn
await saveProcess(processDefinitionsId, bpmn);
eventHandler.dispatch('processAdded', { process: metadata });
return metadata;
}
/**
* Updates an existing process with the given bpmn
*
* @param {String} processDefinitionsId
* @param {String} newBpmn
* @returns {Object} - contains the new process meta information
*/
export async function updateProcess(processDefinitionsId, newInfo) {
checkIfProcessExists(processDefinitionsId);
const { bpmn: newBpmn } = newInfo;
delete newInfo.bpmn;
let metaChanges = {
...newInfo,
};
if (newBpmn) {
// get new info from bpmn
metaChanges = {
...metaChanges,
...(await getProcessInfo(newBpmn)),
};
}
const newMetaData = await updateProcessMetaData(processDefinitionsId, metaChanges);
if (newBpmn) {
await overwriteProcess(processDefinitionsId, newBpmn);
eventHandler.dispatch('backend_processXmlChanged', {
definitionsId: processDefinitionsId,
newXml: newBpmn,
});
}
return newMetaData;
}
/**
* Direct updates to process meta data, should mostly be used for internal changes (puppeteer client, electron) to avoid
* parsing the bpmn unnecessarily
*
* @param {Object} processDefinitionsId
* @param {Object} metaChanges contains the elements to change and their new values
*/
export async function updateProcessMetaData(processDefinitionsId, metaChanges) {
checkIfProcessExists(processDefinitionsId);
const { id: newId } = metaChanges;
if (newId && processDefinitionsId !== newId) {
throw new Error(`Illegal try to change id from ${processDefinitionsId} to ${newId}`);
}
const newMetaData = {
...processMetaObjects[processDefinitionsId],
lastEdited: new Date().toUTCString(),
};
mergeIntoObject(newMetaData, metaChanges, true, true, true);
// add shared_with if process is shared
if (metaChanges.shared_with) {
newMetaData.shared_with = metaChanges.shared_with;
}
// remove shared_with if not shared anymore
if (newMetaData.shared_with && metaChanges.shared_with && metaChanges.shared_with.length === 0) {
delete newMetaData.shared_with;
}
processMetaObjects[processDefinitionsId] = newMetaData;
store.update('processes', processDefinitionsId, removeExcessiveInformation(newMetaData));
eventHandler.dispatch('processUpdated', {
oldId: processDefinitionsId,
updatedInfo: newMetaData,
});
return newMetaData;
}
/**
* Removes an existing process
*
* @param {String} processDefinitionsId
*/
export async function removeProcess(processDefinitionsId) {
if (!processMetaObjects[processDefinitionsId]) {
return;
}
// remove process directory
await deleteProcess(processDefinitionsId);
// remove from store
store.remove('processes', processDefinitionsId);
delete processMetaObjects[processDefinitionsId];
eventHandler.dispatch('processRemoved', { processDefinitionsId });
}
/**
* Removes information from the meta data that would not be correct after a restart
*
* @param {Object} processInfo the complete process meta information
*/
function removeExcessiveInformation(processInfo) {
const newInfo = { ...processInfo };
delete newInfo.inEditingBy;
return newInfo;
}
/**
* Returns the process definition for the process with the given id
*
* @param {String} processDefinitionsId
* @returns {String} - the process definition
*/
export async function getProcessBpmn(processDefinitionsId) {
checkIfProcessExists(processDefinitionsId);
try {
const bpmn = await getBPMN(processDefinitionsId);
return bpmn;
} catch (err) {
logger.debug(`Error reading bpmn of process. Reason:\n${err}`);
throw new Error('Unable to find process bpmn!');
}
}
/**
* Returns the filenames of html data for all user tasks in the given process
*
* @param {String} processDefinitionsId
* @returns {Array} - array containing the filenames of the htmls of all user tasks in the process
*/
export async function getProcessUserTasks(processDefinitionsId) {
checkIfProcessExists(processDefinitionsId);
try {
const userTaskIds = await getUserTaskIds(processDefinitionsId);
return userTaskIds;
} catch (err) {
logger.debug(`Error reading user task ids. Reason:\n${err}`);
throw new Error('Unable to read user task filenames');
}
}
/**
* Returns the html for a specific user task in a process
*
* @param {String} processDefinitionsId
* @param {String} taskFileName
* @returns {String} - the html under the given fileName
*/
export async function getProcessUserTaskHtml(processDefinitionsId, taskFileName) {
checkIfProcessExists(processDefinitionsId);
try {
const userTaskHtml = await getUserTaskHTML(processDefinitionsId, taskFileName);
return userTaskHtml;
} catch (err) {
logger.debug(`Error getting html of user task. Reason:\n${err}`);
throw new Error('Unable to get html for user task!');
}
}
/**
* Return object mapping from user tasks fileNames to their html
*
* @param {String} processDefinitionsId
* @returns {Object} - contains the html for all user tasks in the process
*/
export async function getProcessUserTasksHtml(processDefinitionsId) {
checkIfProcessExists(processDefinitionsId);
try {
const userTasksHtml = await getUserTasksHTML(processDefinitionsId);
return userTasksHtml;
} catch (err) {
logger.debug(`Error getting user task html. Reason:\n${err}`);
throw new Error('Failed getting html for all user tasks');
}
}
export async function saveProcessUserTask(processDefinitionsId, userTaskFileName, html) {
checkIfProcessExists(processDefinitionsId);
try {
await saveUserTaskHTML(processDefinitionsId, userTaskFileName, html);
eventHandler.dispatch('backend_processTaskHtmlChanged', {
processDefinitionsId,
userTaskFileName,
html,
});
} catch (err) {
logger.debug(`Error storing user task data. Reason:\n${err}`);
throw new Error('Failed to store the user task data');
}
}
/**
* Removes a stored user task from disk
*
* @param {String} processDefinitionsId
* @param {String} userTaskFileName
*/
export async function deleteProcessUserTask(processDefinitionsId, userTaskFileName) {
checkIfProcessExists(processDefinitionsId);
try {
await deleteUserTaskHTML(processDefinitionsId, userTaskFileName);
eventHandler.dispatch('backend_processTaskHtmlChanged', {
processDefinitionsId,
userTaskFileName,
});
} catch (err) {
logger.debug(`Error removing user task html. Reason:\n${err}`);
}
}
/**
* Stores the id of the socket wanting to block the process from being deleted inside the process object
*
* @param {String} socketId
* @param {String} processDefinitionsId
*/
export function blockProcess(socketId, processDefinitionsId) {
checkIfProcessExists(processDefinitionsId);
const process = { ...processMetaObjects[processDefinitionsId] };
const blocker = { id: socketId, task: null };
let { inEditingBy } = process;
if (!inEditingBy) {
inEditingBy = [blocker];
} else {
const existingBlocker = inEditingBy.find((b) => b.id == blocker.id);
if (!existingBlocker) {
inEditingBy.push(blocker);
}
}
updateProcessMetaData(processDefinitionsId, { inEditingBy });
}
/**
* Removes the id of the socket wanting to unblock the process from the process object
*
* @param {String} socketId
* @param {String} processDefinitionsId
*/
export function unblockProcess(socketId, processDefinitionsId) {
checkIfProcessExists(processDefinitionsId);
const process = processMetaObjects[processDefinitionsId];
if (!process.inEditingBy) {
return;
}
const inEditingBy = process.inEditingBy.filter((blocker) => blocker.id !== socketId);
updateProcessMetaData(processDefinitionsId, { inEditingBy });
}
export function blockTask(socketId, processDefinitionsId, taskId) {
checkIfProcessExists(processDefinitionsId);
const process = processMetaObjects[processDefinitionsId];
if (!process.inEditingBy) {
return;
}
let blocker = process.inEditingBy.find((b) => b.id === socketId);
let { inEditingBy } = process;
if (!blocker) {
blocker = { id: socketId, task: taskId };
inEditingBy.push(blocker);
} else {
blocker.task = taskId;
}
updateProcessMetaData(processDefinitionsId, { inEditingBy });
}
export function unblockTask(socketId, processDefinitionsId, taskId) {
checkIfProcessExists(processDefinitionsId);
const process = processMetaObjects[processDefinitionsId];
if (!process.inEditingBy) {
return;
}
let blocker = process.inEditingBy.find((b) => b.id === socketId);
if (blocker && blocker.task === taskId) {
blocker.task = null;
updateProcessMetaData(processDefinitionsId, { inEditingBy: process.inEditingBy });
}
}
/**
* initializes the process meta information objects
*/
export async function init() {
processMetaObjects = {};
// get processes that were persistently stored
const storedProcesses = store.get('processes');
const updatedProcesses = await getUpdatedProcessesJSON(storedProcesses);
store.set('processes', 'processes', updatedProcesses);
const processes = updatedProcesses.map((uP) => ({ ...uP, inEditingBy: [] }));
processes.forEach((process) => (processMetaObjects[process.id] = process));
}
init();