- automation-worker-code: getFormFields возвращает поле code (не fieldCode) — поиск поля всегда завершался ошибкой 'Field ... not found', ошибка терялась - automation.routes: результаты runAutomationsByTrigger (логи, ошибки) теперь пишутся в консоль сервиса с префиксом [automation]
129 lines
5.2 KiB
TypeScript
129 lines
5.2 KiB
TypeScript
/**
|
|
* Automation worker code as a CommonJS string.
|
|
*
|
|
* Passed to `new Worker(AUTOMATION_WORKER_CODE, { eval: true })` so that the
|
|
* worker runs in any environment (dev with tsx, production dist/) without
|
|
* requiring a separate file to be present on disk.
|
|
*
|
|
* The source-of-truth logic lives here. esbuild inlines this string into the
|
|
* bundle when the server is built for production.
|
|
*/
|
|
export const AUTOMATION_WORKER_CODE = `
|
|
'use strict';
|
|
const { workerData, parentPort } = require('node:worker_threads');
|
|
const vm = require('vm');
|
|
|
|
if (!parentPort) throw new Error('Must be run as a Worker Thread');
|
|
|
|
const { code, organizationId, triggerData } = workerData;
|
|
const logs = [];
|
|
let callId = 0;
|
|
const pending = new Map();
|
|
|
|
parentPort.on('message', function(msg) {
|
|
if (msg.type !== 'storage-response') return;
|
|
const p = pending.get(msg.callId);
|
|
if (!p) return;
|
|
pending.delete(msg.callId);
|
|
if (msg.error !== undefined) {
|
|
p.reject(new Error(msg.error));
|
|
} else {
|
|
p.resolve(msg.result);
|
|
}
|
|
});
|
|
|
|
function callStorage(method, args) {
|
|
return new Promise(function(resolve, reject) {
|
|
const id = ++callId;
|
|
pending.set(id, { resolve, reject });
|
|
parentPort.postMessage({ type: 'storage-call', callId: id, method, args });
|
|
});
|
|
}
|
|
|
|
const ctx = {
|
|
organizationId,
|
|
triggerData,
|
|
result: undefined,
|
|
log: function() {
|
|
const args = Array.prototype.slice.call(arguments);
|
|
logs.push(args.map(function(a) {
|
|
return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
|
|
}).join(' '));
|
|
},
|
|
forms: {
|
|
list: function() { return callStorage('getFormsByOrganization', [organizationId]); },
|
|
get: function(formId) { return callStorage('getForm', [formId, organizationId]); },
|
|
getFields: function(formId) { return callStorage('getFormFields', [formId, organizationId]); },
|
|
getStatuses: function(formId) { return callStorage('getFormStatuses', [formId, organizationId]); },
|
|
},
|
|
users: {
|
|
get: function(userId) { return callStorage('getUser', [userId]); },
|
|
list: function() { return callStorage('getUsersByOrganization', [organizationId]); },
|
|
update: function(userId, updates) { return callStorage('updateUser', [userId, updates]); },
|
|
updateStatus: function(userId, statusId) { return callStorage('updateUser', [userId, { statusId }]); },
|
|
setFieldValue: function(userId, fieldCodeOrId, value) {
|
|
// taskId подставляем из данных триггера, чтобы оценки history-number были привязаны к задаче
|
|
const taskId = triggerData && typeof triggerData === 'object' && typeof triggerData.taskId === 'number'
|
|
? triggerData.taskId
|
|
: null;
|
|
return callStorage('setUserProfileFieldValueFromAutomation', [userId, fieldCodeOrId, value, organizationId, taskId]);
|
|
},
|
|
recalcFieldAverage: function(userId, fieldCodeOrId) {
|
|
return callStorage('recalcUserFieldAverageFromAutomation', [userId, fieldCodeOrId, organizationId]);
|
|
},
|
|
},
|
|
tasks: {
|
|
list: function(formId) { return callStorage('getTasksByForm', [formId, organizationId]); },
|
|
get: function(taskId) { return callStorage('getTask', [taskId, organizationId]); },
|
|
create: function(data) {
|
|
return callStorage('createTask', [Object.assign({}, data, { organizationId })]);
|
|
},
|
|
update: function(taskId, updates) {
|
|
return callStorage('updateTask', [taskId, organizationId, updates]);
|
|
},
|
|
delete: function(taskId) {
|
|
return callStorage('deleteTask', [taskId, organizationId]);
|
|
},
|
|
getFieldValues: function(taskId) {
|
|
return callStorage('getTaskFieldValues', [taskId, organizationId]);
|
|
},
|
|
getAssignees: function(taskId) {
|
|
return callStorage('getTaskAssignees', [taskId, organizationId]);
|
|
},
|
|
setFieldValue: async function(taskId, fieldCode, value) {
|
|
const fields = await callStorage('getTaskFieldValues', [taskId, organizationId]);
|
|
const task = await callStorage('getTask', [taskId, organizationId]);
|
|
if (!task) throw new Error('Task ' + taskId + ' not found');
|
|
const formFields = await callStorage('getFormFields', [task.formId, organizationId]);
|
|
const field = formFields.find(function(f) { return (f.fieldCode || f.code) === fieldCode; });
|
|
if (!field) throw new Error('Field ' + fieldCode + ' not found');
|
|
const existing = fields.find(function(v) { return v.fieldId === field.id; });
|
|
if (existing) {
|
|
return callStorage('updateTaskFieldValue', [taskId, field.id, organizationId, { value: String(value) }]);
|
|
} else {
|
|
return callStorage('createTaskFieldValue', [{ taskId, fieldId: field.id, value: String(value) }]);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
async function run() {
|
|
try {
|
|
const wrappedCode = '(async () => { ' + code + ' })()';
|
|
const sandbox = vm.createContext({
|
|
ctx,
|
|
console: { log: ctx.log, error: ctx.log, warn: ctx.log },
|
|
});
|
|
const script = new vm.Script(wrappedCode);
|
|
await script.runInContext(sandbox, { timeout: 5000 });
|
|
parentPort.postMessage({ type: 'done', logs, result: ctx.result });
|
|
} catch (err) {
|
|
const message = err && err.message ? err.message : String(err);
|
|
logs.push('ERROR: ' + message);
|
|
parentPort.postMessage({ type: 'done', logs, error: message });
|
|
}
|
|
}
|
|
|
|
run();
|
|
`;
|