data-primals-engine 1.6.2 → 1.6.5

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.
Files changed (39) hide show
  1. package/README.md +56 -911
  2. package/client/package-lock.json +64 -50
  3. package/client/package.json +6 -0
  4. package/client/src/App.scss +29 -0
  5. package/client/src/Dashboard.jsx +1 -1
  6. package/client/src/WorkflowEditor.jsx +101 -0
  7. package/client/src/WorkflowEditor.scss +29 -4
  8. package/client/src/_variables.scss +3 -0
  9. package/doc/automation-workflows.md +102 -0
  10. package/doc/core-concepts.md +33 -0
  11. package/doc/custom-api-endpoints.md +40 -0
  12. package/doc/dashboards-kpis-charts.md +49 -0
  13. package/doc/data-management.md +120 -0
  14. package/doc/data-models.md +75 -0
  15. package/doc/index.md +14 -0
  16. package/doc/roles-permissions.md +43 -0
  17. package/doc/users.md +30 -0
  18. package/package.json +7 -5
  19. package/src/client.js +6 -4
  20. package/src/core.js +31 -12
  21. package/src/defaultModels +1628 -0
  22. package/src/defaultModels.js +14 -0
  23. package/src/filter.js +110 -42
  24. package/src/modules/data/data.history.js +5 -1
  25. package/src/modules/data/data.js +2 -1
  26. package/src/modules/data/data.operations.js +116 -72
  27. package/src/modules/data/data.relations.js +1 -1
  28. package/src/modules/mongodb.js +2 -1
  29. package/src/modules/swagger.js +25 -5
  30. package/src/modules/user.js +138 -76
  31. package/src/modules/workflow.js +187 -177
  32. package/src/providers.js +1 -1
  33. package/swagger-en.yml +2308 -472
  34. package/swagger-fr.yml +487 -3
  35. package/test/core.test.js +341 -0
  36. package/test/data.history.integration.test.js +140 -16
  37. package/test/data.integration.test.js +137 -2
  38. package/test/user.test.js +201 -280
  39. package/test/workflow.integration.test.js +8 -0
@@ -7,6 +7,7 @@
7
7
  "": {
8
8
  "name": "client",
9
9
  "version": "0.0.0",
10
+ "hasInstallScript": true,
10
11
  "dependencies": {
11
12
  "@babel/runtime": "^7.28.3",
12
13
  "@codeium/react-code-editor": "^1.0.12",
@@ -1134,7 +1135,7 @@
1134
1135
  "globals": "^14.0.0",
1135
1136
  "ignore": "^5.2.0",
1136
1137
  "import-fresh": "^3.2.1",
1137
- "js-yaml": "^4.1.0",
1138
+ "js-yaml": ">=4.1.1",
1138
1139
  "minimatch": "^3.1.2",
1139
1140
  "strip-json-comments": "^3.1.1"
1140
1141
  },
@@ -3223,23 +3224,28 @@
3223
3224
  }
3224
3225
  },
3225
3226
  "node_modules/body-parser": {
3226
- "version": "2.2.0",
3227
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
3228
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
3227
+ "version": "2.2.1",
3228
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
3229
+ "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
3230
+ "license": "MIT",
3229
3231
  "peer": true,
3230
3232
  "dependencies": {
3231
3233
  "bytes": "^3.1.2",
3232
3234
  "content-type": "^1.0.5",
3233
- "debug": "^4.4.0",
3235
+ "debug": "^4.4.3",
3234
3236
  "http-errors": "^2.0.0",
3235
- "iconv-lite": "^0.6.3",
3237
+ "iconv-lite": "^0.7.0",
3236
3238
  "on-finished": "^2.4.1",
3237
3239
  "qs": "^6.14.0",
3238
- "raw-body": "^3.0.0",
3239
- "type-is": "^2.0.0"
3240
+ "raw-body": "^3.0.1",
3241
+ "type-is": "^2.0.1"
3240
3242
  },
3241
3243
  "engines": {
3242
3244
  "node": ">=18"
3245
+ },
3246
+ "funding": {
3247
+ "type": "opencollective",
3248
+ "url": "https://opencollective.com/express"
3243
3249
  }
3244
3250
  },
