data-primals-engine 1.4.2 → 1.5.0
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 +878 -856
- package/client/package-lock.json +82 -0
- package/client/package.json +2 -0
- package/client/src/App.jsx +1 -1
- package/client/src/App.scss +25 -7
- package/client/src/AssistantChat.scss +3 -2
- package/client/src/ConditionBuilder.jsx +1 -1
- package/client/src/ConditionBuilder2.jsx +2 -1
- package/client/src/DashboardView.jsx +569 -569
- package/client/src/DataEditor.jsx +376 -368
- package/client/src/DataLayout.jsx +4 -10
- package/client/src/DataTable.jsx +858 -817
- package/client/src/Field.jsx +1825 -1784
- package/client/src/FlexDataRenderer.jsx +2 -0
- package/client/src/FlexTreeUtils.js +1 -1
- package/client/src/GeolocationField.jsx +94 -0
- package/client/src/KPIDialog.jsx +11 -1
- package/client/src/ModelCreator.jsx +1 -2
- package/client/src/ModelCreatorField.jsx +24 -27
- package/client/src/ModelList.jsx +1 -1
- package/client/src/RelationField.jsx +2 -2
- package/client/src/constants.js +3 -3
- package/client/src/filter.js +0 -155
- package/client/src/hooks/useTutorials.jsx +62 -65
- package/client/src/translations.js +14 -2
- package/package.json +2 -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 +264 -31
- package/src/core.js +15 -1
- package/src/data.js +1 -1
- package/src/defaultModels.js +1544 -1540
- package/src/email.js +5 -2
- package/src/engine.js +10 -3
- package/src/filter.js +274 -260
- package/src/i18n.js +187 -177
- package/src/modules/assistant/assistant.js +3 -1
- package/src/modules/bucket.js +12 -15
- package/src/modules/data/data.backup.js +11 -8
- package/src/modules/data/data.core.js +2 -1
- package/src/modules/data/data.js +6 -3
- package/src/modules/data/data.operations.js +610 -168
- package/src/modules/data/data.routes.js +1821 -1785
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/data/data.validation.js +7 -1
- package/src/modules/file.js +4 -2
- 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 +22 -5
- package/src/providers.js +22 -7
- package/swagger-en.yml +133 -0
- package/test/data.integration.test.js +1060 -981
- package/test/import_export.integration.test.js +1 -1
- package/test/model.integration.test.js +377 -221
|
@@ -33,8 +33,10 @@ const formatDate = (value, type, t) => {
|
|
|
33
33
|
|
|
34
34
|
const FlexDataRenderer = ({ value, fieldDefinition,data }) => {
|
|
35
35
|
const { t, i18n } = useTranslation();
|
|
36
|
+
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
36
37
|
const {models} = useModelContext()
|
|
37
38
|
const {me} = useAuthContext()
|
|
39
|
+
|
|
38
40
|
// 'translatedLabel' is added by getFieldDefinitionByPath
|
|
39
41
|
const field = fieldDefinition;
|
|
40
42
|
const translatedLabel = t('model_'+field.name, field.name);
|
|
@@ -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;
|
package/client/src/KPIDialog.jsx
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Dialog } from './Dialog.jsx'; // Assure-toi que Dialog est importé
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import {FaAd, FaPlus} from "react-icons/fa";
|
|
5
|
+
import {getUserHash} from "../../src/data.js";
|
|
6
|
+
import {useNavigate} from "react-router-dom";
|
|
7
|
+
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
4
8
|
|
|
5
9
|
function KPIDialog({ availableKpis = [], onAddKpi, onClose }) {
|
|
6
10
|
const { t } = useTranslation();
|
|
7
11
|
const [searchTerm, setSearchTerm] = useState('');
|
|
8
|
-
|
|
12
|
+
const nav = useNavigate()
|
|
13
|
+
const {me} = useAuthContext()
|
|
9
14
|
const filteredKpis = (availableKpis || []).filter(kpi =>
|
|
10
15
|
kpi.name.value.toLowerCase().includes(searchTerm.toLowerCase())
|
|
11
16
|
);
|
|
@@ -32,6 +37,11 @@ function KPIDialog({ availableKpis = [], onAddKpi, onClose }) {
|
|
|
32
37
|
<li>{t('dashboards.no_kpi_found', 'Aucun KPI disponible ou correspondant.')}</li>
|
|
33
38
|
)}
|
|
34
39
|
</ul>
|
|
40
|
+
<div className={"flex mg-2"}>
|
|
41
|
+
<button onClick={() => {
|
|
42
|
+
nav(`/user/${getUserHash(me)}/?model=kpi`);
|
|
43
|
+
}} className={"btn"}><FaPlus />{t('dashboards.addKpi', 'Nouvel indicateur KPI')}</button>
|
|
44
|
+
</div>
|
|
35
45
|
</Dialog>
|
|
36
46
|
);
|
|
37
47
|
}
|
|
@@ -660,11 +660,10 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
660
660
|
{/* Boutons d'action, visibles si un modèle est affiché ou en mode manuel */}
|
|
661
661
|
{(showModel || !useAI || initialModel) && (
|
|
662
662
|
<div className="actions flex">
|
|
663
|
-
|
|
663
|
+
|
|
664
664
|
<Button type="button" onClick={handleAddField}>
|
|
665
665
|
<FaPlus /> <Trans i18nKey={"btns.addField"}>Ajouter un champ</Trans>
|
|
666
666
|
</Button>
|
|
667
|
-
)}
|
|
668
667
|
|
|
669
668
|
<Button type="submit">
|
|
670
669
|
<FaSave />
|
|
@@ -72,7 +72,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
72
72
|
newFields[index].name = e.target.value;
|
|
73
73
|
setFields(newFields);
|
|
74
74
|
}}
|
|
75
|
-
help={t('modelcreator.name.hint')}
|
|
76
75
|
required
|
|
77
76
|
after={!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
|
|
78
77
|
<Button type={"button"} className={"btn-form btn-last"}
|
|
@@ -117,6 +116,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
117
116
|
{label: t("field.array"), value: "array"},
|
|
118
117
|
{label: t("field.relation"), value: "relation"},
|
|
119
118
|
{label: t("field.file"), value: "file"},
|
|
119
|
+
{label: t("field.geolocation"), value: "geolocation"},
|
|
120
120
|
{label: t("field.code"), value: "code"},
|
|
121
121
|
{label: t("field.calculated", "calculated data"), value: "calculated"},
|
|
122
122
|
{label: t("field.cronSchedule", "task schedule"), value: "cronSchedule"},
|
|
@@ -211,10 +211,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
211
211
|
|
|
212
212
|
<div className="flex flex-no-wrap">
|
|
213
213
|
{hint('modelcreator.relationFilter.hint')}
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
<div className="checkbox-label flex flex-1">
|
|
215
|
+
<CheckboxField
|
|
216
|
+
label={<Trans
|
|
217
|
+
i18nKey={"modelcreator.relationFilter"}>Filtre</Trans>}
|
|
218
218
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
219
219
|
checked={field.relationFilter !== undefined}
|
|
220
220
|
onChange={(e) => {
|
|
@@ -227,7 +227,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
227
227
|
setFields(newFields);
|
|
228
228
|
}}
|
|
229
229
|
/>
|
|
230
|
-
</
|
|
230
|
+
</div>
|
|
231
231
|
</div>)}
|
|
232
232
|
|
|
233
233
|
|
|
@@ -285,7 +285,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
285
285
|
newFields[index].multiline = e;
|
|
286
286
|
setFields(newFields);
|
|
287
287
|
}}
|
|
288
|
-
help={field.multiline && t('modelcreator.multiline.hint')}
|
|
289
288
|
/>
|
|
290
289
|
</div>
|
|
291
290
|
</div>
|
|
@@ -294,28 +293,26 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
294
293
|
|
|
295
294
|
{['string', 'string_t', 'richtext', 'richtext_t', 'email', 'phone', 'url', 'password', 'code'].includes(field.itemsType || field.type) && (
|
|
296
295
|
<>
|
|
297
|
-
<div className={"flex flex-no-wrap"}>
|
|
296
|
+
<div className={"flex flex-no-wrap field-bg"}>
|
|
298
297
|
{hint('modelcreator.maxlength.hint')}
|
|
299
|
-
<
|
|
300
|
-
<Trans i18nKey={"modelcreator.maxlength"}>Longueur
|
|
301
|
-
maximale :</Trans>
|
|
302
|
-
<NumberField
|
|
298
|
+
<div className={"flex-1"}><NumberField
|
|
303
299
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
304
300
|
step={1}
|
|
305
301
|
min={0}
|
|
302
|
+
label={<Trans i18nKey={"modelcreator.maxlength"}>Longueur
|
|
303
|
+
maximale :</Trans>}
|
|
306
304
|
className={"flex-1"}
|
|
307
305
|
value={field.maxlength}
|
|
308
306
|
onChange={(e) => {
|
|
309
307
|
const newFields = [...fields];
|
|
310
|
-
const val = parseInt(e, 10);
|
|
311
|
-
if (
|
|
308
|
+
const val = parseInt(e.target.value, 10);
|
|
309
|
+
if (isNaN(val))
|
|
312
310
|
newFields[index].maxlength = undefined;
|
|
313
311
|
else
|
|
314
312
|
newFields[index].maxlength = val;
|
|
315
313
|
setFields(newFields);
|
|
316
314
|
}}
|
|
317
|
-
|
|
318
|
-
</label>
|
|
315
|
+
/></div>
|
|
319
316
|
</div>
|
|
320
317
|
</>
|
|
321
318
|
)}
|
|
@@ -437,7 +434,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
437
434
|
newFields[index].required = e;
|
|
438
435
|
setFields(newFields);
|
|
439
436
|
}}
|
|
440
|
-
help={field.required && t('modelcreator.required.hint')}
|
|
441
437
|
/>
|
|
442
438
|
</div>
|
|
443
439
|
</div>
|
|
@@ -455,7 +451,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
455
451
|
newFields[index].unique = e;
|
|
456
452
|
setFields(newFields);
|
|
457
453
|
}}
|
|
458
|
-
help={field.unique && t('modelcreator.unique.hint')}
|
|
459
454
|
/>
|
|
460
455
|
</div>
|
|
461
456
|
</div>
|
|
@@ -554,7 +549,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
554
549
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
555
550
|
onChange={(e) => {
|
|
556
551
|
const newFields = [...fields];
|
|
557
|
-
|
|
558
552
|
newFields[index].default = e.value;
|
|
559
553
|
setFields(newFields);
|
|
560
554
|
}}
|
|
@@ -625,10 +619,12 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
625
619
|
|
|
626
620
|
{(['datetime', 'date'].includes(field.itemsType || field.type)) && (
|
|
627
621
|
<>
|
|
628
|
-
<div
|
|
622
|
+
<div
|
|
623
|
+
className="flex flex-no-wrap mg-item">
|
|
629
624
|
{hint('modelcreator.min.hint')}
|
|
630
|
-
<
|
|
631
|
-
|
|
625
|
+
<div className="flex field-bg flex-1">
|
|
626
|
+
<label className={"flex-1"}><Trans i18nKey={"modelcreator.min"}>Valeur minimale :</Trans></label>
|
|
627
|
+
<input
|
|
632
628
|
className={"flex-1"}
|
|
633
629
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
634
630
|
type={field.type === 'datetime' ? 'datetime-local' : field.type}
|
|
@@ -642,13 +638,16 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
642
638
|
}
|
|
643
639
|
setFields(newFields);
|
|
644
640
|
}}
|
|
645
|
-
|
|
641
|
+
/>
|
|
642
|
+
</div>
|
|
646
643
|
</div>
|
|
647
644
|
|
|
648
645
|
<div className="flex flex-no-wrap mg-item">
|
|
649
646
|
{hint('modelcreator.max.hint')}
|
|
650
|
-
<
|
|
651
|
-
|
|
647
|
+
<div className="flex field-bg flex-1">
|
|
648
|
+
<label className={"flex-1"}><Trans i18nKey={"modelcreator.max"}>Valeur maximale :</Trans></label>
|
|
649
|
+
<input
|
|
650
|
+
className={"flex-1"}
|
|
652
651
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
653
652
|
type={field.type === 'datetime' ? 'datetime-local' : field.type}
|
|
654
653
|
value={field.type === "number" ? (field.max + '').replace('.', ',') : field.max}
|
|
@@ -806,7 +805,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
806
805
|
newFields[index].asMain = e;
|
|
807
806
|
setFields(newFields);
|
|
808
807
|
}}
|
|
809
|
-
help={field.asMain && t('modelcreator.asMain.hint')}
|
|
810
808
|
/>
|
|
811
809
|
</div>
|
|
812
810
|
</div>)}
|
|
@@ -857,7 +855,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
857
855
|
newFields[index].hiddenable = e;
|
|
858
856
|
setFields(newFields);
|
|
859
857
|
}}
|
|
860
|
-
help={field.unique && t('modelcreator.hiddenable.hint')}
|
|
861
858
|
/>
|
|
862
859
|
</div>
|
|
863
860
|
</div>
|
package/client/src/ModelList.jsx
CHANGED
|
@@ -190,7 +190,7 @@ export function ModelList({ onModelSelect, onCreateModel, onImportModel, onEditM
|
|
|
190
190
|
key={'modelist' + model.name} onClick={() => generatedModels.some(g => g.name === model.name) ? onEditModel(model) : onModelSelect(model)}>
|
|
191
191
|
<div className="flex flex-center flex-fw">
|
|
192
192
|
<div
|
|
193
|
-
className="flex flex-1 flex-no-wrap break-word">
|
|
193
|
+
className="flex flex-1 flex-no-wrap break-word gap-2">
|
|
194
194
|
<div className={"icon"}>{IconComponent ? IconComponent : <></>}</div>
|
|
195
195
|
<div>{t(`model_${model.name}`, model.name)} {generatedModels.some(f => f.name === model.name) ? '(tmp)' : (mods.some(f => f.mod === model.name) ? `(${mods.find(f => f.mod === model.name).count})` : '')}</div></div>
|
|
196
196
|
<div className="btns">
|
|
@@ -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
|
@@ -57,14 +57,14 @@ export const profiles = {
|
|
|
57
57
|
}, // budget,
|
|
58
58
|
'developer': {
|
|
59
59
|
"packs": ["Multilingual starter pack", "Website Starter Pack"],
|
|
60
|
-
"models": ['alert','endpoint','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role']
|
|
60
|
+
"models": ['alert','endpoint','request','kpi','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
|
}
|
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;
|