data-primals-engine 1.6.2 → 1.6.3
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 +1009 -969
- package/client/src/Dashboard.jsx +1 -1
- package/client/src/WorkflowEditor.jsx +101 -0
- package/client/src/WorkflowEditor.scss +29 -4
- package/package.json +1 -1
- package/src/defaultModels +1628 -0
- package/src/defaultModels.js +14 -0
- package/src/filter.js +170 -1
- package/src/modules/data/data.history.js +5 -1
- package/src/modules/data/data.operations.js +9 -3
- package/src/modules/user.js +57 -25
- package/src/modules/workflow.js +51 -176
- package/src/providers.js +1 -1
- package/test/data.history.integration.test.js +140 -16
- package/test/user.test.js +195 -278
- package/test/workflow.integration.test.js +8 -0
package/client/src/Dashboard.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, {useState, useMemo, useEffect} from 'react';
|
|
2
2
|
import { Trans, useTranslation } from 'react-i18next';
|
|
3
|
-
import {FaPlus, FaSpinner, FaCog} from "react-icons/fa"; // Ajout FaTrash
|
|
3
|
+
import {FaPlus, FaSpinner, FaCog, FaEye} from "react-icons/fa"; // Ajout FaTrash
|
|
4
4
|
|
|
5
5
|
import "./Dashboard.scss"
|
|
6
6
|
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
@@ -12,6 +12,7 @@ import { useModelContext } from './contexts/ModelContext';
|
|
|
12
12
|
import { useAuthContext } from './contexts/AuthContext';
|
|
13
13
|
import { Trans, useTranslation } from 'react-i18next';
|
|
14
14
|
import { CodeField } from "./Field.jsx";
|
|
15
|
+
import { useData } from './hooks/data.js';
|
|
15
16
|
import { useQuery } from 'react-query';
|
|
16
17
|
|
|
17
18
|
import "./WorkflowEditor.scss"
|
|
@@ -32,6 +33,23 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
32
33
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
33
34
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
34
35
|
const [selectedNode, setSelectedNode] = useState(null);
|
|
36
|
+
const [highlightedElements, setHighlightedElements] = useState({ nodes: new Set(), edges: new Set() });
|
|
37
|
+
const [workflowRuns, setWorkflowRuns] = useState([]);
|
|
38
|
+
const [selectedRun, setSelectedRun] = useState(null);
|
|
39
|
+
|
|
40
|
+
// --- CORRECTION ---
|
|
41
|
+
// On utilise le hook `useData` comme prévu, en lui passant directement les paramètres de la requête.
|
|
42
|
+
// Cela corrige l'erreur "Cannot read properties of undefined (reading 'queryKey')".
|
|
43
|
+
const { data: runsData, refetch: refetchRuns } = useData(
|
|
44
|
+
'workflowRun', // queryKey
|
|
45
|
+
{ model: 'workflowRun', filter: { workflow: workflowId }, sort: 'startedAt:DESC', limit: 10 }, // queryParams
|
|
46
|
+
{
|
|
47
|
+
queryKey: ['workflowRuns', workflowId],
|
|
48
|
+
enabled: !!workflowId,
|
|
49
|
+
// --- CORRECTION ---
|
|
50
|
+
// Le hook `useData` retourne déjà le tableau de données. On utilise donc `data` directement.
|
|
51
|
+
onSuccess: (data) => setWorkflowRuns(data || [])
|
|
52
|
+
});
|
|
35
53
|
const [panelWidth, setPanelWidth] = useState(300);
|
|
36
54
|
const resizeData = useRef({ isResizing: false, initialX: 0, initialWidth: 0 });
|
|
37
55
|
|
|
@@ -157,6 +175,7 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
157
175
|
const nodePosition = { x: parentPosition.x, y: parentPosition.y + 120 };
|
|
158
176
|
initialNodes.push({
|
|
159
177
|
id: step._id,
|
|
178
|
+
// --- NOUVEAU --- Ajout d'une classe de base pour le styling
|
|
160
179
|
type: 'default',
|
|
161
180
|
className: 'workflow-step-node',
|
|
162
181
|
position: nodePosition,
|
|
@@ -182,6 +201,7 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
182
201
|
initialNodes.push({
|
|
183
202
|
id: action._id,
|
|
184
203
|
type: 'default',
|
|
204
|
+
// --- NOUVEAU --- Ajout d'une classe de base pour le styling
|
|
185
205
|
className: 'workflow-action-node',
|
|
186
206
|
position: actionNodePosition,
|
|
187
207
|
data: {
|
|
@@ -220,6 +240,62 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
220
240
|
}
|
|
221
241
|
}, [mainWorkflowData, workflowStepsData, workflowActionsData, setNodes, setEdges, t]);
|
|
222
242
|
|
|
243
|
+
// --- NOUVEAU ---
|
|
244
|
+
// Ce `useEffect` est le cœur de la nouvelle fonctionnalité.
|
|
245
|
+
// Il s'exécute chaque fois que l'utilisateur sélectionne un `workflowRun` différent.
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (!selectedRun) {
|
|
248
|
+
// Si aucun run n'est sélectionné, on réinitialise tous les styles.
|
|
249
|
+
setNodes((nds) =>
|
|
250
|
+
nds.map((node) => ({
|
|
251
|
+
...node,
|
|
252
|
+
className: node.className?.split(' ')[0] // Garde seulement la classe de base (ex: 'workflow-step-node')
|
|
253
|
+
}))
|
|
254
|
+
);
|
|
255
|
+
setEdges((eds) =>
|
|
256
|
+
eds.map((edge) => ({
|
|
257
|
+
...edge,
|
|
258
|
+
animated: false,
|
|
259
|
+
className: ''
|
|
260
|
+
}))
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 1. Identifier tous les IDs des nœuds (étapes et actions) qui ont été exécutés.
|
|
266
|
+
const executedStepIds = new Set(selectedRun.history.map(h => h.stepId));
|
|
267
|
+
const executedActionIds = new Set(selectedRun.history.flatMap(h => h.actions?.map(a => a.actionId) || []));
|
|
268
|
+
const allExecutedNodeIds = new Set([
|
|
269
|
+
`workflow-start-${mainWorkflowData._id}`, // Toujours inclure le nœud de départ
|
|
270
|
+
...executedStepIds,
|
|
271
|
+
...executedActionIds
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
// 2. Mettre à jour les nœuds pour ajouter une classe 'executed'.
|
|
275
|
+
setNodes((nds) =>
|
|
276
|
+
nds.map((node) => {
|
|
277
|
+
const baseClass = node.className?.split(' ')[0] || '';
|
|
278
|
+
if (allExecutedNodeIds.has(node.id)) {
|
|
279
|
+
return { ...node, className: `${baseClass} executed` };
|
|
280
|
+
}
|
|
281
|
+
return { ...node, className: baseClass }; // Rétablir la classe de base si non exécuté
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// 3. Mettre à jour les arêtes pour les animer si elles connectent deux nœuds exécutés.
|
|
286
|
+
setEdges((eds) =>
|
|
287
|
+
eds.map((edge) => {
|
|
288
|
+
const isExecuted = allExecutedNodeIds.has(edge.source) && allExecutedNodeIds.has(edge.target);
|
|
289
|
+
return {
|
|
290
|
+
...edge,
|
|
291
|
+
animated: isExecuted,
|
|
292
|
+
className: isExecuted ? 'executed' : ''
|
|
293
|
+
};
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
}, [selectedRun, setNodes, setEdges, mainWorkflowData]);
|
|
298
|
+
|
|
223
299
|
const onConnect = useCallback(
|
|
224
300
|
(params) => setEdges((eds) => addEdge(params, eds)),
|
|
225
301
|
[setEdges],
|
|
@@ -264,6 +340,11 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
264
340
|
{/* Panneau latéral pour l'édition des propriétés */}
|
|
265
341
|
{selectedNode && (
|
|
266
342
|
<div className="properties-panel" style={{ width: `${panelWidth}px`, padding: '10px', borderLeft: '1px solid #ccc' }}>
|
|
343
|
+
<select onChange={(e) => setSelectedRun(workflowRuns.find(r => r._id === e.target.value))} value={selectedRun?._id || ''}>
|
|
344
|
+
<option value="">{t('select_a_run', 'Select a run to debug')}</option>
|
|
345
|
+
{workflowRuns.map(run => <option key={run._id} value={run._id}>{new Date(run.startedAt).toLocaleString()} - {run.status}</option>)}
|
|
346
|
+
</select>
|
|
347
|
+
<button onClick={() => refetchRuns()}>{t('refresh', 'Refresh')}</button>
|
|
267
348
|
<div
|
|
268
349
|
className="resizer"
|
|
269
350
|
onMouseDown={handleMouseDown}
|
|
@@ -271,6 +352,26 @@ const WorkflowEditor = ({ workflowId }) => {
|
|
|
271
352
|
<h3><Trans i18nKey="properties">Propriétés</Trans></h3>
|
|
272
353
|
<p><strong>ID:</strong> {selectedNode.id}</p>
|
|
273
354
|
|
|
355
|
+
{/* --- NOUVELLE SECTION POUR L'HISTORIQUE --- */}
|
|
356
|
+
{selectedRun && selectedRun.history && selectedRun.history.find(h => h.stepId === selectedNode.id) && (
|
|
357
|
+
<div className="mt-2 p-2 border rounded bg-gray-50">
|
|
358
|
+
<strong><Trans i18nKey="run_history">Run History</Trans> ({selectedRun.status})</strong>
|
|
359
|
+
{selectedRun.history.filter(h => h.stepId === selectedNode.id).map((historyEntry, index) => (
|
|
360
|
+
<div key={index} className="mt-1 text-sm">
|
|
361
|
+
<p><strong><Trans i18nKey="executed_at">Executed at:</Trans></strong> {new Date(historyEntry.executedAt).toLocaleTimeString()}</p>
|
|
362
|
+
<p><strong><Trans i18nKey="status">Status:</Trans></strong> {historyEntry.status}</p>
|
|
363
|
+
{historyEntry.actions?.length > 0 && (
|
|
364
|
+
<ul className="pl-4 list-disc">
|
|
365
|
+
{historyEntry.actions.map((actionHist, aIndex) => (
|
|
366
|
+
<li key={aIndex}>{actionHist.actionName}: {actionHist.status} ({actionHist.result})</li>
|
|
367
|
+
))}
|
|
368
|
+
</ul>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
|
|
274
375
|
{/* Affiche les conditions pour un workflowStep */}
|
|
275
376
|
{selectedNode.data.conditions && (
|
|
276
377
|
<div className="mt-2">
|
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
|
|
2
2
|
.workflow-step-node {
|
|
3
|
-
background-color: #
|
|
4
|
-
border-color: #
|
|
3
|
+
background-color: #f3f4f6; /* Gris clair par défaut */
|
|
4
|
+
border-color: #d1d5db;
|
|
5
5
|
border-width: 2px;
|
|
6
6
|
font-weight: bold;
|
|
7
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
.workflow-action-node {
|
|
10
|
-
background-color: #
|
|
11
|
-
border-color: #
|
|
11
|
+
background-color: #f9fafb; /* Gris très clair par défaut */
|
|
12
|
+
border-color: #e5e7eb;
|
|
12
13
|
border-width: 1px;
|
|
13
14
|
border-style: dashed;
|
|
14
15
|
width: auto;
|
|
15
16
|
min-width: 150px;
|
|
17
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* --- NOUVEAU : Styles pour les éléments exécutés --- */
|
|
21
|
+
|
|
22
|
+
/* Style pour les nœuds (étapes et actions) qui ont été exécutés */
|
|
23
|
+
.react-flow__node.executed {
|
|
24
|
+
background-color: #dcfce7; /* Vert clair */
|
|
25
|
+
border-color: #4ade80; /* Vert plus prononcé */
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Style pour les arêtes qui font partie du chemin d'exécution */
|
|
29
|
+
.react-flow__edge.executed .react-flow__edge-path {
|
|
30
|
+
stroke: #2563eb; /* Bleu vif */
|
|
31
|
+
stroke-width: 2.5;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Style pour l'animation sur les arêtes exécutées */
|
|
35
|
+
.react-flow__edge.executed.animated .react-flow__edge-path {
|
|
36
|
+
animation-duration: 1s;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.react-flow__edge-textbg {
|
|
40
|
+
fill: #f9fafb;
|
|
16
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|