3245
3251
  "node_modules/brace-expansion": {
@@ -3323,6 +3329,7 @@
3323
3329
  "version": "3.1.2",
3324
3330
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
3325
3331
  "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
3332
+ "license": "MIT",
3326
3333
  "peer": true,
3327
3334
  "engines": {
3328
3335
  "node": ">= 0.8"
@@ -3766,9 +3773,10 @@
3766
3773
  "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
3767
3774
  },
3768
3775
  "node_modules/debug": {
3769
- "version": "4.4.1",
3770
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
3771
- "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
3776
+ "version": "4.4.3",
3777
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
3778
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
3779
+ "license": "MIT",
3772
3780
  "dependencies": {
3773
3781
  "ms": "^2.1.3"
3774
3782
  },
@@ -4440,7 +4448,7 @@
4440
4448
  "peer": true,
4441
4449
  "dependencies": {
4442
4450
  "accepts": "^2.0.0",
4443
- "body-parser": "^2.2.0",
4451
+ "body-parser": ">=2.2.1",
4444
4452
  "content-disposition": "^1.0.0",
4445
4453
  "content-type": "^1.0.5",
4446
4454
  "cookie": "^0.7.1",
@@ -5020,19 +5028,24 @@
5020
5028
  }
5021
5029
  },
5022
5030
  "node_modules/http-errors": {
5023
- "version": "2.0.0",
5024
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
5025
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
5031
+ "version": "2.0.1",
5032
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
5033
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
5034
+ "license": "MIT",
5026
5035
  "peer": true,
5027
5036
  "dependencies": {
5028
- "depd": "2.0.0",
5029
- "inherits": "2.0.4",
5030
- "setprototypeof": "1.2.0",
5031
- "statuses": "2.0.1",
5032
- "toidentifier": "1.0.1"
5037
+ "depd": "~2.0.0",
5038
+ "inherits": "~2.0.4",
5039
+ "setprototypeof": "~1.2.0",
5040
+ "statuses": "~2.0.2",
5041
+ "toidentifier": "~1.0.1"
5033
5042
  },
5034
5043
  "engines": {
5035
5044
  "node": ">= 0.8"
5045
+ },
5046
+ "funding": {
5047
+ "type": "opencollective",
5048
+ "url": "https://opencollective.com/express"
5036
5049
  }
5037
5050
  },
5038
5051
  "node_modules/i18next": {
@@ -5067,15 +5080,20 @@
5067
5080
  }
5068
5081
  },
5069
5082
  "node_modules/iconv-lite": {
5070
- "version": "0.6.3",
5071
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
5072
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
5083
+ "version": "0.7.0",
5084
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
5085
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
5086
+ "license": "MIT",
5073
5087
  "peer": true,
5074
5088
  "dependencies": {
5075
5089
  "safer-buffer": ">= 2.1.2 < 3.0.0"
5076
5090
  },
5077
5091
  "engines": {
5078
5092
  "node": ">=0.10.0"
5093
+ },
5094
+ "funding": {
5095
+ "type": "opencollective",
5096
+ "url": "https://opencollective.com/express"
5079
5097
  }
5080
5098
  },
5081
5099
  "node_modules/ignore": {
@@ -5575,15 +5593,16 @@
5575
5593
  "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
5576
5594
  },
5577
5595
  "node_modules/js-yaml": {
5578
- "version": "4.1.0",
5579
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
5580
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
5596
+ "version": "4.1.1",
5597
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
5598
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
5581
5599
  "dev": true,
5600
+ "license": "MIT",
5582
5601
  "dependencies": {
5583
5602
  "argparse": "^2.0.1"
5584
5603
  },
5585
5604
  "bin": {
5586
- "js-yaml": "bin/js-yaml.js"
5605
+ "js-yaml": ">=4.1.1"
5587
5606
  }
5588
5607
  },
5589
5608
  "node_modules/jsesc": {
@@ -6559,18 +6578,19 @@
6559
6578
  }
6560
6579
  },
6561
6580
  "node_modules/raw-body": {
6562
- "version": "3.0.0",
6563
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
6564
- "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
6581
+ "version": "3.0.2",
6582
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
6583
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
6584
+ "license": "MIT",
6565
6585
  "peer": true,
6566
6586
  "dependencies": {
6567
- "bytes": "3.1.2",
6568
- "http-errors": "2.0.0",
6569
- "iconv-lite": "0.6.3",
6570
- "unpipe": "1.0.0"
6587
+ "bytes": "~3.1.2",
6588
+ "http-errors": "~2.0.1",
6589
+ "iconv-lite": "~0.7.0",
6590
+ "unpipe": "~1.0.0"
6571
6591
  },
6572
6592
  "engines": {
6573
- "node": ">= 0.8"
6593
+ "node": ">= 0.10"
6574
6594
  }
6575
6595
  },
