@nocobase/flow-engine 2.1.0-beta.24 → 2.1.0-beta.26
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/lib/components/dnd/gridDragPlanner.js +16 -4
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/models/flowModel.js +2 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/package.json +4 -4
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/components/__tests__/gridDragPlanner.test.ts +46 -0
- package/src/components/dnd/gridDragPlanner.ts +19 -4
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/models/__tests__/flowModel.test.ts +19 -3
- package/src/models/flowModel.tsx +3 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
|
@@ -851,6 +851,18 @@ const findCellByPath = /* @__PURE__ */ __name((layout, path) => {
|
|
|
851
851
|
}
|
|
852
852
|
return null;
|
|
853
853
|
}, "findCellByPath");
|
|
854
|
+
const findCellByPathOrClosestAncestor = /* @__PURE__ */ __name((layout, path) => {
|
|
855
|
+
if (!(path == null ? void 0 : path.length)) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
for (let length = path.length; length > 0; length -= 1) {
|
|
859
|
+
const target = findCellByPath(layout, path.slice(0, length));
|
|
860
|
+
if (target) {
|
|
861
|
+
return target;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return null;
|
|
865
|
+
}, "findCellByPathOrClosestAncestor");
|
|
854
866
|
const removeItemFromGridLayout = /* @__PURE__ */ __name((layout, sourceUid) => {
|
|
855
867
|
const removeFromRows = /* @__PURE__ */ __name((rows) => rows.map((row) => {
|
|
856
868
|
const cellsWithSizes = row.cells.map((cell, index) => {
|
|
@@ -918,7 +930,7 @@ const simulateGridLayoutForSlot = /* @__PURE__ */ __name(({
|
|
|
918
930
|
removeItemFromGridLayout(cloned, sourceUid);
|
|
919
931
|
switch (slot.type) {
|
|
920
932
|
case "column": {
|
|
921
|
-
const target =
|
|
933
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
922
934
|
if (!target) {
|
|
923
935
|
break;
|
|
924
936
|
}
|
|
@@ -934,7 +946,7 @@ const simulateGridLayoutForSlot = /* @__PURE__ */ __name(({
|
|
|
934
946
|
break;
|
|
935
947
|
}
|
|
936
948
|
case "empty-column": {
|
|
937
|
-
const target =
|
|
949
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
938
950
|
if (target) {
|
|
939
951
|
delete target.cell.rows;
|
|
940
952
|
target.cell.items = [sourceUid];
|
|
@@ -942,7 +954,7 @@ const simulateGridLayoutForSlot = /* @__PURE__ */ __name(({
|
|
|
942
954
|
break;
|
|
943
955
|
}
|
|
944
956
|
case "column-edge": {
|
|
945
|
-
const target =
|
|
957
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
946
958
|
if (!target) {
|
|
947
959
|
break;
|
|
948
960
|
}
|
|
@@ -969,7 +981,7 @@ const simulateGridLayoutForSlot = /* @__PURE__ */ __name(({
|
|
|
969
981
|
if (!targetItemUid) {
|
|
970
982
|
break;
|
|
971
983
|
}
|
|
972
|
-
const target =
|
|
984
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
973
985
|
if (!(target == null ? void 0 : target.cell.items)) {
|
|
974
986
|
break;
|
|
975
987
|
}
|
|
@@ -15,5 +15,6 @@ export interface SelectWithTitleProps {
|
|
|
15
15
|
itemKey?: string;
|
|
16
16
|
onChange?: (...args: any[]) => void;
|
|
17
17
|
dropdownRender?: any;
|
|
18
|
+
tooltip?: any;
|
|
18
19
|
}
|
|
19
|
-
export declare function SelectWithTitle({ title, getDefaultValue, onChange, options, fieldNames, itemKey, ...others }: SelectWithTitleProps): React.JSX.Element;
|
|
20
|
+
export declare function SelectWithTitle({ title, getDefaultValue, onChange, options, fieldNames, itemKey, tooltip, ...others }: SelectWithTitleProps): React.JSX.Element;
|
|
@@ -50,6 +50,7 @@ function SelectWithTitle({
|
|
|
50
50
|
options,
|
|
51
51
|
fieldNames,
|
|
52
52
|
itemKey,
|
|
53
|
+
tooltip,
|
|
53
54
|
...others
|
|
54
55
|
}) {
|
|
55
56
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
@@ -80,6 +81,18 @@ function SelectWithTitle({
|
|
|
80
81
|
setValue(val);
|
|
81
82
|
onChange == null ? void 0 : onChange({ [itemKey]: val });
|
|
82
83
|
}, "handleChange");
|
|
84
|
+
const titleNode = /* @__PURE__ */ import_react.default.createElement(
|
|
85
|
+
"span",
|
|
86
|
+
{
|
|
87
|
+
style: {
|
|
88
|
+
whiteSpace: "nowrap",
|
|
89
|
+
// 不换行
|
|
90
|
+
flexShrink: 0
|
|
91
|
+
// 不被挤压
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
title
|
|
95
|
+
);
|
|
83
96
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
84
97
|
"div",
|
|
85
98
|
{
|
|
@@ -94,18 +107,7 @@ function SelectWithTitle({
|
|
|
94
107
|
}, 200);
|
|
95
108
|
}
|
|
96
109
|
},
|
|
97
|
-
/* @__PURE__ */ import_react.default.createElement(
|
|
98
|
-
"span",
|
|
99
|
-
{
|
|
100
|
-
style: {
|
|
101
|
-
whiteSpace: "nowrap",
|
|
102
|
-
// 不换行
|
|
103
|
-
flexShrink: 0
|
|
104
|
-
// 不被挤压
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
title
|
|
108
|
-
),
|
|
110
|
+
tooltip ? /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: tooltip, placement: "top", destroyTooltipOnHide: true }, titleNode) : titleNode,
|
|
109
111
|
/* @__PURE__ */ import_react.default.createElement(
|
|
110
112
|
import_antd.Select,
|
|
111
113
|
{
|
package/lib/models/flowModel.js
CHANGED
|
@@ -622,6 +622,7 @@ const _FlowModel = class _FlowModel {
|
|
|
622
622
|
} else {
|
|
623
623
|
this.props = { ...this.props, ...props };
|
|
624
624
|
}
|
|
625
|
+
this._options.props = { ...this.props };
|
|
625
626
|
}
|
|
626
627
|
getProps() {
|
|
627
628
|
return this.props;
|
|
@@ -1163,6 +1164,7 @@ const _FlowModel = class _FlowModel {
|
|
|
1163
1164
|
const data = {
|
|
1164
1165
|
uid: this.uid,
|
|
1165
1166
|
...import_lodash.default.omit(this._options, ["flowEngine"]),
|
|
1167
|
+
props: { ...this.props },
|
|
1166
1168
|
stepParams: this.stepParams,
|
|
1167
1169
|
sortIndex: this.sortIndex,
|
|
1168
1170
|
flowRegistry: {}
|
|
@@ -32,6 +32,10 @@ __export(createCollectionContextMeta_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(createCollectionContextMeta_exports);
|
|
33
33
|
const RELATION_FIELD_TYPES = ["belongsTo", "hasOne", "hasMany", "belongsToMany", "belongsToArray"];
|
|
34
34
|
const NUMERIC_FIELD_TYPES = ["integer", "float", "double", "decimal"];
|
|
35
|
+
function shouldShowFieldInMeta(field, includeNonFilterable) {
|
|
36
|
+
return Boolean(field.interface && (includeNonFilterable || field.filterable));
|
|
37
|
+
}
|
|
38
|
+
__name(shouldShowFieldInMeta, "shouldShowFieldInMeta");
|
|
35
39
|
function createFieldMetadata(field, includeNonFilterable) {
|
|
36
40
|
const baseProperties = createMetaBaseProperties(field);
|
|
37
41
|
if (field.isAssociationField()) {
|
|
@@ -49,7 +53,7 @@ function createFieldMetadata(field, includeNonFilterable) {
|
|
|
49
53
|
properties: /* @__PURE__ */ __name(async () => {
|
|
50
54
|
const subProperties = {};
|
|
51
55
|
targetCollection.fields.forEach((subField) => {
|
|
52
|
-
if (includeNonFilterable
|
|
56
|
+
if (shouldShowFieldInMeta(subField, includeNonFilterable)) {
|
|
53
57
|
subProperties[subField.name] = createFieldMetadata(subField, includeNonFilterable);
|
|
54
58
|
}
|
|
55
59
|
});
|
|
@@ -104,7 +108,7 @@ function createCollectionContextMeta(collectionOrFactory, title, includeNonFilte
|
|
|
104
108
|
properties: /* @__PURE__ */ __name(async () => {
|
|
105
109
|
const properties = {};
|
|
106
110
|
collection.fields.forEach((field) => {
|
|
107
|
-
if (includeNonFilterable
|
|
111
|
+
if (shouldShowFieldInMeta(field, includeNonFilterable)) {
|
|
108
112
|
properties[field.name] = createFieldMetadata(field, includeNonFilterable);
|
|
109
113
|
}
|
|
110
114
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.1.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.1.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.1.0-beta.26",
|
|
12
|
+
"@nocobase/shared": "2.1.0-beta.26",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "b17e1a72057813fa27d8435bf0f2af67ea4b059f"
|
|
41
41
|
}
|
|
@@ -26,6 +26,7 @@ function setupEngineWithCollections() {
|
|
|
26
26
|
fields: [
|
|
27
27
|
{ name: 'id', type: 'integer', interface: 'number' },
|
|
28
28
|
{ name: 'name', type: 'string', interface: 'text' },
|
|
29
|
+
{ name: 'rawUserPayload', type: 'json', filterable: true },
|
|
29
30
|
],
|
|
30
31
|
});
|
|
31
32
|
ds.addCollection({
|
|
@@ -41,6 +42,8 @@ function setupEngineWithCollections() {
|
|
|
41
42
|
filterTargetKey: 'id',
|
|
42
43
|
fields: [
|
|
43
44
|
{ name: 'title', type: 'string', interface: 'text' },
|
|
45
|
+
{ name: 'internalName', type: 'string', interface: 'text' },
|
|
46
|
+
{ name: 'rawPostPayload', type: 'json', filterable: true },
|
|
44
47
|
{ name: 'author', type: 'belongsTo', target: 'users', interface: 'm2o' },
|
|
45
48
|
{ name: 'tags', type: 'belongsToMany', target: 'tags', interface: 'm2m' },
|
|
46
49
|
],
|
|
@@ -91,6 +94,27 @@ describe('objectVariable utilities', () => {
|
|
|
91
94
|
});
|
|
92
95
|
});
|
|
93
96
|
|
|
97
|
+
it('createAssociationAwareObjectMetaFactory should hide fields without interface from object variable meta', async () => {
|
|
98
|
+
const { collection } = setupEngineWithCollections();
|
|
99
|
+
const obj = { title: 'hello', internalName: 'internal', rawPostPayload: { secret: true }, author: 1 };
|
|
100
|
+
const metaFactory = createAssociationAwareObjectMetaFactory(
|
|
101
|
+
() => collection,
|
|
102
|
+
'Current object',
|
|
103
|
+
() => obj,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const meta = await metaFactory();
|
|
107
|
+
const props = await (meta?.properties as any)?.();
|
|
108
|
+
const authorFields = await props?.author?.properties?.();
|
|
109
|
+
|
|
110
|
+
expect(props).toHaveProperty('title');
|
|
111
|
+
expect(props).toHaveProperty('internalName');
|
|
112
|
+
expect(props).toHaveProperty('author');
|
|
113
|
+
expect(props).not.toHaveProperty('rawPostPayload');
|
|
114
|
+
expect(authorFields).toHaveProperty('name');
|
|
115
|
+
expect(authorFields).not.toHaveProperty('rawUserPayload');
|
|
116
|
+
});
|
|
117
|
+
|
|
94
118
|
it('integrates with FlowContext.resolveJsonTemplate to call variables:resolve with flattened contextParams', async () => {
|
|
95
119
|
const { engine, collection } = setupEngineWithCollections();
|
|
96
120
|
const obj = { author: 1 };
|
|
@@ -829,6 +829,52 @@ describe('simulateLayoutForSlot', () => {
|
|
|
829
829
|
expect(nestedRows[1].sizes).toEqual([12, 12]);
|
|
830
830
|
});
|
|
831
831
|
|
|
832
|
+
it('keeps nested column insertion target when removing a sibling collapses the original path', () => {
|
|
833
|
+
const layout = createLayout(
|
|
834
|
+
{
|
|
835
|
+
vyvfw2jw071: [['6ad3ccaabd5', 'ff8b4b57f65']],
|
|
836
|
+
ablhoqw51gb: [['21b422021b8']],
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
vyvfw2jw071: [24],
|
|
840
|
+
ablhoqw51gb: [24],
|
|
841
|
+
},
|
|
842
|
+
['vyvfw2jw071', 'ablhoqw51gb'],
|
|
843
|
+
);
|
|
844
|
+
layout.layout = normalizeGridLayout({
|
|
845
|
+
rows: layout.rows,
|
|
846
|
+
sizes: layout.sizes,
|
|
847
|
+
rowOrder: layout.rowOrder,
|
|
848
|
+
itemUids: ['6ad3ccaabd5', 'ff8b4b57f65', '21b422021b8'],
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
const slot: LayoutSlot = {
|
|
852
|
+
type: 'column',
|
|
853
|
+
rowId: 'll5vo5pzj3u',
|
|
854
|
+
columnIndex: 0,
|
|
855
|
+
insertIndex: 1,
|
|
856
|
+
position: 'after',
|
|
857
|
+
path: [
|
|
858
|
+
{ rowId: 'vyvfw2jw071', cellId: 'vyvfw2jw071:cell:0' },
|
|
859
|
+
{ rowId: 'll5vo5pzj3u', cellId: 'ghy612j5zzg' },
|
|
860
|
+
],
|
|
861
|
+
rect,
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const result = simulateLayoutForSlot({ slot, sourceUid: 'ff8b4b57f65', layout });
|
|
865
|
+
|
|
866
|
+
expect(result.layout!.rows).toMatchObject([
|
|
867
|
+
{
|
|
868
|
+
id: 'vyvfw2jw071',
|
|
869
|
+
cells: [{ items: ['6ad3ccaabd5', 'ff8b4b57f65'] }],
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
id: 'ablhoqw51gb',
|
|
873
|
+
cells: [{ items: ['21b422021b8'] }],
|
|
874
|
+
},
|
|
875
|
+
]);
|
|
876
|
+
});
|
|
877
|
+
|
|
832
878
|
it('treats dragging an item to its own item-edge as no-op', () => {
|
|
833
879
|
const layout = createLayout(
|
|
834
880
|
{
|
|
@@ -1146,6 +1146,21 @@ const findCellByPath = (layout: GridLayoutV2, path?: GridLayoutPath) => {
|
|
|
1146
1146
|
return null;
|
|
1147
1147
|
};
|
|
1148
1148
|
|
|
1149
|
+
const findCellByPathOrClosestAncestor = (layout: GridLayoutV2, path?: GridLayoutPath) => {
|
|
1150
|
+
if (!path?.length) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
for (let length = path.length; length > 0; length -= 1) {
|
|
1155
|
+
const target = findCellByPath(layout, path.slice(0, length));
|
|
1156
|
+
if (target) {
|
|
1157
|
+
return target;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return null;
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1149
1164
|
const removeItemFromGridLayout = (layout: GridLayoutV2, sourceUid: string) => {
|
|
1150
1165
|
const removeFromRows = (rows: GridRowV2[]): GridRowV2[] =>
|
|
1151
1166
|
rows
|
|
@@ -1231,7 +1246,7 @@ const simulateGridLayoutForSlot = ({
|
|
|
1231
1246
|
|
|
1232
1247
|
switch (slot.type) {
|
|
1233
1248
|
case 'column': {
|
|
1234
|
-
const target =
|
|
1249
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
1235
1250
|
if (!target) {
|
|
1236
1251
|
break;
|
|
1237
1252
|
}
|
|
@@ -1247,7 +1262,7 @@ const simulateGridLayoutForSlot = ({
|
|
|
1247
1262
|
break;
|
|
1248
1263
|
}
|
|
1249
1264
|
case 'empty-column': {
|
|
1250
|
-
const target =
|
|
1265
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
1251
1266
|
if (target) {
|
|
1252
1267
|
delete target.cell.rows;
|
|
1253
1268
|
target.cell.items = [sourceUid];
|
|
@@ -1255,7 +1270,7 @@ const simulateGridLayoutForSlot = ({
|
|
|
1255
1270
|
break;
|
|
1256
1271
|
}
|
|
1257
1272
|
case 'column-edge': {
|
|
1258
|
-
const target =
|
|
1273
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
1259
1274
|
if (!target) {
|
|
1260
1275
|
break;
|
|
1261
1276
|
}
|
|
@@ -1282,7 +1297,7 @@ const simulateGridLayoutForSlot = ({
|
|
|
1282
1297
|
if (!targetItemUid) {
|
|
1283
1298
|
break;
|
|
1284
1299
|
}
|
|
1285
|
-
const target =
|
|
1300
|
+
const target = findCellByPathOrClosestAncestor(cloned, targetPath);
|
|
1286
1301
|
if (!target?.cell.items) {
|
|
1287
1302
|
break;
|
|
1288
1303
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Select } from 'antd';
|
|
10
|
+
import { Select, Tooltip } from 'antd';
|
|
11
11
|
import React, { useEffect, useRef, useState } from 'react';
|
|
12
12
|
import { useFlowEngineContext } from '../../../../provider';
|
|
13
13
|
|
|
@@ -19,6 +19,7 @@ export interface SelectWithTitleProps {
|
|
|
19
19
|
itemKey?: string;
|
|
20
20
|
onChange?: (...args: any[]) => void;
|
|
21
21
|
dropdownRender?: any;
|
|
22
|
+
tooltip?: any;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function SelectWithTitle({
|
|
@@ -28,6 +29,7 @@ export function SelectWithTitle({
|
|
|
28
29
|
options,
|
|
29
30
|
fieldNames,
|
|
30
31
|
itemKey,
|
|
32
|
+
tooltip,
|
|
31
33
|
...others
|
|
32
34
|
}: SelectWithTitleProps) {
|
|
33
35
|
const [open, setOpen] = useState(false);
|
|
@@ -66,6 +68,17 @@ export function SelectWithTitle({
|
|
|
66
68
|
setValue(val);
|
|
67
69
|
onChange?.({ [itemKey]: val });
|
|
68
70
|
};
|
|
71
|
+
const titleNode = (
|
|
72
|
+
<span
|
|
73
|
+
style={{
|
|
74
|
+
whiteSpace: 'nowrap', // 不换行
|
|
75
|
+
flexShrink: 0, // 不被挤压
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
</span>
|
|
80
|
+
);
|
|
81
|
+
|
|
69
82
|
return (
|
|
70
83
|
<div
|
|
71
84
|
style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}
|
|
@@ -79,14 +92,13 @@ export function SelectWithTitle({
|
|
|
79
92
|
}, 200);
|
|
80
93
|
}}
|
|
81
94
|
>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</span>
|
|
95
|
+
{tooltip ? (
|
|
96
|
+
<Tooltip title={tooltip} placement="top" destroyTooltipOnHide>
|
|
97
|
+
{titleNode}
|
|
98
|
+
</Tooltip>
|
|
99
|
+
) : (
|
|
100
|
+
titleNode
|
|
101
|
+
)}
|
|
90
102
|
<Select
|
|
91
103
|
{...others}
|
|
92
104
|
open={open}
|
|
@@ -1855,7 +1855,7 @@ describe('FlowModel', () => {
|
|
|
1855
1855
|
});
|
|
1856
1856
|
|
|
1857
1857
|
describe('serialization', () => {
|
|
1858
|
-
test('should serialize basic model data
|
|
1858
|
+
test('should serialize basic model data with the latest props, excluding flowEngine', () => {
|
|
1859
1859
|
model.sortIndex = 5;
|
|
1860
1860
|
model.setProps({ name: 'Test Model', value: 42 });
|
|
1861
1861
|
model.setStepParams({
|
|
@@ -1867,13 +1867,12 @@ describe('FlowModel', () => {
|
|
|
1867
1867
|
expect(serialized).toEqual(
|
|
1868
1868
|
expect.objectContaining({
|
|
1869
1869
|
uid: model.uid,
|
|
1870
|
+
props: expect.objectContaining({ name: 'Test Model', value: 42 }),
|
|
1870
1871
|
stepParams: expect.objectContaining({ flow1: { step1: { param1: 'value1' } } }),
|
|
1871
1872
|
sortIndex: 5,
|
|
1872
1873
|
subModels: expect.any(Object),
|
|
1873
1874
|
}),
|
|
1874
1875
|
);
|
|
1875
|
-
// props should be excluded from serialization
|
|
1876
|
-
expect(serialized.props).toBeUndefined();
|
|
1877
1876
|
expect(serialized.flowEngine).toBeUndefined();
|
|
1878
1877
|
});
|
|
1879
1878
|
|
|
@@ -1892,6 +1891,7 @@ describe('FlowModel', () => {
|
|
|
1892
1891
|
expect(serialized).toEqual(
|
|
1893
1892
|
expect.objectContaining({
|
|
1894
1893
|
uid: 'empty-model',
|
|
1894
|
+
props: expect.objectContaining({ foo: 'bar' }),
|
|
1895
1895
|
stepParams: expect.any(Object),
|
|
1896
1896
|
sortIndex: expect.any(Number),
|
|
1897
1897
|
subModels: expect.any(Object),
|
|
@@ -1899,6 +1899,22 @@ describe('FlowModel', () => {
|
|
|
1899
1899
|
);
|
|
1900
1900
|
expect(serialized.flowEngine).toBeUndefined();
|
|
1901
1901
|
});
|
|
1902
|
+
|
|
1903
|
+
test('should serialize the latest props after multiple updates', () => {
|
|
1904
|
+
model.setProps({ fieldNames: { title: 'name' }, searchable: true });
|
|
1905
|
+
model.setProps({ fieldNames: { title: 'age' } });
|
|
1906
|
+
model.setProps('defaultExpandAll', false);
|
|
1907
|
+
|
|
1908
|
+
const serialized = model.serialize();
|
|
1909
|
+
|
|
1910
|
+
expect(serialized.props).toEqual(
|
|
1911
|
+
expect.objectContaining({
|
|
1912
|
+
fieldNames: { title: 'age' },
|
|
1913
|
+
searchable: true,
|
|
1914
|
+
defaultExpandAll: false,
|
|
1915
|
+
}),
|
|
1916
|
+
);
|
|
1917
|
+
});
|
|
1902
1918
|
});
|
|
1903
1919
|
});
|
|
1904
1920
|
|
package/src/models/flowModel.tsx
CHANGED
|
@@ -762,6 +762,8 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
762
762
|
} else {
|
|
763
763
|
this.props = { ...this.props, ...props };
|
|
764
764
|
}
|
|
765
|
+
|
|
766
|
+
this._options.props = { ...this.props };
|
|
765
767
|
}
|
|
766
768
|
|
|
767
769
|
getProps(): ReadonlyModelProps {
|
|
@@ -1509,6 +1511,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1509
1511
|
const data = {
|
|
1510
1512
|
uid: this.uid,
|
|
1511
1513
|
..._.omit(this._options, ['flowEngine']),
|
|
1514
|
+
props: { ...this.props },
|
|
1512
1515
|
stepParams: this.stepParams,
|
|
1513
1516
|
sortIndex: this.sortIndex,
|
|
1514
1517
|
flowRegistry: {},
|
|
@@ -26,6 +26,7 @@ describe('createCollectionContextMeta', () => {
|
|
|
26
26
|
{ name: 'id', type: 'integer', interface: 'number', filterable: true },
|
|
27
27
|
{ name: 'email', type: 'string', interface: 'text', filterable: true },
|
|
28
28
|
{ name: 'nickname', type: 'string', interface: 'text' }, // 未声明 filterable
|
|
29
|
+
{ name: 'rawUserPayload', type: 'json', filterable: true },
|
|
29
30
|
],
|
|
30
31
|
});
|
|
31
32
|
|
|
@@ -34,6 +35,7 @@ describe('createCollectionContextMeta', () => {
|
|
|
34
35
|
fields: [
|
|
35
36
|
{ name: 'title', type: 'string', interface: 'text', filterable: true },
|
|
36
37
|
{ name: 'author', type: 'belongsTo', target: 'users', interface: 'm2o', filterable: true },
|
|
38
|
+
{ name: 'rawPostPayload', type: 'json', filterable: true },
|
|
37
39
|
],
|
|
38
40
|
});
|
|
39
41
|
|
|
@@ -44,8 +46,54 @@ describe('createCollectionContextMeta', () => {
|
|
|
44
46
|
const authorMeta: any = props?.author;
|
|
45
47
|
const authorFields = await authorMeta?.properties?.();
|
|
46
48
|
|
|
49
|
+
expect(props).toHaveProperty('title');
|
|
50
|
+
expect(props).toHaveProperty('author');
|
|
51
|
+
expect(props).not.toHaveProperty('rawPostPayload');
|
|
47
52
|
expect(authorFields).toBeTruthy();
|
|
48
53
|
expect(authorFields).toHaveProperty('email');
|
|
49
54
|
expect(authorFields).not.toHaveProperty('nickname');
|
|
55
|
+
expect(authorFields).not.toHaveProperty('rawUserPayload');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('keeps interfaced non-filterable fields but hides fields without interface when includeNonFilterable is true', async () => {
|
|
59
|
+
const engine = new FlowEngine();
|
|
60
|
+
const dm = engine.dataSourceManager as any;
|
|
61
|
+
dm.collectionFieldInterfaceManager = new CollectionFieldInterfaceManager([], {}, dm);
|
|
62
|
+
engine.context.defineProperty('app', { value: { dataSourceManager: dm } });
|
|
63
|
+
const ds = dm.getDataSource('main')!;
|
|
64
|
+
|
|
65
|
+
ds.addCollection({
|
|
66
|
+
name: 'users',
|
|
67
|
+
fields: [
|
|
68
|
+
{ name: 'id', type: 'integer', interface: 'number', filterable: true },
|
|
69
|
+
{ name: 'email', type: 'string', interface: 'text', filterable: true },
|
|
70
|
+
{ name: 'nickname', type: 'string', interface: 'text' },
|
|
71
|
+
{ name: 'rawUserPayload', type: 'json', filterable: true },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
ds.addCollection({
|
|
76
|
+
name: 'posts',
|
|
77
|
+
fields: [
|
|
78
|
+
{ name: 'title', type: 'string', interface: 'text', filterable: true },
|
|
79
|
+
{ name: 'internalName', type: 'string', interface: 'text' },
|
|
80
|
+
{ name: 'rawPostPayload', type: 'json', filterable: true },
|
|
81
|
+
{ name: 'author', type: 'belongsTo', target: 'users', interface: 'm2o', filterable: true },
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const posts = ds.getCollection('posts')!;
|
|
86
|
+
const metaFactory = createCollectionContextMeta(posts, 'Posts', true);
|
|
87
|
+
const meta = await metaFactory();
|
|
88
|
+
const props = await (meta?.properties as any)?.();
|
|
89
|
+
const authorFields = await props?.author?.properties?.();
|
|
90
|
+
|
|
91
|
+
expect(props).toHaveProperty('title');
|
|
92
|
+
expect(props).toHaveProperty('internalName');
|
|
93
|
+
expect(props).toHaveProperty('author');
|
|
94
|
+
expect(props).not.toHaveProperty('rawPostPayload');
|
|
95
|
+
expect(authorFields).toHaveProperty('email');
|
|
96
|
+
expect(authorFields).toHaveProperty('nickname');
|
|
97
|
+
expect(authorFields).not.toHaveProperty('rawUserPayload');
|
|
50
98
|
});
|
|
51
99
|
});
|
|
@@ -14,6 +14,10 @@ import type { PropertyMetaFactory } from '../flowContext';
|
|
|
14
14
|
const RELATION_FIELD_TYPES = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany', 'belongsToArray'] as const;
|
|
15
15
|
const NUMERIC_FIELD_TYPES = ['integer', 'float', 'double', 'decimal'] as const;
|
|
16
16
|
|
|
17
|
+
function shouldShowFieldInMeta(field: CollectionField, includeNonFilterable?: boolean) {
|
|
18
|
+
return Boolean(field.interface && (includeNonFilterable || field.filterable));
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
/**
|
|
18
22
|
* 创建字段的完整元数据(统一处理关联和非关联字段)
|
|
19
23
|
*/
|
|
@@ -36,7 +40,7 @@ function createFieldMetadata(field: CollectionField, includeNonFilterable?: bool
|
|
|
36
40
|
properties: async () => {
|
|
37
41
|
const subProperties: Record<string, any> = {};
|
|
38
42
|
targetCollection.fields.forEach((subField) => {
|
|
39
|
-
if (includeNonFilterable
|
|
43
|
+
if (shouldShowFieldInMeta(subField, includeNonFilterable)) {
|
|
40
44
|
subProperties[subField.name] = createFieldMetadata(subField, includeNonFilterable);
|
|
41
45
|
}
|
|
42
46
|
});
|
|
@@ -114,7 +118,7 @@ export function createCollectionContextMeta(
|
|
|
114
118
|
|
|
115
119
|
// 添加所有字段
|
|
116
120
|
collection.fields.forEach((field) => {
|
|
117
|
-
if (includeNonFilterable
|
|
121
|
+
if (shouldShowFieldInMeta(field, includeNonFilterable)) {
|
|
118
122
|
properties[field.name] = createFieldMetadata(field, includeNonFilterable);
|
|
119
123
|
}
|
|
120
124
|
});
|