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.
@@ -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: #e0e7ff; /* Un bleu clair pour les étapes */
4
- border-color: #a5b4fc;
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: #f0f9ff; /* Un bleu très clair pour les actions */
11
- border-color: #bae6fd;
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.2",
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",