6576
6596
  "node_modules/react": {
@@ -6952,7 +6972,7 @@
6952
6972
  "highlight.js": "^10.4.1",
6953
6973
  "highlightjs-vue": "^1.0.0",
6954
6974
  "lowlight": "^1.17.0",
6955
- "prismjs": "^1.27.0",
6975
+ "prismjs": ">=1.30.0",
6956
6976
  "refractor": "^3.6.0"
6957
6977
  },
6958
6978
  "peerDependencies": {
@@ -7068,7 +7088,7 @@
7068
7088
  "dependencies": {
7069
7089
  "hastscript": "^6.0.0",
7070
7090
  "parse-entities": "^2.0.0",
7071
- "prismjs": "~1.27.0"
7091
+ "prismjs": ">=1.30.0"
7072
7092
  },
7073
7093
  "funding": {
7074
7094
  "type": "github",
@@ -7167,15 +7187,6 @@
7167
7187
  "url": "https://github.com/sponsors/wooorm"
7168
7188
  }
7169
7189
  },
7170
- "node_modules/refractor/node_modules/prismjs": {
7171
- "version": "1.27.0",
7172
- "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
7173
- "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
7174
- "peer": true,
7175
- "engines": {
7176
- "node": ">=6"
7177
- }
7178
- },
7179
7190
  "node_modules/regexp.prototype.flags": {
7180
7191
  "version": "1.5.4",
7181
7192
  "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -7422,6 +7433,7 @@
7422
7433
  "version": "2.1.2",
7423
7434
  "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
7424
7435
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
7436
+ "license": "MIT",
7425
7437
  "peer": true
7426
7438
  },
7427
7439
  "node_modules/sass": {
@@ -8021,9 +8033,10 @@
8021
8033
  "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
8022
8034
  },
8023
8035
  "node_modules/statuses": {
8024
- "version": "2.0.1",
8025
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
8026
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
8036
+ "version": "2.0.2",
8037
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
8038
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
8039
+ "license": "MIT",
8027
8040
  "peer": true,
8028
8041
  "engines": {
8029
8042
  "node": ">= 0.8"
@@ -8440,6 +8453,7 @@
8440
8453
  "version": "1.0.0",
8441
8454
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
8442
8455
  "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
8456
+ "license": "MIT",
8443
8457
  "peer": true,
8444
8458
  "engines": {
8445
8459
  "node": ">= 0.8"
@@ -8808,4 +8822,4 @@
8808
8822
  }
8809
8823
  }
8810
8824
  }
8811
- }
8825
+ }
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
+ "preinstall": "npx force-resolutions",
7
8
  "dev": "vite",
8
9
  "build": "vite build --mode development",
9
10
  "build:ssr-client": "vite build --ssrManifest --outDir dist/client --mode development",
@@ -11,6 +12,11 @@
11
12
  "lint": "eslint .",
12
13
  "preview": "vite preview"
13
14
  },
