data-primals-engine 1.4.3 → 1.5.1
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 +913 -867
- package/client/package-lock.json +49 -0
- package/client/package.json +1 -0
- package/client/src/AddWidgetTypeModal.jsx +47 -43
- package/client/src/App.jsx +3 -7
- package/client/src/App.scss +25 -3
- package/client/src/AssistantChat.jsx +363 -323
- package/client/src/AssistantChat.scss +30 -12
- package/client/src/Dashboard.jsx +480 -396
- package/client/src/Dashboard.scss +1 -1
- package/client/src/DashboardHtmlViewItem.jsx +147 -0
- package/client/src/DashboardView.jsx +104 -19
- package/client/src/DataEditor.jsx +12 -5
- package/client/src/DataLayout.jsx +805 -762
- package/client/src/DataLayout.scss +14 -0
- package/client/src/DataTable.jsx +63 -77
- package/client/src/Dialog.scss +1 -1
- package/client/src/Field.jsx +591 -322
- package/client/src/FlexDataRenderer.jsx +2 -0
- package/client/src/FlexTreeUtils.js +1 -1
- package/client/src/FlexViewCard.jsx +44 -0
- package/client/src/HistoryDialog.jsx +47 -14
- package/client/src/HtmlViewBuilderModal.jsx +91 -0
- package/client/src/HtmlViewBuilderModal.scss +18 -0
- package/client/src/HtmlViewCard.jsx +44 -0
- package/client/src/HtmlViewCard.scss +35 -0
- package/client/src/KPIDialog.jsx +11 -1
- package/client/src/KanbanCard.jsx +1 -2
- package/client/src/ModelCreator.jsx +6 -6
- package/client/src/ModelCreatorField.jsx +74 -31
- package/client/src/ModelList.jsx +93 -54
- package/client/src/Notification.jsx +136 -136
- package/client/src/Notification.scss +0 -18
- package/client/src/Pagination.jsx +5 -3
- package/client/src/RelationField.jsx +354 -258
- package/client/src/RelationSelectorWidget.jsx +173 -0
- package/client/src/constants.js +1 -1
- package/client/src/contexts/ModelContext.jsx +10 -1
- package/client/src/contexts/UIContext.jsx +72 -63
- package/client/src/filter.js +262 -212
- package/client/src/hooks/useTutorials.jsx +62 -65
- package/client/src/hooks/useValidation.js +75 -0
- package/client/src/translations.js +26 -24
- package/package.json +3 -1
- package/perf/README.md +147 -0
- package/perf/artillery-hooks.js +37 -0
- package/perf/perf-shot-hardwork.yml +84 -0
- package/perf/perf-shot-search.yml +45 -0
- package/perf/setup.yml +26 -0
- package/server.js +1 -1
- package/src/constants.js +3 -28
- package/src/core.js +15 -1
- package/src/data.js +1 -1
- package/src/defaultModels.js +63 -7
- package/src/email.js +5 -2
- package/src/engine.js +5 -3
- package/src/filter.js +5 -3
- package/src/i18n.js +710 -10
- package/src/modules/assistant/assistant.js +151 -19
- package/src/modules/bucket.js +14 -16
- package/src/modules/data/data.backup.js +11 -8
- package/src/modules/data/data.core.js +118 -92
- package/src/modules/data/data.history.js +531 -492
- package/src/modules/data/data.js +9 -56
- package/src/modules/data/data.operations.js +3282 -2999
- package/src/modules/data/data.relations.js +686 -686
- package/src/modules/data/data.routes.js +118 -24
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/data/data.validation.js +85 -3
- package/src/modules/file.js +247 -236
- package/src/modules/user.js +4 -1
- package/src/modules/workflow.js +9 -10
- package/src/openai.jobs.js +2 -0
- package/src/packs.js +5482 -5461
- package/src/providers.js +22 -7
- package/test/data.integration.test.js +136 -2
- package/test/import_export.integration.test.js +1 -1
package/client/src/filter.js
CHANGED
|
@@ -1,213 +1,263 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Finds the matching {{/each}} for a {{#each ...}} tag at a given position.
|
|
3
|
+
* It correctly handles nested {{#each}} blocks.
|
|
4
|
+
* @param {string} template - The template string.
|
|
5
|
+
* @param {number} startIndex - The starting position of the opening {{#each ...}} tag.
|
|
6
|
+
* @returns {number} The position of the start of the matching {{/each}} tag, or -1 if not found.
|
|
7
|
+
*/
|
|
8
|
+
function findMatchingEndEach(template, startIndex) {
|
|
9
|
+
const openTag = '{{#each';
|
|
10
|
+
const closeTag = '{{/each}}';
|
|
11
|
+
let level = 1;
|
|
12
|
+
let searchPos = startIndex + openTag.length;
|
|
13
|
+
|
|
14
|
+
while (level > 0 && searchPos < template.length) {
|
|
15
|
+
const nextClose = template.indexOf(closeTag, searchPos);
|
|
16
|
+
if (nextClose === -1) {
|
|
17
|
+
return -1; // Unmatched opening tag
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const nextOpen = template.indexOf(openTag, searchPos);
|
|
21
|
+
|
|
22
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
23
|
+
level++;
|
|
24
|
+
searchPos = nextOpen + openTag.length;
|
|
25
|
+
} else {
|
|
26
|
+
level--;
|
|
27
|
+
searchPos = nextClose + closeTag.length;
|
|
28
|
+
if (level === 0) {
|
|
29
|
+
return nextClose; // Found the matching closing tag
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return -1; // No matching closing tag found
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Renders a simple HTML template by substituting placeholders.
|
|
38
|
+
* This is a robust version that correctly handles nested {{#each}} loops.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} templateString The template with placeholders.
|
|
41
|
+
* @param {Array<object>|object} data The data to inject.
|
|
42
|
+
* @returns {string} The rendered HTML string.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Renders a simple HTML template by substituting placeholders.
|
|
47
|
+
* This is a robust version that correctly handles nested {{#each}} loops.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} templateString The template with placeholders.
|
|
50
|
+
* @param {Array<object>|object} data The data to inject.
|
|
51
|
+
* @returns {string} The rendered HTML string.
|
|
52
|
+
*/
|
|
53
|
+
export const convertInputValue = (value) => {
|
|
54
|
+
if (typeof value !== 'string') return value;
|
|
55
|
+
|
|
56
|
+
if (value.startsWith('$')) {
|
|
57
|
+
// C'est une référence de champ
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Si la valeur est une chaîne entourée de guillemets, on la conserve telle quelle
|
|
62
|
+
if (/^['"].*['"]$/.test(value)) {
|
|
63
|
+
return value.slice(1, -1); // Retire les guillemets
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Conversion en nombre si possible
|
|
67
|
+
if (!isNaN(value) && value.trim() !== '') {
|
|
68
|
+
return Number(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (value.toLowerCase() === 'null') return null;
|
|
72
|
+
|
|
73
|
+
// Conversion en booléen pour 'true' et 'false'
|
|
74
|
+
if (value.toLowerCase() === 'true') return true;
|
|
75
|
+
if (value.toLowerCase() === 'false') return false;
|
|
76
|
+
|
|
77
|
+
// Par défaut, retourne la chaîne telle quelle
|
|
78
|
+
return value;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const parseNode = (node, pathPrefix = '$') => {
|
|
82
|
+
if (node && node.path && Array.isArray(node.path)) {
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const keys = Object.keys(node);
|
|
91
|
+
if (keys.length === 0) return null;
|
|
92
|
+
|
|
93
|
+
// Gestion des $find
|
|
94
|
+
if (keys.length === 1 && keys[0] === '$find') {
|
|
95
|
+
const findCondition = node.$find;
|
|
96
|
+
const parsedCondition = parseNode(findCondition, pathPrefix === '$' ? '$$this.' : pathPrefix);
|
|
97
|
+
|
|
98
|
+
if (parsedCondition) {
|
|
99
|
+
return {
|
|
100
|
+
...parsedCondition,
|
|
101
|
+
path: parsedCondition.path || [],
|
|
102
|
+
op: '$find',
|
|
103
|
+
value: parsedCondition
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Gestion des opérateurs logiques
|
|
109
|
+
if (keys.length === 1 && (keys[0] === '$and' || keys[0] === '$or')) {
|
|
110
|
+
const children = node[keys[0]]
|
|
111
|
+
.map(childNode => parseNode(childNode, pathPrefix))
|
|
112
|
+
.filter(Boolean);
|
|
113
|
+
return { [keys[0]]: children };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Gestion des opérateurs de comparaison
|
|
117
|
+
if (keys[0].startsWith('$')) {
|
|
118
|
+
const op = keys[0];
|
|
119
|
+
const args = node[op];
|
|
120
|
+
|
|
121
|
+
// Cas spécial pour regexMatch
|
|
122
|
+
if (op === '$regexMatch' && typeof args === 'object') {
|
|
123
|
+
const transformExpr = reverseTransformExpr(args.input, pathPrefix === '$$this.');
|
|
124
|
+
return {
|
|
125
|
+
path: transformExpr.path || [args.input.substring(pathPrefix.length).split('.')],
|
|
126
|
+
op: '$regex',
|
|
127
|
+
value: args.regex,
|
|
128
|
+
transform: transformExpr.transform
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Cas spécial pour exists
|
|
133
|
+
if ((op === '$ne' || op === '$eq') && Array.isArray(args) && args.length === 2 &&
|
|
134
|
+
typeof args[0] === 'object' && args[0].$type && args[1] === 'missing') {
|
|
135
|
+
const transformExpr = reverseTransformExpr(args[0].$type, pathPrefix === '$$this.');
|
|
136
|
+
return {
|
|
137
|
+
path: transformExpr.path || [args[0].$type.substring(pathPrefix.length).split('.')],
|
|
138
|
+
op: '$exists',
|
|
139
|
+
value: op === '$ne',
|
|
140
|
+
transform: transformExpr.transform
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Cas normal avec transformation
|
|
145
|
+
if (Array.isArray(args) && args.length === 2) {
|
|
146
|
+
const transformExpr = reverseTransformExpr(args[0], pathPrefix === '$$this.');
|
|
147
|
+
return {
|
|
148
|
+
path: transformExpr.path || [args[0].substring(pathPrefix.length).split('.')],
|
|
149
|
+
op,
|
|
150
|
+
value: args[1],
|
|
151
|
+
transform: transformExpr.transform
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Cas des transformations pures
|
|
156
|
+
const transformExpr = reverseTransformExpr(node, pathPrefix === '$$this.');
|
|
157
|
+
if (transformExpr && transformExpr.transform) {
|
|
158
|
+
return {
|
|
159
|
+
path: transformExpr.path || [keys[0].substring(1)],
|
|
160
|
+
transform: transformExpr.transform
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Gestion des champs simples
|
|
166
|
+
const path = keys[0];
|
|
167
|
+
if (typeof path === 'string' && !path.startsWith('$')) {
|
|
168
|
+
const condition = node[path];
|
|
169
|
+
if (typeof condition === 'object' && condition !== null && !Array.isArray(condition)) {
|
|
170
|
+
if (condition.$find) {
|
|
171
|
+
const parsedInnerNode = parseNode(condition.$find, '$$this.');
|
|
172
|
+
if (parsedInnerNode) {
|
|
173
|
+
return {
|
|
174
|
+
...parsedInnerNode,
|
|
175
|
+
path: [path, ...(parsedInnerNode.path || [])]
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
const parsedCondition = parseNode(condition, pathPrefix);
|
|
180
|
+
if (parsedCondition) {
|
|
181
|
+
return {
|
|
182
|
+
...parsedCondition,
|
|
183
|
+
path: [path, ...(parsedCondition.path || [])]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
return {
|
|
189
|
+
path: [path],
|
|
190
|
+
op: '$eq',
|
|
191
|
+
value: condition
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
export const buildRootFromExpr = (query) => {
|
|
201
|
+
if (!query || typeof query !== 'object' || Array.isArray(query)) {
|
|
202
|
+
return { $and: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const exprToParse = query.$expr || query;
|
|
206
|
+
const parsedRoot = parseNode(exprToParse);
|
|
207
|
+
|
|
208
|
+
if (!parsedRoot) return { $and: [] };
|
|
209
|
+
if (parsedRoot.$and || parsedRoot.$or) return parsedRoot;
|
|
210
|
+
|
|
211
|
+
return { $and: [parsedRoot] };
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const pagedFilterToMongoConds = (pagedFilters, model) => {
|
|
215
|
+
const modelFilter = pagedFilters?.[model.name] || {};
|
|
216
|
+
const filterKeys = Object.keys(modelFilter);
|
|
217
|
+
|
|
218
|
+
// Si le filtre est vide, on retourne un tableau vide.
|
|
219
|
+
if (filterKeys.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Détecte si c'est un filtre avancé (contient des opérateurs logiques de haut niveau comme $and, $or, etc.)
|
|
224
|
+
const isAdvancedFilter = filterKeys.some(key => key.startsWith('$'));
|
|
225
|
+
|
|
226
|
+
if (isAdvancedFilter) {
|
|
227
|
+
// C'est un filtre avancé du ConditionBuilder.
|
|
228
|
+
// Il est déjà une condition MongoDB complète. On le met dans un tableau pour l'insérer dans le $and global.
|
|
229
|
+
// Si le filtre avancé est vide (ex: { $and: [] }), on le retourne quand même pour que le ConditionBuilder puisse s'initialiser correctement.
|
|
230
|
+
return [modelFilter];
|
|
231
|
+
} else {
|
|
232
|
+
// C'est un filtre simple (par colonne).
|
|
233
|
+
// On transforme { champ1: cond1, champ2: cond2 } en [{ champ1: cond1 }, { champ2: cond2 }]
|
|
234
|
+
const c = [];
|
|
235
|
+
filterKeys.forEach(fieldName => {
|
|
236
|
+
if (model.fields.some(f => f.name === fieldName)) {
|
|
237
|
+
const condition = modelFilter[fieldName];
|
|
238
|
+
if (condition && typeof condition === 'object' && Object.keys(condition).length > 0) {
|
|
239
|
+
c.push(condition);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
return c;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Remplace les placeholders dans un objet filtre.
|
|
249
|
+
* Gère {{userId}}.
|
|
250
|
+
*/
|
|
251
|
+
export const processFilterPlaceholders = (filter, user) => {
|
|
252
|
+
const processedFilter = JSON.parse(JSON.stringify(filter));
|
|
253
|
+
for (const key in processedFilter) {
|
|
254
|
+
if (processedFilter[key] === '{{userId}}') {
|
|
255
|
+
// Dans le système Primals, le champ utilisateur est souvent `_user`
|
|
256
|
+
// et contient le nom d'utilisateur, pas l'ID. Adaptez si besoin.
|
|
257
|
+
processedFilter[key] = user.username;
|
|
258
|
+
} else if (typeof processedFilter[key] === 'object' && processedFilter[key] !== null) {
|
|
259
|
+
processedFilter[key] = processFilterPlaceholders(processedFilter[key], user);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return processedFilter;
|
|
213
263
|
};
|