data-primals-engine 1.4.1 → 1.4.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 +37 -22
- package/client/package-lock.json +33 -0
- package/client/package.json +1 -0
- package/client/src/App.scss +12 -4
- package/client/src/AssistantChat.jsx +48 -5
- package/client/src/AssistantChat.scss +0 -1
- package/client/src/ChartConfigModal.jsx +34 -9
- package/client/src/ConditionBuilder.jsx +1 -1
- package/client/src/ConditionBuilder2.jsx +2 -1
- package/client/src/Dashboard.jsx +396 -349
- package/client/src/DashboardChart.jsx +91 -12
- package/client/src/DataEditor.jsx +376 -368
- package/client/src/DataLayout.jsx +3 -2
- package/client/src/DataTable.jsx +60 -31
- package/client/src/Field.jsx +1788 -1782
- package/client/src/GeolocationField.jsx +94 -0
- package/client/src/ModelCreatorField.jsx +1 -0
- package/client/src/RelationField.jsx +2 -2
- package/client/src/constants.js +2 -2
- package/client/src/contexts/UIContext.jsx +5 -2
- package/client/src/filter.js +0 -155
- package/client/src/translations.js +16828 -16750
- package/package.json +10 -2
- package/src/config.js +2 -2
- package/src/constants.js +262 -4
- package/src/core.js +21 -3
- package/src/defaultModels.js +12 -12
- package/src/engine.js +7 -1
- package/src/events.js +4 -3
- package/src/filter.js +272 -260
- package/src/i18n.js +187 -177
- package/src/middlewares/middleware-mongodb.js +3 -1
- package/src/modules/assistant/assistant.js +131 -42
- package/src/modules/bucket.js +3 -2
- package/src/modules/data/data.backup.js +374 -0
- package/src/modules/data/data.core.js +2 -1
- package/src/modules/data/data.history.js +11 -8
- package/src/modules/data/data.js +190 -4551
- package/src/modules/data/data.operations.js +3000 -0
- package/src/modules/data/data.relations.js +687 -0
- package/src/modules/data/data.routes.js +132 -38
- package/src/modules/data/data.scheduling.js +274 -0
- package/src/modules/data/data.validation.js +248 -0
- package/src/modules/data/index.js +29 -1
- package/src/modules/user.js +2 -1
- package/src/modules/workflow.js +3 -2
- package/src/services/stripe.js +3 -2
- package/swagger-en.yml +133 -0
- package/test/model.integration.test.js +377 -221
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { MapContainer, TileLayer, Marker, useMapEvents, Popup } from 'react-leaflet';
|
|
3
|
+
import 'leaflet/dist/leaflet.css';
|
|
4
|
+
import L from 'leaflet';
|
|
5
|
+
|
|
6
|
+
// Fix for default marker icon issue with webpack/parcel
|
|
7
|
+
delete L.Icon.Default.prototype._getIconUrl;
|
|
8
|
+
|
|
9
|
+
L.Icon.Default.mergeOptions({
|
|
10
|
+
iconRetinaUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png',
|
|
11
|
+
iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png',
|
|
12
|
+
shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const LocationMarker = ({ position, setPosition, name, onChange, value }) => {
|
|
17
|
+
const map = useMapEvents({
|
|
18
|
+
click(e) {
|
|
19
|
+
const { lat, lng } = e.latlng;
|
|
20
|
+
const newPos = [lat, lng];
|
|
21
|
+
setPosition(newPos);
|
|
22
|
+
map.flyTo(newPos, map.getZoom());
|
|
23
|
+
onChange({
|
|
24
|
+
name: name,
|
|
25
|
+
value: {
|
|
26
|
+
type: 'Point',
|
|
27
|
+
coordinates: [lng, lat]
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
locationfound(e) {
|
|
32
|
+
const { lat, lng } = e.latlng;
|
|
33
|
+
const newPos = [lat, lng];
|
|
34
|
+
setPosition(newPos);
|
|
35
|
+
map.flyTo(newPos, map.getZoom());
|
|
36
|
+
onChange({
|
|
37
|
+
name: name,
|
|
38
|
+
value: {
|
|
39
|
+
type: 'Point',
|
|
40
|
+
coordinates: [lng, lat]
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// If there's no value, try to locate user
|
|
48
|
+
if (!value) {
|
|
49
|
+
map.locate();
|
|
50
|
+
}
|
|
51
|
+
}, [map, value]);
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
return position === null ? null : (
|
|
55
|
+
<Marker position={position}>
|
|
56
|
+
<Popup>Selected location</Popup>
|
|
57
|
+
</Marker>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const GeolocationField = ({ value, onChange, name }) => {
|
|
62
|
+
// Default to a central location if no value is provided, e.g., Paris
|
|
63
|
+
const initialPosition = (value && value.coordinates && value.coordinates.length === 2)
|
|
64
|
+
? [value.coordinates[1], value.coordinates[0]] // Leaflet uses [lat, lng], GeoJSON uses [lng, lat]
|
|
65
|
+
: [48.8566, 2.3522]; // Default to Paris
|
|
66
|
+
|
|
67
|
+
const [position, setPosition] = useState(initialPosition);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (value && value.coordinates && value.coordinates.length === 2) {
|
|
71
|
+
const newPos = [value.coordinates[1], value.coordinates[0]];
|
|
72
|
+
if (position[0] !== newPos[0] || position[1] !== newPos[1]) {
|
|
73
|
+
setPosition(newPos);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// If value is cleared, reset to default. The map.locate() will try to find the user.
|
|
77
|
+
setPosition([48.8566, 2.3522]);
|
|
78
|
+
}
|
|
79
|
+
}, [value]);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div style={{ height: '300px', width: '100%' }}>
|
|
83
|
+
<MapContainer center={position} zoom={13} scrollWheelZoom={true} style={{ height: '100%', width: '100%' }}>
|
|
84
|
+
<TileLayer
|
|
85
|
+
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
86
|
+
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
87
|
+
/>
|
|
88
|
+
<LocationMarker position={position} setPosition={setPosition} name={name} onChange={onChange} value={value} />
|
|
89
|
+
</MapContainer>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default GeolocationField;
|
|
@@ -117,6 +117,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
117
117
|
{label: t("field.array"), value: "array"},
|
|
118
118
|
{label: t("field.relation"), value: "relation"},
|
|
119
119
|
{label: t("field.file"), value: "file"},
|
|
120
|
+
{label: t("field.geolocation"), value: "geolocation"},
|
|
120
121
|
{label: t("field.code"), value: "code"},
|
|
121
122
|
{label: t("field.calculated", "calculated data"), value: "calculated"},
|
|
122
123
|
{label: t("field.cronSchedule", "task schedule"), value: "cronSchedule"},
|
|
@@ -43,14 +43,14 @@ const RelationField = ({ field, help, onFocus, onBlur, onChange, value = null })
|
|
|
43
43
|
// Recherche sur les champs principaux (asMain)
|
|
44
44
|
model.fields.forEach(f => {
|
|
45
45
|
if (f.asMain && mainFieldsTypes.includes(f.type)) {
|
|
46
|
-
orConditions.push({
|
|
46
|
+
orConditions.push({"$regexMatch": {input: "$" + f.name, regex: searchValue}});
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
49
|
// Si aucun champ principal, recherche sur les champs texte
|
|
50
50
|
if (orConditions.length === 0) {
|
|
51
51
|
model.fields.forEach(f => {
|
|
52
52
|
if (["string", "string_t", "richtext", "url"].includes(f.type)) {
|
|
53
|
-
orConditions.push({
|
|
53
|
+
orConditions.push({"$regexMatch": {input: "$" + f.name, regex: searchValue}});
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
}
|
package/client/src/constants.js
CHANGED
|
@@ -60,11 +60,11 @@ export const profiles = {
|
|
|
60
60
|
"models": ['alert','endpoint','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role']
|
|
61
61
|
},
|
|
62
62
|
'company': {
|
|
63
|
-
"packs": ["E-commerce Starter Kit"],
|
|
63
|
+
"packs": ["Multilingual starter pack", "E-commerce Starter Kit"],
|
|
64
64
|
"models": ['alert','request','location', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'message', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard']
|
|
65
65
|
},
|
|
66
66
|
'engineer': {
|
|
67
|
-
"packs": ["AI Content Generation - Starter Pack"],
|
|
67
|
+
"packs": ["Multilingual starter pack", "AI Content Generation - Starter Pack"],
|
|
68
68
|
"models":['alert','endpoint','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -11,6 +11,8 @@ export const UIProvider = ({ children }) => {
|
|
|
11
11
|
const [isTourOpen, setIsTourOpen] = useState(false);
|
|
12
12
|
const [allTourSteps, setAllTourSteps] = useState({});
|
|
13
13
|
|
|
14
|
+
const [chartToAdd, setChartToAdd] = useState(null);
|
|
15
|
+
|
|
14
16
|
const [currentTour, setCurrentTour] = useLocalStorage("spotlight-tour", null);
|
|
15
17
|
// This is the single source of truth for tours that have been launched.
|
|
16
18
|
// It correctly reads from localStorage on initial load and persists any changes.
|
|
@@ -36,14 +38,15 @@ export const UIProvider = ({ children }) => {
|
|
|
36
38
|
launchedTours, setLaunchedTours, addLaunchedTour,
|
|
37
39
|
currentTour, setCurrentTour,
|
|
38
40
|
isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
|
|
39
|
-
tourStepIndex, setTourStepIndex
|
|
41
|
+
tourStepIndex, setTourStepIndex, chartToAdd, setChartToAdd
|
|
40
42
|
}), [isLocked,
|
|
41
43
|
setLocked,
|
|
42
44
|
currentTourSteps, setCurrentTourSteps,
|
|
43
45
|
launchedTours,setLaunchedTours,
|
|
44
46
|
currentTour, setCurrentTour,
|
|
45
47
|
isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
|
|
46
|
-
tourStepIndex, setTourStepIndex, addLaunchedTour
|
|
48
|
+
tourStepIndex, setTourStepIndex, addLaunchedTour,
|
|
49
|
+
chartToAdd, setChartToAdd]);
|
|
47
50
|
|
|
48
51
|
return (
|
|
49
52
|
<UIContext.Provider value={contextValue}>
|
package/client/src/filter.js
CHANGED
|
@@ -1,159 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
export const MONGO_CALC_OPERATORS = {
|
|
3
|
-
$find: {
|
|
4
|
-
label: 'Find in relation',
|
|
5
|
-
description: 'Recherche dans une relation/tableau',
|
|
6
|
-
supportsNesting: true,
|
|
7
|
-
multi: false,
|
|
8
|
-
isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
|
|
9
|
-
},
|
|
10
|
-
'$regexMatch': {
|
|
11
|
-
label: 'Find by regex',
|
|
12
|
-
description: 'Find a string matching a regular expression (Ecmascript)',
|
|
13
|
-
args: 2, // input et regex
|
|
14
|
-
specialStructure: true // Indique une structure spéciale
|
|
15
|
-
},
|
|
16
|
-
$and: { label: 'Et (and)', description: 'Renvoie vrai si toutes les conditions sont vérifiées', args: 1, multi: true },
|
|
17
|
-
$or: { label: 'Ou (or)', description: 'Renvoie vrai si au moins une des conditions est vérifiée.', args: 1, multi: true },
|
|
18
|
-
$not: { label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true },
|
|
19
|
-
$nor: { label: 'Ou Non (nor)', description: 'Renvoie vrai si aucune des conditions n\'est vérifiée', args: 1, multi: true },
|
|
20
|
-
$eq: { label: '=', multi: false, args: 2 },
|
|
21
|
-
$ne: { label: '!=', multi: false, args: 2 },
|
|
22
|
-
$gt: { label: '>', multi: false, args: 2 },
|
|
23
|
-
$gte: { label: '>=', multi: false, args: 2 },
|
|
24
|
-
$lt: { label: '<', multi: false, args: 2 },
|
|
25
|
-
$lte: { label: '<=', multi: false, args: 2 },
|
|
26
|
-
$in: { label: 'in', multi: true, args: 2 },
|
|
27
|
-
$nin: { label: 'nin', multi: true, args: 2 },
|
|
28
|
-
$all: { label: 'afll', multi: true },
|
|
29
|
-
$size: {
|
|
30
|
-
label: 'size',
|
|
31
|
-
multi: false,
|
|
32
|
-
description: 'Renvoie le nombre d\'éléments dans un tableau',
|
|
33
|
-
disableAdvancedValue: true
|
|
34
|
-
},
|
|
35
|
-
$elemMatch: { label: 'elemMatch', multi: true },
|
|
36
|
-
$type: {
|
|
37
|
-
label: 'type',
|
|
38
|
-
description: 'Renvoie le type BSON de l\'élément',
|
|
39
|
-
multi: false,
|
|
40
|
-
disableAdvancedValue: true
|
|
41
|
-
},
|
|
42
|
-
$add: { label: '+', multi: true },
|
|
43
|
-
$subtract: { label: '-', multi: false, args: 2 },
|
|
44
|
-
$multiply: { label: '*', multi: true },
|
|
45
|
-
$divide: { label: '/', multi: false, args: 2 },
|
|
46
|
-
$mod: { label: '%', multi: false, args: 2 },
|
|
47
|
-
$pow: { label: 'pow', multi: false },
|
|
48
|
-
$sqrt: { label: 'sqrt', multi: false },
|
|
49
|
-
$exp: { label: 'exp', multi: false },
|
|
50
|
-
$abs: { label: 'abs', multi: false },
|
|
51
|
-
$ceil: { label: 'ceil', multi: false },
|
|
52
|
-
$floor: { label: 'floor', multi: false },
|
|
53
|
-
$ln: { label: 'ln', multi: false },
|
|
54
|
-
$log10: { label: 'log', multi: false },
|
|
55
|
-
$concat: { label: 'concat', multi: true },
|
|
56
|
-
|
|
57
|
-
// Date-specific
|
|
58
|
-
$year: { label: 'year', multi: false, isDate: true },
|
|
59
|
-
$month: { label: 'month', multi: false, isDate: true },
|
|
60
|
-
$dayOfMonth: { label: 'dayOfMonth', multi: false, isDate: true },
|
|
61
|
-
$hour: { label: 'hour', multi: false, isDate: true },
|
|
62
|
-
$minute: { label: 'minute', multi: false, isDate: true },
|
|
63
|
-
$second: { label: 'second', multi: false, isDate: true },
|
|
64
|
-
$millisecond: { label: 'ms', multi: false, isDate: true },
|
|
65
|
-
// Date operators
|
|
66
|
-
$dateAdd: {
|
|
67
|
-
label: 'Add to date',
|
|
68
|
-
description: 'Adds a duration to a date',
|
|
69
|
-
isDate: true,
|
|
70
|
-
specialStructure: true,
|
|
71
|
-
args: [
|
|
72
|
-
{ name: 'startDate', label: 'Start Date', type: 'date' },
|
|
73
|
-
{ name: 'unit', label: 'Unit', type: 'select', options: ['year', 'month', 'day', 'hour', 'minute', 'second'] },
|
|
74
|
-
{ name: 'amount', label: 'Amount', type: 'number' },
|
|
75
|
-
{ name: 'timezone', label: 'Timezone', type: 'text', optional: true }
|
|
76
|
-
]
|
|
77
|
-
},
|
|
78
|
-
$dateSubtract: {
|
|
79
|
-
label: 'Subtract from date',
|
|
80
|
-
description: 'Subtracts a duration from a date',
|
|
81
|
-
isDate: true,
|
|
82
|
-
specialStructure: true,
|
|
83
|
-
args: [
|
|
84
|
-
{ name: 'startDate', label: 'Start Date', type: 'date' },
|
|
85
|
-
{ name: 'unit', label: 'Unit', type: 'select', options: ['year', 'month', 'day', 'hour', 'minute', 'second'] },
|
|
86
|
-
{ name: 'amount', label: 'Amount', type: 'number' },
|
|
87
|
-
{ name: 'timezone', label: 'Timezone', type: 'text', optional: true }
|
|
88
|
-
]
|
|
89
|
-
},
|
|
90
|
-
$dateDiff: {
|
|
91
|
-
label: 'Date Difference',
|
|
92
|
-
description: 'Calculates the difference between two dates in specified units',
|
|
93
|
-
isDate: true,
|
|
94
|
-
specialStructure: true,
|
|
95
|
-
args: [
|
|
96
|
-
{
|
|
97
|
-
name: 'startDate',
|
|
98
|
-
label: 'Start Date',
|
|
99
|
-
type: 'date',
|
|
100
|
-
description: 'The starting date (inclusive)'
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'endDate',
|
|
104
|
-
label: 'End Date',
|
|
105
|
-
type: 'date',
|
|
106
|
-
description: 'The ending date (exclusive)'
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'unit',
|
|
110
|
-
label: 'Unit',
|
|
111
|
-
type: 'select',
|
|
112
|
-
options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
|
|
113
|
-
description: 'The unit for the result'
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
name: 'timezone',
|
|
117
|
-
label: 'Timezone',
|
|
118
|
-
type: 'text',
|
|
119
|
-
optional: true,
|
|
120
|
-
description: 'The timezone (e.g. "Europe/Paris")'
|
|
121
|
-
}
|
|
122
|
-
]
|
|
123
|
-
},
|
|
124
|
-
$dateToString: {
|
|
125
|
-
label: 'Format date as string',
|
|
126
|
-
description: 'Formats a date as a string',
|
|
127
|
-
isDate: true,
|
|
128
|
-
specialStructure: true,
|
|
129
|
-
args: [
|
|
130
|
-
{
|
|
131
|
-
name: 'date',
|
|
132
|
-
label: 'Date',
|
|
133
|
-
type: 'date'
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: 'format',
|
|
137
|
-
label: 'Format',
|
|
138
|
-
optional: true,
|
|
139
|
-
type: 'text'
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'timezone',
|
|
143
|
-
label: 'Timezone',
|
|
144
|
-
type: 'text',
|
|
145
|
-
optional: true,
|
|
146
|
-
description: 'The timezone (e.g. "Europe/Paris")'
|
|
147
|
-
}
|
|
148
|
-
]
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
// Converters
|
|
152
|
-
$toBool: { label: 'toBool', multi: false, converter: true },
|
|
153
|
-
$toString: { label: 'toString', multi: false, converter: true },
|
|
154
|
-
$toInt: { label: 'toInt', multi: false, converter: true },
|
|
155
|
-
$toDouble: { label: 'toDouble', multi: false, converter: true }
|
|
156
|
-
};
|
|
157
2
|
|
|
158
3
|
export const convertInputValue = (value) => {
|
|
159
4
|
if (typeof value !== 'string') return value;
|