15
+ "resolutions": {
16
+ "body-parser": ">=2.2.1",
17
+ "js-yaml": ">=4.1.1",
18
+ "prismjs": ">=1.30.0"
19
+ },
14
20
  "peerDependencies": {
15
21
  "@tiptap/react": "^2.26.1",
16
22
  "react-chartjs-2": "^5.3.0",
@@ -5,6 +5,35 @@ img {
5
5
  width: auto;
6
6
  }
7
7
 
8
+ /*
9
+ * Style global pour les barres de défilement (scrollbars)
10
+ * S'applique à toute l'application, y compris les blocs de code.
11
+ */
12
+
13
+ /* Pour les navigateurs WebKit (Chrome, Safari, Edge, etc.) */
14
+ ::-webkit-scrollbar {
15
+ width: 8px; /* Largeur de la scrollbar verticale */
16
+ height: 8px; /* Hauteur de la scrollbar horizontale */
17
+ }
18
+
19
+ /* La piste (le fond de la scrollbar) */
20
+ ::-webkit-scrollbar-track {
21
+ background: $scrollbar-track-color;
22
+ border-radius: 10px;
23
+ }
24
+
25
+ /* La poignée (la partie mobile de la scrollbar) */
26
+ ::-webkit-scrollbar-thumb {
27
+ background-color: $scrollbar-thumb-color;
28
+ border-radius: 10px;
29
+ border: 2px solid $scrollbar-track-color; /* Crée un petit espacement autour de la poignée */
30
+ }
31
+
32
+ /* La poignée au survol de la souris */
33
+ ::-webkit-scrollbar-thumb:hover {
34
+ background-color: $scrollbar-thumb-hover-color;
35
+ }
36
+
8
37
  body {
9
38
  font-family: "Nunito Sans", sans-serif;
10
39
  line-height: 1.6; // Améliore la lisibilité
@@ -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
  }
@@ -17,5 +17,8 @@ $danger-color: #ff1e00;
17
17
  $shadow-color: rgba(0, 0, 0, 0.15);
18
18
  $shadow-color-hover: rgba(0, 0, 0, 0.25);
19
19
 
20
+ $scrollbar-track-color: #f1f1f1; // Couleur de fond de la scrollbar (très clair)
21
+ $scrollbar-thumb-color: #c1c1c1; // Couleur de la barre de défilement
22
+ $scrollbar-thumb-hover-color: #a8a8a8; // Couleur au survol
20
23
 
21
24
  // Appliquer cette palette aux différents éléments
@@ -0,0 +1,102 @@
1
+ # Automation with Workflows: Create Automated Processes
2
+
3
+ The `data-primals-engine` features a powerful **Workflow** system that enables you to define and automate complex business processes. Workflows are sequences of steps and actions that can be triggered by various events or on a schedule, allowing for sophisticated automation directly within your backend.
4
+
5
+ ## Core Workflow Models
6
+
7
+ The workflow system is composed of several interconnected data models:
8
+
9
+ ### 1. `workflow`
10
+
11
+ The `workflow` model is the top-level definition of an automated process. It outlines the overall flow and its starting point.
12
+
13
+ - **`name`** (string, required): A unique, descriptive name for the workflow (e.g., "Order Validation", "Low Stock Notification").
14
+ - **`description`** (richtext, optional): A detailed explanation of the workflow's purpose.
15
+ - **`startStep`** (relation to `workflowStep`, optional): The first step to execute when the workflow is initiated.
16
+
17
+ ### 2. `workflowTrigger`
18
+
19
+ A `workflowTrigger` defines an event or schedule that initiates a workflow.
20
+
21
+ - **`workflow`** (relation to `workflow`, required): The workflow that this trigger belongs to.
22
+ - **`name`** (string, required, unique): A descriptive name for the trigger (e.g., "New Order Created", "Stock < 5", "Monday 9 AM Report").
23
+ - **`type`** (enum, required): How the workflow is initiated:
24
+ - `manual`: Triggered by a data event.
25
+ - `scheduled`: Triggered by a cron schedule.
26
+ - **`onEvent`** (enum, for `manual` type): The data event that triggers the workflow: `DataAdded`, `DataEdited`, `DataDeleted`, `ModelAdded`, `ModelEdited`, `ModelDeleted`.
27
+ - **`targetModel`** (string, for `manual` type): The name of the model targeted by the `onEvent`.
28
+ - **`dataFilter`** (code - JSON, optional, for `manual` type): Optional MongoDB filter conditions checked against the `triggerData` before executing the workflow.
29
+ - **`cronExpression`** (string, for `scheduled` type): A cron expression (e.g., `'0 9 * * 1'` for Monday 9 AM) to schedule the workflow.
30
+ - **`isActive`** (boolean): Whether the trigger is currently active.
31
+ - **`env`** (code - JSON, optional): Environment variables (JSON key/value pairs) specific to this trigger.
32
+
33
+ ### 3. `workflowStep`
34
+
35
+ A `workflowStep` represents a single stage within a workflow process. It can contain conditions, actions, and define the next steps based on success or failure.
36
+
37
+ - **`workflow`** (relation to `workflow`, required): The workflow this step belongs to.
38
+ - **`name`** (string, optional): A descriptive name for the step (e.g., "Check Inventory", "Send Confirmation Email").
39
+ - **`conditions`** (code - JSON, optional): Optional conditions (MongoDB filter syntax) that must be met before the step's actions are executed. These can reference `contextData`.
40
+ - **`actions`** (multiple relation to `workflowAction`, required): The main operations performed by this step.
41
+ - **`onSuccessStep`** (relation to `workflowStep`, optional): The next step to execute if this step's conditions are met and actions succeed.
42
+ - **`onFailureStep`** (relation to `workflowStep`, optional): The next step if conditions fail or any action within this step fails.
43
+ - **`isTerminal`** (boolean, default: `false`): Indicates if this step marks the end of a workflow path.
44
+
45
+ ### 4. `workflowAction`
46
+
47
+ A `workflowAction` defines a specific operation to be performed by a `workflowStep`. This is where the actual work of the workflow happens.
48
+
49
+ - **`name`** (string, required): Name of the action (e.g., "Update Order Status", "Send Email", "Call Payment API").
50
+ - **`type`** (enum, required): The type of operation to perform:
51
+ - `UpdateData`: Modify existing data in a `targetModel`.
52
+ - `CreateData`: Add new data to a `targetModel`.
53
+ - `DeleteData`: Remove data from a `targetModel`.
54
+ - `ExecuteScript`: Run custom JavaScript code.
55
+ - `HttpRequest`: Make an HTTP request to an external service.
56
+ - `SendEmail`: Send an email.
57
+ - `Wait`: Pause the workflow for a specified duration.
58
+ - `GenerateAIContent`: Generate content using an AI model (e.g., OpenAI, Google Gemini).
59
+ - `ExecuteServiceFunction`: Call a function from a registered internal service (e.g., 'stripe').
60
+ - **`targetModel`** (string, for data operations): The model to target.
61
+ - **`targetSelector`** (code - JSON, for `UpdateData`/`DeleteData`): Expression to filter the target document(s).
62
+ - **`fieldsToUpdate`** (code - JSON, for `UpdateData`): Key-value pairs of fields to update.
63
+ - **`dataToCreate`** (code - JSON, for `CreateData`): Object template for the new document.
64
+ - **`script`** (code - JavaScript, for `ExecuteScript`): The JavaScript code to execute.
65
+ - **`url`, `method`, `headers`, `body`** (for `HttpRequest`): Details for the HTTP request.
66
+ - **`emailRecipients`, `emailSubject`, `emailContent`** (for `SendEmail`): Email details.
67
+ - **`duration`, `durationUnit`** (for `Wait`): How long to pause.
68
+ - **`aiProvider`, `aiModel`, `prompt`** (for `GenerateAIContent`): AI model and prompt details.
69
+ - **`serviceName`, `functionName`, `args`** (for `ExecuteServiceFunction`): Service call details.
70
+
71
+ ## Workflow Execution (`workflowRun`)
72
+
73
+ Each time a workflow is triggered, a `workflowRun` document is created to track its execution.
74
+
75
+ - **`workflow`** (relation to `workflow`): The workflow definition that was executed.
76
+ - **`contextData`** (code - JSON): A snapshot of the data or event that triggered this run, and any data generated during execution.
77
+ - **`status`** (enum): The current status (`pending`, `running`, `completed`, `failed`, `waiting`, `cancelled`).
78
+ - **`history`** (array of objects): Detailed execution history of each step and action.
79
+ - **`startedAt`, `completedAt`**: Timestamps for the run.
80
+ - **`error`**: Error message if the workflow run failed.
81
+
82
+ ## Example Workflow Scenario
83
+
84
+ Consider a workflow to "Send a Welcome Email to New Users":
85
+
86
+ 1. **`workflow`**: "New User Onboarding"
87
+ 2. **`workflowTrigger`**:
88
+ - `name`: "On New User Added"
89
+ - `type`: `manual`
90
+ - `onEvent`: `DataAdded`
91
+ - `targetModel`: `user`
92
+ 3. **`workflowStep`**: "Send Welcome Email"
93
+ - `workflow`: "New User Onboarding"
94
+ - `actions`: [ID of "Send Welcome Email Action"]
95
+ - `onSuccessStep`: (optional) "Create Onboarding Task"
96
+ 4. **`workflowAction`**: "Send Welcome Email Action"
97
+ - `type`: `SendEmail`
98
+ - `emailRecipients`: `{triggerData.contact.email}`
99
+ - `emailSubject`: "Welcome to Our Platform, {triggerData.contact.firstName}!"
100
+ - `emailContent`: "Hello {triggerData.contact.firstName}, welcome aboard..."
101
+
102
+ This powerful system allows you to automate virtually any process, from simple notifications to complex data transformations and integrations with external services.