data-primals-engine 1.2.6-rc3 → 1.2.6
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/client/package-lock.json +247 -4354
- package/client/package.json +16 -15
- package/client/src/App.scss +1 -0
- package/client/src/DataEditor.jsx +1 -1
- package/client/src/Field.jsx +4 -16
- package/client/src/translations.js +17 -0
- package/package.json +1 -1
- package/src/filter.js +35 -5
- package/src/modules/data/data.core.js +1 -3
- package/src/modules/data/data.js +58 -31
- package/src/modules/mongodb.js +3 -1
- package/src/modules/workflow.js +66 -70
- package/test/data.integration.test.js +1 -1
- package/test/workflow.actions.integration.test.js +474 -0
package/src/modules/workflow.js
CHANGED
|
@@ -6,7 +6,7 @@ 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";
|
|
9
|
+
import {deleteData, getModel, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
|
|
10
10
|
import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
|
|
11
11
|
import {ChatOpenAI} from "@langchain/openai";
|
|
12
12
|
import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
|
|
@@ -17,6 +17,8 @@ import {sendEmail} from "../email.js";
|
|
|
17
17
|
|
|
18
18
|
import * as workflowModule from './workflow.js';
|
|
19
19
|
import util from "node:util";
|
|
20
|
+
import {object_equals} from "../core.js";
|
|
21
|
+
import {isConditionMet} from "../filter.js";
|
|
20
22
|
|
|
21
23
|
let logger = null;
|
|
22
24
|
export async function onInit(defaultEngine) {
|
|
@@ -219,12 +221,18 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
219
221
|
};
|
|
220
222
|
|
|
221
223
|
// 1. Build the sandboxed API
|
|
222
|
-
await jail.set('_db_create', new ivm.Reference((modelName, dataObject) =>
|
|
224
|
+
await jail.set('_db_create', new ivm.Reference(async (modelName, dataObject) => {
|
|
225
|
+
const result = await insertData(modelName, JSON.parse(dataObject), {}, user, false);
|
|
226
|
+
if (result.success && result.insertedIds) {
|
|
227
|
+
result.insertedIds = result.insertedIds.map(id => id.toString());
|
|
228
|
+
}
|
|
229
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
230
|
+
}));
|
|
223
231
|
await jail.set('_db_find', new ivm.Reference(find));
|
|
224
232
|
await jail.set('_db_findOne', new ivm.Reference(findOne));
|
|
225
233
|
|
|
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)));
|
|
234
|
+
await jail.set('_db_update', new ivm.Reference(async (modelName, filter, updateObject) => patchData(modelName, JSON.parse(filter), JSON.parse(updateObject), {}, user, false)));
|
|
235
|
+
await jail.set('_db_delete', new ivm.Reference(async (modelName, filter) => deleteData(modelName, JSON.parse(filter), user, false)));
|
|
228
236
|
|
|
229
237
|
const createLoggerMethod = (level) => {
|
|
230
238
|
return (...args) => {
|
|
@@ -1099,102 +1107,96 @@ export async function substituteVariables(template, contextData, user) {
|
|
|
1099
1107
|
}
|
|
1100
1108
|
|
|
1101
1109
|
/**
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1110
|
+
* Triggers the instantiation of a workflowRun if conditions are met.
|
|
1111
|
+
* Checks the event type and trigger's data filter.
|
|
1112
|
+
* Creates a 'workflowRun' document for later asynchronous execution.
|
|
1105
1113
|
*
|
|
1106
|
-
* @param {object} triggerData -
|
|
1107
|
-
* @param {object} user -
|
|
1108
|
-
* @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType -
|
|
1114
|
+
* @param {object} triggerData - The data that triggered the workflow(s) (can be a data document or model document).
|
|
1115
|
+
* @param {object} user - The associated user.
|
|
1116
|
+
* @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType - The event type.
|
|
1109
1117
|
*/
|
|
1110
|
-
export async function triggerWorkflows(triggerData, user, eventType)
|
|
1111
|
-
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1112
|
-
|
|
1118
|
+
export async function triggerWorkflows(triggerData, user, eventType) {
|
|
1113
1119
|
const trigger = async (triggerData, user, eventType) => {
|
|
1114
|
-
|
|
1115
1120
|
// Basic validation
|
|
1116
1121
|
if (!triggerData || !user || !eventType) {
|
|
1117
|
-
console.warn("triggerWorkflows:
|
|
1122
|
+
console.warn("triggerWorkflows: Invalid call - missing triggerData, user, or eventType.", {
|
|
1123
|
+
hasTriggerData: !!triggerData,
|
|
1124
|
+
hasUser: !!user,
|
|
1125
|
+
eventType
|
|
1126
|
+
});
|
|
1118
1127
|
return;
|
|
1119
1128
|
}
|
|
1120
|
-
|
|
1129
|
+
|
|
1130
|
+
// Determine model name and data ID based on event type
|
|
1121
1131
|
const targetModelName = eventType.startsWith('Model') ? triggerData.name : triggerData._model;
|
|
1122
|
-
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1132
|
+
const dataId = eventType.startsWith('Model') ? null : triggerData._id;
|
|
1123
1133
|
|
|
1124
1134
|
if (!targetModelName) {
|
|
1125
|
-
console.warn(`triggerWorkflows:
|
|
1135
|
+
console.warn(`triggerWorkflows: Cannot determine model name for event ${eventType}.`, triggerData);
|
|
1126
1136
|
return;
|
|
1127
1137
|
}
|
|
1128
1138
|
|
|
1129
1139
|
console.log(`[Workflow Trigger] Event: ${eventType}, Model: ${targetModelName}${dataId ? `, Data ID: ${dataId}` : ''}, User: ${user.username}`);
|
|
1130
1140
|
|
|
1131
1141
|
try {
|
|
1132
|
-
const dbCollection = await getCollectionForUser(user);
|
|
1142
|
+
const dbCollection = await getCollectionForUser(user);
|
|
1133
1143
|
|
|
1134
|
-
// 1.
|
|
1144
|
+
// 1. Find relevant WorkflowTriggers
|
|
1135
1145
|
const workflowTriggers = await dbCollection.find({
|
|
1136
1146
|
_model: 'workflowTrigger',
|
|
1137
1147
|
targetModel: targetModelName,
|
|
1138
1148
|
isActive: true,
|
|
1139
|
-
onEvent: eventType,
|
|
1149
|
+
onEvent: eventType,
|
|
1140
1150
|
$or: [{_user: user._user}, {_user: user.username}]
|
|
1141
1151
|
}).toArray();
|
|
1142
1152
|
|
|
1143
1153
|
if (workflowTriggers.length === 0) {
|
|
1144
|
-
console.debug(`[Workflow Trigger]
|
|
1154
|
+
console.debug(`[Workflow Trigger] No active triggers found for ${targetModelName}/${eventType}.`);
|
|
1145
1155
|
return;
|
|
1146
1156
|
}
|
|
1147
|
-
console.debug(`[Workflow Trigger]
|
|
1157
|
+
console.debug(`[Workflow Trigger] Found ${workflowTriggers.length} potential trigger(s) for ${targetModelName}/${eventType}.`);
|
|
1148
1158
|
|
|
1149
|
-
// 2.
|
|
1159
|
+
// 2. For each trigger, verify data filter and create workflowRun
|
|
1150
1160
|
for (const trigger of workflowTriggers) {
|
|
1151
|
-
console.debug(`[Workflow Trigger]
|
|
1161
|
+
console.debug(`[Workflow Trigger] Evaluating trigger ${trigger._id} (${trigger.name || 'Unnamed'})...`);
|
|
1152
1162
|
|
|
1153
|
-
// 3.
|
|
1154
|
-
if (eventType.startsWith('Data') && trigger.dataFilter
|
|
1163
|
+
// 3. Check data filter if applicable
|
|
1164
|
+
if (eventType.startsWith('Data') && trigger.dataFilter) {
|
|
1155
1165
|
let dataFilterCondition = null;
|
|
1156
1166
|
try {
|
|
1157
|
-
// dataFilter
|
|
1167
|
+
// dataFilter is expected to be stored as an object or valid JSON string
|
|
1158
1168
|
if (typeof trigger.dataFilter === 'string') {
|
|
1159
1169
|
dataFilterCondition = JSON.parse(trigger.dataFilter);
|
|
1160
1170
|
} else if (typeof trigger.dataFilter === 'object' && trigger.dataFilter !== null) {
|
|
1161
1171
|
dataFilterCondition = trigger.dataFilter;
|
|
1162
1172
|
}
|
|
1163
1173
|
} catch (parseError) {
|
|
1164
|
-
console.error(`[Workflow Trigger]
|
|
1165
|
-
continue; //
|
|
1174
|
+
console.error(`[Workflow Trigger] JSON parsing error for dataFilter in trigger ${trigger._id}:`, parseError);
|
|
1175
|
+
continue; // Skip to next trigger if filter is invalid
|
|
1166
1176
|
}
|
|
1167
1177
|
|
|
1168
1178
|
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}.`);
|
|
1179
|
+
const mod = await getModel(targetModelName, user);
|
|
1180
|
+
const filterMatches = isConditionMet(mod, dataFilterCondition, triggerData, [], user);
|
|
1181
|
+
|
|
1182
|
+
if (!filterMatches) {
|
|
1183
|
+
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter not satisfied by data. Skipping workflowRun creation.`);
|
|
1184
|
+
continue;
|
|
1184
1185
|
}
|
|
1186
|
+
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfied.`);
|
|
1185
1187
|
} catch (filterError) {
|
|
1186
|
-
console.error(`[Workflow Trigger]
|
|
1187
|
-
continue;
|
|
1188
|
+
console.error(`[Workflow Trigger] Error evaluating dataFilter for trigger ${trigger._id}:`, filterError);
|
|
1189
|
+
continue;
|
|
1188
1190
|
}
|
|
1189
|
-
}
|
|
1191
|
+
}
|
|
1190
1192
|
|
|
1191
|
-
// 4.
|
|
1193
|
+
// 4. If filters passed, create workflowRun instance
|
|
1192
1194
|
if (!trigger.workflow || !isObjectId(trigger.workflow)) {
|
|
1193
|
-
console.warn(`[Workflow Trigger] Trigger ${trigger._id}
|
|
1195
|
+
console.warn(`[Workflow Trigger] Trigger ${trigger._id} has no valid associated workflow.`);
|
|
1194
1196
|
continue;
|
|
1195
1197
|
}
|
|
1196
1198
|
|
|
1197
|
-
// a.
|
|
1199
|
+
// a. Verify workflow exists
|
|
1198
1200
|
const workflowDefinition = await dbCollection.findOne({
|
|
1199
1201
|
_id: new ObjectId(trigger.workflow),
|
|
1200
1202
|
_model: 'workflow',
|
|
@@ -1202,21 +1204,20 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1202
1204
|
});
|
|
1203
1205
|
|
|
1204
1206
|
if (!workflowDefinition) {
|
|
1205
|
-
console.warn(`[Workflow Trigger] Workflow ${trigger.workflow}
|
|
1207
|
+
console.warn(`[Workflow Trigger] Workflow ${trigger.workflow} associated with trigger ${trigger._id} not found.`);
|
|
1206
1208
|
continue;
|
|
1207
1209
|
}
|
|
1208
1210
|
|
|
1209
|
-
// b.
|
|
1211
|
+
// b. Create workflowRun document
|
|
1210
1212
|
const workflowRunData = {
|
|
1211
1213
|
_model: 'workflowRun',
|
|
1212
1214
|
_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
|
|
1215
|
+
workflow: workflowDefinition._id,
|
|
1216
|
+
contextData: {
|
|
1217
|
+
triggerDataModel: targetModelName,
|
|
1218
|
+
triggerData: triggerData
|
|
1218
1219
|
},
|
|
1219
|
-
status: 'pending',
|
|
1220
|
+
status: 'pending',
|
|
1220
1221
|
owner: null,
|
|
1221
1222
|
startedAt: new Date()
|
|
1222
1223
|
};
|
|
@@ -1224,30 +1225,25 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1224
1225
|
try {
|
|
1225
1226
|
const insertResult = await dbCollection.insertOne(workflowRunData);
|
|
1226
1227
|
if (insertResult.insertedId) {
|
|
1227
|
-
console.info(`[Workflow Trigger]
|
|
1228
|
-
|
|
1228
|
+
console.info(`[Workflow Trigger] Created workflowRun ${insertResult.insertedId} for workflow ${workflowDefinition.name} (ID: ${workflowDefinition._id}) triggered by ${trigger._id}.`);
|
|
1229
1229
|
await workflowModule.processWorkflowRun(insertResult.insertedId, user);
|
|
1230
1230
|
} else {
|
|
1231
|
-
console.error(`[Workflow Trigger]
|
|
1231
|
+
console.error(`[Workflow Trigger] Failed to create workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}).`);
|
|
1232
1232
|
}
|
|
1233
1233
|
} catch (insertError) {
|
|
1234
|
-
console.error(`[Workflow Trigger]
|
|
1234
|
+
console.error(`[Workflow Trigger] Error creating workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}):`, insertError);
|
|
1235
1235
|
}
|
|
1236
|
-
|
|
1237
|
-
} // Fin de la boucle des triggers
|
|
1238
|
-
|
|
1236
|
+
}
|
|
1239
1237
|
} catch (error) {
|
|
1240
|
-
console.error(`[Workflow Trigger]
|
|
1238
|
+
console.error(`[Workflow Trigger] General error in triggerWorkflows for ${targetModelName}${dataId ? ` ID: ${dataId}` : ''} (Event: ${eventType}):`, error);
|
|
1241
1239
|
}
|
|
1242
1240
|
}
|
|
1243
1241
|
|
|
1244
1242
|
return new Promise((resolve) => setTimeout(async () => {
|
|
1245
1243
|
await trigger(triggerData, user, eventType);
|
|
1246
1244
|
resolve();
|
|
1247
|
-
}, 0)
|
|
1248
|
-
);
|
|
1245
|
+
}, 0));
|
|
1249
1246
|
}
|
|
1250
|
-
|
|
1251
1247
|
/**
|
|
1252
1248
|
* Processes a workflowRun instance step-by-step.
|
|
1253
1249
|
* Fetches the run, evaluates conditions, executes actions, and transitions
|
|
@@ -795,7 +795,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
795
795
|
name: 'produit',
|
|
796
796
|
type: 'relation',
|
|
797
797
|
relation: 'produitTestFiltre',
|
|
798
|
-
relationFilter: { actif
|
|
798
|
+
relationFilter: { "$eq":["$actif", true] } // Only link active products
|
|
799
799
|
}
|
|
800
800
|
]
|
|
801
801
|
};
|