data-primals-engine 1.2.6-rc3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2
- package/client/package-lock.json +247 -4354
- package/client/package.json +16 -15
- package/client/src/App.jsx +13 -20
- package/client/src/App.scss +1 -0
- package/client/src/AssistantChat.jsx +5 -4
- package/client/src/DataEditor.jsx +2 -2
- package/client/src/DataTable.jsx +47 -3
- package/client/src/ExportDialog.jsx +2 -2
- package/client/src/Field.jsx +6 -18
- package/client/src/KanbanCard.jsx +4 -2
- package/client/src/KanbanConfigModal.jsx +5 -7
- package/client/src/ModelCreatorField.jsx +9 -9
- package/client/src/PackGallery.jsx +89 -9
- package/client/src/PackGallery.scss +58 -4
- package/client/src/RTETrans.jsx +11 -0
- package/client/src/RelationValue.jsx +3 -4
- package/client/src/constants.js +1 -1
- package/client/src/core/data.js +2 -1
- package/client/src/translations.js +80 -0
- package/package.json +8 -1
- package/server.js +4 -4
- package/src/constants.js +6 -0
- package/src/defaultModels.js +23 -10
- package/src/filter.js +35 -5
- package/src/i18n.js +1 -1
- package/src/modules/{assistant.js → assistant/assistant.js} +42 -27
- package/src/modules/assistant/constants.js +16 -0
- package/src/modules/data/data.core.js +1 -3
- package/src/modules/data/data.js +4601 -4525
- package/src/modules/data/data.routes.js +29 -3
- package/src/modules/mongodb.js +3 -1
- package/src/modules/user.js +12 -1
- package/src/modules/workflow.js +198 -117
- package/src/packs.js +1015 -9
- package/src/services/index.js +11 -0
- package/src/services/stripe.js +141 -0
- package/test/data.integration.test.js +66 -3
- package/test/workflow.actions.integration.test.js +474 -0
- package/test/workflow.integration.test.js +1 -1
package/src/modules/workflow.js
CHANGED
|
@@ -6,8 +6,8 @@ import crypto from "node:crypto";
|
|
|
6
6
|
import ivm from 'isolated-vm';
|
|
7
7
|
|
|
8
8
|
import {Logger} from "../gameObject.js";
|
|
9
|
-
import {deleteData, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
|
|
10
|
-
import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
|
|
9
|
+
import {deleteData, getModel, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
|
|
10
|
+
import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps, port} from "../constants.js";
|
|
11
11
|
import {ChatOpenAI} from "@langchain/openai";
|
|
12
12
|
import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
|
|
13
13
|
import {ChatPromptTemplate} from "@langchain/core/prompts";
|
|
@@ -16,7 +16,13 @@ import i18n from "../../src/i18n.js";
|
|
|
16
16
|
import {sendEmail} from "../email.js";
|
|
17
17
|
|
|
18
18
|
import * as workflowModule from './workflow.js';
|
|
19
|
-
import
|
|
19
|
+
import {isConditionMet} from "../filter.js";
|
|
20
|
+
import { services } from '../services/index.js';
|
|
21
|
+
import {getEnv} from "./user.js";
|
|
22
|
+
import {getHost} from "../constants.js";
|
|
23
|
+
import {providers} from "./assistant/constants.js";
|
|
24
|
+
import {ChatAnthropic} from "@langchain/anthropic";
|
|
25
|
+
import {getAIProvider} from "./assistant/assistant.js";
|
|
20
26
|
|
|
21
27
|
let logger = null;
|
|
22
28
|
export async function onInit(defaultEngine) {
|
|
@@ -25,6 +31,51 @@ export async function onInit(defaultEngine) {
|
|
|
25
31
|
await scheduleWorkflowTriggers();
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Déclenche un workflow par son nom et lui passe des données de contexte.
|
|
38
|
+
* C'est la fonction clé à exposer aux endpoints pour lancer des processus métier.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} name - Le nom du workflow à exécuter.
|
|
41
|
+
* @param {object} data - Les données à injecter dans context.triggerData.
|
|
42
|
+
* @param {object} user - L'objet utilisateur qui initie l'action.
|
|
43
|
+
* @returns {Promise<{success: boolean, message?: string, runId?: ObjectId}>}
|
|
44
|
+
*/
|
|
45
|
+
export async function runWorkflowByName(name, data, user) {
|
|
46
|
+
if (!name) {
|
|
47
|
+
return { success: false, message: "Workflow name is required." };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dbCollection = await getCollectionForUser(user);
|
|
51
|
+
|
|
52
|
+
// 1. Trouver la définition du workflow par son nom
|
|
53
|
+
const workflowDefinition = await dbCollection.findOne({ _model: 'workflow', name });
|
|
54
|
+
|
|
55
|
+
if (!workflowDefinition) {
|
|
56
|
+
const msg = `Workflow with name "${name}" not found.`;
|
|
57
|
+
logger.error(`[runWorkflowByName] ${msg}`);
|
|
58
|
+
return { success: false, message: msg };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Créer le document workflowRun
|
|
62
|
+
const workflowRunData = {
|
|
63
|
+
_model: 'workflowRun',
|
|
64
|
+
_user: user._user || user.username,
|
|
65
|
+
workflow: workflowDefinition._id,
|
|
66
|
+
contextData: { triggerData: data }, // Les données passées deviennent le triggerData
|
|
67
|
+
status: 'pending',
|
|
68
|
+
startedAt: new Date()
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const insertResult = await dbCollection.insertOne(workflowRunData);
|
|
72
|
+
logger.info(`[runWorkflowByName] Created workflowRun ${insertResult.insertedId} for workflow "${name}".`);
|
|
73
|
+
|
|
74
|
+
// 3. Lancer le traitement de manière asynchrone
|
|
75
|
+
await processWorkflowRun(insertResult.insertedId, user);
|
|
76
|
+
|
|
77
|
+
return { success: true, runId: insertResult.insertedId };
|
|
78
|
+
}
|
|
28
79
|
/**
|
|
29
80
|
* Exécute une fonction de manière sécurisée en s'assurant qu'une seule instance
|
|
30
81
|
* s'exécute à la fois, grâce à un système de verrouillage distribué basé sur la base de données.
|
|
@@ -218,13 +269,23 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
218
269
|
return new ivm.ExternalCopy(result.data?.[0] || null).copyInto();
|
|
219
270
|
};
|
|
220
271
|
|
|
221
|
-
// 1. Build the sandboxed API
|
|
222
|
-
await jail.set('
|
|
272
|
+
// 1. Build the sandboxed API methods
|
|
273
|
+
await jail.set('_workflow_run', new ivm.Reference(async (name, contextData) => {
|
|
274
|
+
const result = await runWorkflowByName(name, JSON.parse(contextData), user);
|
|
275
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
276
|
+
}));
|
|
277
|
+
await jail.set('_db_create', new ivm.Reference(async (modelName, dataObject) => {
|
|
278
|
+
const result = await insertData(modelName, JSON.parse(dataObject), {}, user, false);
|
|
279
|
+
if (result.success && result.insertedIds) {
|
|
280
|
+
result.insertedIds = result.insertedIds.map(id => id.toString());
|
|
281
|
+
}
|
|
282
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
283
|
+
}));
|
|
223
284
|
await jail.set('_db_find', new ivm.Reference(find));
|
|
224
285
|
await jail.set('_db_findOne', new ivm.Reference(findOne));
|
|
225
286
|
|
|
226
|
-
await jail.set('_db_update', new ivm.Reference((modelName, filter, updateObject) => patchData(modelName, JSON.parse(filter), JSON.parse(updateObject), {}, user, false)));
|
|
227
|
-
await jail.set('_db_delete', new ivm.Reference((modelName, filter) => deleteData(modelName, JSON.parse(filter), user, false)));
|
|
287
|
+
await jail.set('_db_update', new ivm.Reference(async (modelName, filter, updateObject) => patchData(modelName, JSON.parse(filter), JSON.parse(updateObject), {}, user, false)));
|
|
288
|
+
await jail.set('_db_delete', new ivm.Reference(async (modelName, filter) => deleteData(modelName, JSON.parse(filter), user, false)));
|
|
228
289
|
|
|
229
290
|
const createLoggerMethod = (level) => {
|
|
230
291
|
return (...args) => {
|
|
@@ -247,12 +308,8 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
247
308
|
return new ivm.ExternalCopy(result.data?.[0]?.value || null).copyInto();
|
|
248
309
|
}));
|
|
249
310
|
await jail.set('_env_get_all', new ivm.Reference(async () => {
|
|
250
|
-
const result = await
|
|
251
|
-
|
|
252
|
-
acc[v.name] = v.value;
|
|
253
|
-
return acc;
|
|
254
|
-
}, {});
|
|
255
|
-
return new ivm.ExternalCopy(envObject).copyInto();
|
|
311
|
+
const result = await getEnv(user);
|
|
312
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
256
313
|
}));
|
|
257
314
|
|
|
258
315
|
// Contexte sécurisé
|
|
@@ -274,6 +331,10 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
274
331
|
update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
|
|
275
332
|
delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
|
|
276
333
|
};
|
|
334
|
+
|
|
335
|
+
const workflow = {
|
|
336
|
+
run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
|
|
337
|
+
};
|
|
277
338
|
|
|
278
339
|
const logger = {
|
|
279
340
|
info: _log_info,
|
|
@@ -324,7 +385,7 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
324
385
|
}
|
|
325
386
|
|
|
326
387
|
/**
|
|
327
|
-
* Handles the '
|
|
388
|
+
* Handles the 'HttpRequest' workflow action.
|
|
328
389
|
* Sends an HTTP request to a specified URL with substituted data using native fetch.
|
|
329
390
|
*
|
|
330
391
|
* @param {object} actionDef - The definition of the 'Webhook' action.
|
|
@@ -333,17 +394,17 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
333
394
|
* @param {object} dbCollection - The MongoDB collection (moins pertinent ici, mais gardé pour la cohérence).
|
|
334
395
|
* @returns {Promise<{success: boolean, message?: string, responseStatus?: number, responseBody?: any}>} - Result of the action.
|
|
335
396
|
*/
|
|
336
|
-
async function
|
|
397
|
+
async function handleHttpRequestAction(actionDef, contextData, user, dbCollection) {
|
|
337
398
|
const { name: actionName, _id: actionId, url, method = 'POST', headers: headersTemplate, body: bodyTemplate } = actionDef;
|
|
338
399
|
|
|
339
400
|
// 1. Basic Validation
|
|
340
401
|
if (!url) {
|
|
341
|
-
const msg = `[
|
|
402
|
+
const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Missing 'url'.`;
|
|
342
403
|
logger.error(msg);
|
|
343
404
|
return { success: false, message: msg };
|
|
344
405
|
}
|
|
345
406
|
|
|
346
|
-
logger.info(`[
|
|
407
|
+
logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
|
|
347
408
|
|
|
348
409
|
try {
|
|
349
410
|
// 2. Substitute Variables
|
|
@@ -360,7 +421,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
360
421
|
} else if (typeof headersTemplate === 'object') {
|
|
361
422
|
headersObject = await substituteVariables(headersTemplate, contextData, user);
|
|
362
423
|
} else {
|
|
363
|
-
logger.warn(`[
|
|
424
|
+
logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'headers' has an invalid type (${typeof headersTemplate}). Ignoring.`);
|
|
364
425
|
}
|
|
365
426
|
}
|
|
366
427
|
|
|
@@ -371,7 +432,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
371
432
|
} else if (typeof bodyTemplate === 'object') {
|
|
372
433
|
bodyObject = await substituteVariables(bodyTemplate, contextData, user);
|
|
373
434
|
} else {
|
|
374
|
-
logger.warn(`[
|
|
435
|
+
logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'body' has an invalid type (${typeof bodyTemplate}). Ignoring.`);
|
|
375
436
|
}
|
|
376
437
|
}
|
|
377
438
|
|
|
@@ -383,7 +444,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
383
444
|
throw new Error("Parsed headers is not a valid object.");
|
|
384
445
|
}
|
|
385
446
|
} catch (parseError) {
|
|
386
|
-
logger.error(`[
|
|
447
|
+
logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse substituted 'headers' JSON. Error: ${parseError.message}. Using default headers. Substituted string: ${substitutedHeadersString}`);
|
|
387
448
|
headersObject = { 'Content-Type': 'application/json' }; // Fallback
|
|
388
449
|
}
|
|
389
450
|
}
|
|
@@ -425,7 +486,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
425
486
|
}
|
|
426
487
|
|
|
427
488
|
// 5. Execute Fetch Request using native fetch
|
|
428
|
-
logger.info(`[
|
|
489
|
+
logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Calling URL: ${substitutedUrl}`);
|
|
429
490
|
const response = await fetch(substitutedUrl, fetchOptions); // Utilisation de fetch natif
|
|
430
491
|
|
|
431
492
|
// 6. Process Response
|
|
@@ -438,7 +499,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
438
499
|
responseBody = await response.text();
|
|
439
500
|
}
|
|
440
501
|
} catch (responseParseError) {
|
|
441
|
-
logger.error(`[
|
|
502
|
+
logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse response body. Error: ${responseParseError.message}`);
|
|
442
503
|
// Try reading as text again in case of error during json parsing
|
|
443
504
|
try {
|
|
444
505
|
responseBody = await response.text();
|
|
@@ -447,7 +508,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
447
508
|
}
|
|
448
509
|
}
|
|
449
510
|
|
|
450
|
-
logger.info(`[
|
|
511
|
+
logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Received response. Status: ${response.status}`);
|
|
451
512
|
|
|
452
513
|
// 7. Return Result
|
|
453
514
|
if (response.ok) { // Status code 200-299
|
|
@@ -455,13 +516,13 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
455
516
|
success: true,
|
|
456
517
|
message: `Webhook executed successfully. Status: ${response.status}`,
|
|
457
518
|
responseStatus: response.status,
|
|
458
|
-
responseBody: responseBody
|
|
459
|
-
|
|
519
|
+
responseBody: responseBody,
|
|
520
|
+
updatedContext: { httpResponse: responseBody }
|
|
460
521
|
};
|
|
461
522
|
} else {
|
|
462
523
|
// Handle non-successful responses (4xx, 5xx)
|
|
463
524
|
const errorMsg = `Webhook execution failed. Status: ${response.status}. Response: ${typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)}`;
|
|
464
|
-
logger.error(`[
|
|
525
|
+
logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): ${errorMsg}`);
|
|
465
526
|
return {
|
|
466
527
|
success: false,
|
|
467
528
|
message: errorMsg,
|
|
@@ -472,7 +533,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
|
|
|
472
533
|
|
|
473
534
|
} catch (error) {
|
|
474
535
|
// Catch network errors or other unexpected errors during the process
|
|
475
|
-
const msg = `[
|
|
536
|
+
const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Unexpected error during webhook execution. Error: ${error.message}`;
|
|
476
537
|
logger.error(msg, error.stack);
|
|
477
538
|
return { success: false, message: msg };
|
|
478
539
|
}
|
|
@@ -778,6 +839,52 @@ async function handleDeleteDataAction(actionDef, contextData, user, dbCollection
|
|
|
778
839
|
}
|
|
779
840
|
}
|
|
780
841
|
|
|
842
|
+
/**
|
|
843
|
+
* Handles the 'ExecuteServiceFunction' workflow action.
|
|
844
|
+
* Acts as a secure bridge between the workflow engine and native service modules.
|
|
845
|
+
*
|
|
846
|
+
* @param {object} actionDef - The action definition.
|
|
847
|
+
* @param {object} contextData - The current workflow context.
|
|
848
|
+
* @param {object} user - The user object.
|
|
849
|
+
* @returns {Promise<{success: boolean, message?: string, updatedContext?: object}>}
|
|
850
|
+
*/
|
|
851
|
+
async function handleExecuteServiceFunction(actionDef, contextData, user) {
|
|
852
|
+
const { serviceName, functionName, args: argsTemplate } = actionDef;
|
|
853
|
+
|
|
854
|
+
if (!serviceName || !functionName) {
|
|
855
|
+
return { success: false, message: "Action requires 'serviceName' and 'functionName'." };
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const service = services[serviceName];
|
|
859
|
+
if (!service) {
|
|
860
|
+
return { success: false, message: `Service '${serviceName}' not found in the registry.` };
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const func = service[functionName];
|
|
864
|
+
if (typeof func !== 'function') {
|
|
865
|
+
return { success: false, message: `Function '${functionName}' not found in service '${serviceName}'.` };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
// Substitute variables in the arguments array
|
|
870
|
+
const substitutedArgs = Array.isArray(argsTemplate)
|
|
871
|
+
? await substituteVariables(argsTemplate, contextData, user)
|
|
872
|
+
: [];
|
|
873
|
+
|
|
874
|
+
logger.info(`[Service Call] Calling ${serviceName}.${functionName} with ${substitutedArgs.length} argument(s).`);
|
|
875
|
+
const result = await func(...substitutedArgs, user);
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
success: true,
|
|
879
|
+
updatedContext: { serviceResult: result } // Store result in context
|
|
880
|
+
};
|
|
881
|
+
} catch (error) {
|
|
882
|
+
const msg = `Error executing ${serviceName}.${functionName}: ${error.message}`;
|
|
883
|
+
logger.error(`[Service Call] ${msg}`, error.stack);
|
|
884
|
+
return { success: false, message: msg };
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
781
888
|
// Dans workflow.js
|
|
782
889
|
export async function executeStepAction(actionDef, contextData, user, dbCollection) {
|
|
783
890
|
logger.info(`[executeStepAction] Executing action type ${actionDef.type} for action ${actionDef._id} (${actionDef.name})`);
|
|
@@ -789,8 +896,8 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
|
|
|
789
896
|
logger.info(`[Workflow Log Action] Action: ${actionDef.name}. Contexte:`, contextData);
|
|
790
897
|
result = { success: true, message: 'Log action executed successfully.' }; // <--- CORRECTION
|
|
791
898
|
break;
|
|
792
|
-
case '
|
|
793
|
-
result = await
|
|
899
|
+
case 'HttpRequest':
|
|
900
|
+
result = await handleHttpRequestAction(actionDef, contextData, user, dbCollection);
|
|
794
901
|
break;
|
|
795
902
|
case 'CreateData':
|
|
796
903
|
result = await handleCreateDataAction(actionDef, contextData, user, dbCollection);
|
|
@@ -813,6 +920,9 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
|
|
|
813
920
|
case 'ExecuteScript':
|
|
814
921
|
result = await executeSafeJavascript(actionDef, contextData, user);
|
|
815
922
|
break;
|
|
923
|
+
case 'ExecuteServiceFunction':
|
|
924
|
+
result = await handleExecuteServiceFunction(actionDef, contextData, user);
|
|
925
|
+
break;
|
|
816
926
|
default:
|
|
817
927
|
logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
|
|
818
928
|
return { success: false, message: `Unknown action type: ${actionDef.type}` };
|
|
@@ -1027,6 +1137,8 @@ export async function substituteVariables(template, contextData, user) {
|
|
|
1027
1137
|
return new Date().toISOString();
|
|
1028
1138
|
} else if (path === 'randomUUID') {
|
|
1029
1139
|
return crypto.randomUUID();
|
|
1140
|
+
} else if( path === "baseUrl" ){
|
|
1141
|
+
return process.env.NODE_ENV === 'production' ? 'https://'+getHost()+'/' : 'http://localhost:/'+port;
|
|
1030
1142
|
}
|
|
1031
1143
|
|
|
1032
1144
|
// Détecter si le chemin est complexe (contient plus d'un point)
|
|
@@ -1099,102 +1211,96 @@ export async function substituteVariables(template, contextData, user) {
|
|
|
1099
1211
|
}
|
|
1100
1212
|
|
|
1101
1213
|
/**
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1214
|
+
* Triggers the instantiation of a workflowRun if conditions are met.
|
|
1215
|
+
* Checks the event type and trigger's data filter.
|
|
1216
|
+
* Creates a 'workflowRun' document for later asynchronous execution.
|
|
1105
1217
|
*
|
|
1106
|
-
* @param {object} triggerData -
|
|
1107
|
-
* @param {object} user -
|
|
1108
|
-
* @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType -
|
|
1218
|
+
* @param {object} triggerData - The data that triggered the workflow(s) (can be a data document or model document).
|
|
1219
|
+
* @param {object} user - The associated user.
|
|
1220
|
+
* @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType - The event type.
|
|
1109
1221
|
*/
|
|
1110
|
-
export async function triggerWorkflows(triggerData, user, eventType)
|
|
1111
|
-
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1112
|
-
|
|
1222
|
+
export async function triggerWorkflows(triggerData, user, eventType) {
|
|
1113
1223
|
const trigger = async (triggerData, user, eventType) => {
|
|
1114
|
-
|
|
1115
1224
|
// Basic validation
|
|
1116
1225
|
if (!triggerData || !user || !eventType) {
|
|
1117
|
-
console.warn("triggerWorkflows:
|
|
1226
|
+
console.warn("triggerWorkflows: Invalid call - missing triggerData, user, or eventType.", {
|
|
1227
|
+
hasTriggerData: !!triggerData,
|
|
1228
|
+
hasUser: !!user,
|
|
1229
|
+
eventType
|
|
1230
|
+
});
|
|
1118
1231
|
return;
|
|
1119
1232
|
}
|
|
1120
|
-
|
|
1233
|
+
|
|
1234
|
+
// Determine model name and data ID based on event type
|
|
1121
1235
|
const targetModelName = eventType.startsWith('Model') ? triggerData.name : triggerData._model;
|
|
1122
|
-
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1236
|
+
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1123
1237
|
|
|
1124
1238
|
if (!targetModelName) {
|
|
1125
|
-
console.warn(`triggerWorkflows:
|
|
1239
|
+
console.warn(`triggerWorkflows: Cannot determine model name for event ${eventType}.`, triggerData);
|
|
1126
1240
|
return;
|
|
1127
1241
|
}
|
|
1128
1242
|
|
|
1129
1243
|
console.log(`[Workflow Trigger] Event: ${eventType}, Model: ${targetModelName}${dataId ? `, Data ID: ${dataId}` : ''}, User: ${user.username}`);
|
|
1130
1244
|
|
|
1131
1245
|
try {
|
|
1132
|
-
const dbCollection = await getCollectionForUser(user);
|
|
1246
|
+
const dbCollection = await getCollectionForUser(user);
|
|
1133
1247
|
|
|
1134
|
-
// 1.
|
|
1248
|
+
// 1. Find relevant WorkflowTriggers
|
|
1135
1249
|
const workflowTriggers = await dbCollection.find({
|
|
1136
1250
|
_model: 'workflowTrigger',
|
|
1137
1251
|
targetModel: targetModelName,
|
|
1138
1252
|
isActive: true,
|
|
1139
|
-
onEvent: eventType,
|
|
1253
|
+
onEvent: eventType,
|
|
1140
1254
|
$or: [{_user: user._user}, {_user: user.username}]
|
|
1141
1255
|
}).toArray();
|
|
1142
1256
|
|
|
1143
1257
|
if (workflowTriggers.length === 0) {
|
|
1144
|
-
console.debug(`[Workflow Trigger]
|
|
1258
|
+
console.debug(`[Workflow Trigger] No active triggers found for ${targetModelName}/${eventType}.`);
|
|
1145
1259
|
return;
|
|
1146
1260
|
}
|
|
1147
|
-
console.debug(`[Workflow Trigger]
|
|
1261
|
+
console.debug(`[Workflow Trigger] Found ${workflowTriggers.length} potential trigger(s) for ${targetModelName}/${eventType}.`);
|
|
1148
1262
|
|
|
1149
|
-
// 2.
|
|
1263
|
+
// 2. For each trigger, verify data filter and create workflowRun
|
|
1150
1264
|
for (const trigger of workflowTriggers) {
|
|
1151
|
-
console.debug(`[Workflow Trigger]
|
|
1265
|
+
console.debug(`[Workflow Trigger] Evaluating trigger ${trigger._id} (${trigger.name || 'Unnamed'})...`);
|
|
1152
1266
|
|
|
1153
|
-
// 3.
|
|
1154
|
-
if (eventType.startsWith('Data') && trigger.dataFilter
|
|
1267
|
+
// 3. Check data filter if applicable
|
|
1268
|
+
if (eventType.startsWith('Data') && trigger.dataFilter) {
|
|
1155
1269
|
let dataFilterCondition = null;
|
|
1156
1270
|
try {
|
|
1157
|
-
// dataFilter
|
|
1271
|
+
// dataFilter is expected to be stored as an object or valid JSON string
|
|
1158
1272
|
if (typeof trigger.dataFilter === 'string') {
|
|
1159
1273
|
dataFilterCondition = JSON.parse(trigger.dataFilter);
|
|
1160
1274
|
} else if (typeof trigger.dataFilter === 'object' && trigger.dataFilter !== null) {
|
|
1161
1275
|
dataFilterCondition = trigger.dataFilter;
|
|
1162
1276
|
}
|
|
1163
1277
|
} catch (parseError) {
|
|
1164
|
-
console.error(`[Workflow Trigger]
|
|
1165
|
-
continue; //
|
|
1278
|
+
console.error(`[Workflow Trigger] JSON parsing error for dataFilter in trigger ${trigger._id}:`, parseError);
|
|
1279
|
+
continue; // Skip to next trigger if filter is invalid
|
|
1166
1280
|
}
|
|
1167
1281
|
|
|
1168
1282
|
try {
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
]
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
console.debug(`[Workflow Trigger] Vérification dataFilter pour trigger ${trigger._id} avec filtre combiné:`, JSON.stringify(finalFilter));
|
|
1178
|
-
const match = await searchData({ model: triggerData._model, filter: finalFilter, limit: 1 }, user);
|
|
1179
|
-
if (!match.count) {
|
|
1180
|
-
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter non satisfait par la donnée ${dataId}. WorkflowRun non créé.`);
|
|
1181
|
-
continue; // Passer au trigger suivant
|
|
1182
|
-
} else {
|
|
1183
|
-
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfait par la donnée ${dataId}.`);
|
|
1283
|
+
const mod = await getModel(targetModelName, user);
|
|
1284
|
+
const filterMatches = isConditionMet(mod, dataFilterCondition, triggerData, [], user);
|
|
1285
|
+
|
|
1286
|
+
if (!filterMatches) {
|
|
1287
|
+
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter not satisfied by data. Skipping workflowRun creation.`);
|
|
1288
|
+
continue;
|
|
1184
1289
|
}
|
|
1290
|
+
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfied.`);
|
|
1185
1291
|
} catch (filterError) {
|
|
1186
|
-
console.error(`[Workflow Trigger]
|
|
1187
|
-
continue;
|
|
1292
|
+
console.error(`[Workflow Trigger] Error evaluating dataFilter for trigger ${trigger._id}:`, filterError);
|
|
1293
|
+
continue;
|
|
1188
1294
|
}
|
|
1189
|
-
}
|
|
1295
|
+
}
|
|
1190
1296
|
|
|
1191
|
-
// 4.
|
|
1297
|
+
// 4. If filters passed, create workflowRun instance
|
|
1192
1298
|
if (!trigger.workflow || !isObjectId(trigger.workflow)) {
|
|
1193
|
-
console.warn(`[Workflow Trigger] Trigger ${trigger._id}
|
|
1299
|
+
console.warn(`[Workflow Trigger] Trigger ${trigger._id} has no valid associated workflow.`);
|
|
1194
1300
|
continue;
|
|
1195
1301
|
}
|
|
1196
1302
|
|
|
1197
|
-
// a.
|
|
1303
|
+
// a. Verify workflow exists
|
|
1198
1304
|
const workflowDefinition = await dbCollection.findOne({
|
|
1199
1305
|
_id: new ObjectId(trigger.workflow),
|
|
1200
1306
|
_model: 'workflow',
|
|
@@ -1202,21 +1308,20 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1202
1308
|
});
|
|
1203
1309
|
|
|
1204
1310
|
if (!workflowDefinition) {
|
|
1205
|
-
console.warn(`[Workflow Trigger] Workflow ${trigger.workflow}
|
|
1311
|
+
console.warn(`[Workflow Trigger] Workflow ${trigger.workflow} associated with trigger ${trigger._id} not found.`);
|
|
1206
1312
|
continue;
|
|
1207
1313
|
}
|
|
1208
1314
|
|
|
1209
|
-
// b.
|
|
1315
|
+
// b. Create workflowRun document
|
|
1210
1316
|
const workflowRunData = {
|
|
1211
1317
|
_model: 'workflowRun',
|
|
1212
1318
|
_user: user._user || user.username,
|
|
1213
|
-
workflow: workflowDefinition._id,
|
|
1214
|
-
contextData: {
|
|
1215
|
-
triggerDataModel: targetModelName
|
|
1216
|
-
triggerData: triggerData
|
|
1217
|
-
// Vous pourriez ajouter d'autres infos ici si nécessaire
|
|
1319
|
+
workflow: workflowDefinition._id,
|
|
1320
|
+
contextData: {
|
|
1321
|
+
triggerDataModel: targetModelName,
|
|
1322
|
+
triggerData: triggerData
|
|
1218
1323
|
},
|
|
1219
|
-
status: 'pending',
|
|
1324
|
+
status: 'pending',
|
|
1220
1325
|
owner: null,
|
|
1221
1326
|
startedAt: new Date()
|
|
1222
1327
|
};
|
|
@@ -1224,30 +1329,25 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1224
1329
|
try {
|
|
1225
1330
|
const insertResult = await dbCollection.insertOne(workflowRunData);
|
|
1226
1331
|
if (insertResult.insertedId) {
|
|
1227
|
-
console.info(`[Workflow Trigger]
|
|
1228
|
-
|
|
1332
|
+
console.info(`[Workflow Trigger] Created workflowRun ${insertResult.insertedId} for workflow ${workflowDefinition.name} (ID: ${workflowDefinition._id}) triggered by ${trigger._id}.`);
|
|
1229
1333
|
await workflowModule.processWorkflowRun(insertResult.insertedId, user);
|
|
1230
1334
|
} else {
|
|
1231
|
-
console.error(`[Workflow Trigger]
|
|
1335
|
+
console.error(`[Workflow Trigger] Failed to create workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}).`);
|
|
1232
1336
|
}
|
|
1233
1337
|
} catch (insertError) {
|
|
1234
|
-
console.error(`[Workflow Trigger]
|
|
1338
|
+
console.error(`[Workflow Trigger] Error creating workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}):`, insertError);
|
|
1235
1339
|
}
|
|
1236
|
-
|
|
1237
|
-
} // Fin de la boucle des triggers
|
|
1238
|
-
|
|
1340
|
+
}
|
|
1239
1341
|
} catch (error) {
|
|
1240
|
-
console.error(`[Workflow Trigger]
|
|
1342
|
+
console.error(`[Workflow Trigger] General error in triggerWorkflows for ${targetModelName}${dataId ? ` ID: ${dataId}` : ''} (Event: ${eventType}):`, error);
|
|
1241
1343
|
}
|
|
1242
1344
|
}
|
|
1243
1345
|
|
|
1244
1346
|
return new Promise((resolve) => setTimeout(async () => {
|
|
1245
1347
|
await trigger(triggerData, user, eventType);
|
|
1246
1348
|
resolve();
|
|
1247
|
-
}, 0)
|
|
1248
|
-
);
|
|
1349
|
+
}, 0));
|
|
1249
1350
|
}
|
|
1250
|
-
|
|
1251
1351
|
/**
|
|
1252
1352
|
* Processes a workflowRun instance step-by-step.
|
|
1253
1353
|
* Fetches the run, evaluates conditions, executes actions, and transitions
|
|
@@ -1483,12 +1583,7 @@ async function executeGenerateAIContentAction(action, context, user) {
|
|
|
1483
1583
|
// 1. Retrieve the API key (User Environment > Machine Environment)
|
|
1484
1584
|
let apiKey;
|
|
1485
1585
|
|
|
1486
|
-
const
|
|
1487
|
-
"OpenAI" : "OPENAI_API_KEY",
|
|
1488
|
-
"Google": "GOOGLE_API_KEY",
|
|
1489
|
-
"DeepSeek": "DEEPSEEK_API_KEY"
|
|
1490
|
-
}
|
|
1491
|
-
const envKeyName = providers[aiProvider];
|
|
1586
|
+
const envKeyName = providers[aiProvider].key;
|
|
1492
1587
|
if( !envKeyName ) {
|
|
1493
1588
|
return {success: false, message: i18n.t('aiContent.env', `API key for provider ${aiProvider} (${envKeyName}) not found in user environment.`)};
|
|
1494
1589
|
}
|
|
@@ -1512,22 +1607,8 @@ async function executeGenerateAIContentAction(action, context, user) {
|
|
|
1512
1607
|
}
|
|
1513
1608
|
|
|
1514
1609
|
// 2. Initialize the LLM client with LangChain
|
|
1515
|
-
let llm;
|
|
1516
|
-
|
|
1517
|
-
switch (aiProvider) {
|
|
1518
|
-
case 'OpenAI':
|
|
1519
|
-
llm = new ChatOpenAI({ apiKey, model: aiModel, temperature: 0.7 });
|
|
1520
|
-
break;
|
|
1521
|
-
case 'Google':
|
|
1522
|
-
llm = new ChatGoogleGenerativeAI({ apiKey, model: aiModel, temperature: 0.7 });
|
|
1523
|
-
break;
|
|
1524
|
-
case 'DeepSeek':
|
|
1525
|
-
llm = new ChatDeepSeek({ apiKey, model: aiModel, temperature: 0.7 });
|
|
1526
|
-
break;
|
|
1527
|
-
default:
|
|
1528
|
-
throw new Error(`Unsupported AI provider: ${aiProvider}`);
|
|
1529
|
-
}
|
|
1530
|
-
} catch (initError) {
|
|
1610
|
+
let llm = getAIProvider(aiProvider, aiModel, apiKey);
|
|
1611
|
+
if( !llm ) {
|
|
1531
1612
|
const message = `Failed to initialize AI client for ${aiProvider}: ${initError.message}`;
|
|
1532
1613
|
logger.error(`[AI Action] ${message}`);
|
|
1533
1614
|
return { success: false, message };
|