@nocobase/client-v2 2.1.0-beta.41 → 2.1.0-beta.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/client-v2",
3
- "version": "2.1.0-beta.41",
3
+ "version": "2.1.0-beta.42",
4
4
  "license": "Apache-2.0",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.mjs",
@@ -27,11 +27,11 @@
27
27
  "@formily/antd-v5": "1.2.3",
28
28
  "@formily/react": "^2.2.27",
29
29
  "@formily/shared": "^2.2.27",
30
- "@nocobase/evaluators": "2.1.0-beta.41",
31
- "@nocobase/flow-engine": "2.1.0-beta.41",
32
- "@nocobase/sdk": "2.1.0-beta.41",
33
- "@nocobase/shared": "2.1.0-beta.41",
34
- "@nocobase/utils": "2.1.0-beta.41",
30
+ "@nocobase/evaluators": "2.1.0-beta.42",
31
+ "@nocobase/flow-engine": "2.1.0-beta.42",
32
+ "@nocobase/sdk": "2.1.0-beta.42",
33
+ "@nocobase/shared": "2.1.0-beta.42",
34
+ "@nocobase/utils": "2.1.0-beta.42",
35
35
  "ahooks": "^3.7.2",
36
36
  "antd": "5.24.2",
37
37
  "antd-style": "3.7.1",
@@ -45,5 +45,5 @@
45
45
  "react-i18next": "^11.15.1",
46
46
  "react-router-dom": "^6.30.1"
47
47
  },
48
- "gitHead": "ea3f966811146edb13c9e8016bf4cdc81597a003"
48
+ "gitHead": "619b4d06c934bf3266ba7f388438db018217c87d"
49
49
  }
@@ -111,7 +111,7 @@ export class AssignFormGridModel extends FormGridModel {
111
111
  return;
112
112
  }
113
113
  const fieldModel = binding.modelName;
114
- const created = this.flowEngine.createModel({
114
+ const created = this.addSubModel('items', {
115
115
  use: 'AssignFormItemModel',
116
116
  stepParams: {
117
117
  fieldSettings: {
@@ -137,8 +137,6 @@ export class AssignFormGridModel extends FormGridModel {
137
137
  },
138
138
  },
139
139
  },
140
- parentId: this.uid,
141
- subKey: 'items',
142
140
  });
143
141
  created['assignValue'] = value;
144
142
  }
@@ -230,7 +230,6 @@ export class AssignFormItemModel extends FormItemModel {
230
230
  fm?.setProps?.({
231
231
  disabled: false,
232
232
  readPretty: false,
233
- pattern: 'editable',
234
233
  updateAssociation: false,
235
234
  multiple: multi,
236
235
  });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import React from 'react';
11
+ import { App, ConfigProvider } from 'antd';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { render, screen, waitFor } from '@nocobase/test/client';
14
+ import {
15
+ FlowEngine,
16
+ FlowEngineProvider,
17
+ FlowRuntimeContext,
18
+ FlowSettingsContextProvider,
19
+ type FlowModel,
20
+ type IFlowModelRepository,
21
+ } from '@nocobase/flow-engine';
22
+ import { AssignFormGridModel } from '../AssignFormGridModel';
23
+ import { AssignFormItemModel } from '../AssignFormItemModel';
24
+ import { AssignFormModel } from '../AssignFormModel';
25
+ import { createAssignFieldValuesStep } from '../assignFieldValuesFlow';
26
+ import { InputFieldModel } from '../../../fields/InputFieldModel';
27
+
28
+ class MockFlowModelRepository implements IFlowModelRepository {
29
+ async findOne(): Promise<Record<string, any> | null> {
30
+ return null;
31
+ }
32
+
33
+ async save(model: FlowModel): Promise<Record<string, any>> {
34
+ return { uid: model.uid };
35
+ }
36
+
37
+ async destroy(): Promise<boolean> {
38
+ return true;
39
+ }
40
+
41
+ async move(): Promise<void> {}
42
+
43
+ async duplicate(): Promise<Record<string, any> | null> {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ describe('assignFieldValuesFlow (editor)', () => {
49
+ it('repairs AssignFormModel resource init and clears cached collection', async () => {
50
+ const engine = new FlowEngine();
51
+ engine.setModelRepository(new MockFlowModelRepository());
52
+ engine.registerModels({ AssignFormModel, AssignFormGridModel, AssignFormItemModel, InputFieldModel });
53
+ engine.context.defineProperty('location', { value: { search: '' } });
54
+ engine.context.defineProperty('themeToken', { value: { marginLG: 24 } });
55
+
56
+ const dsm = engine.context.dataSourceManager;
57
+ const main = dsm.getDataSource('main');
58
+ main.addCollection({
59
+ name: 'users',
60
+ fields: [{ name: 'nickname', type: 'string', interface: 'input' }],
61
+ });
62
+ const users = dsm.getCollection('main', 'users');
63
+ expect(users?.name).toBe('users');
64
+
65
+ const action = engine.createModel({
66
+ use: 'FlowModel',
67
+ uid: 'act-assign',
68
+ });
69
+ action.setStepParams('assignSettings', 'assignFieldValues', { assignedValues: { nickname: '1111' } });
70
+
71
+ const existing = engine.createModel<AssignFormModel>({
72
+ use: 'AssignFormModel',
73
+ uid: 'form-assign',
74
+ parentId: action.uid,
75
+ subKey: 'assignForm',
76
+ stepParams: {
77
+ resourceSettings: {
78
+ init: {
79
+ dataSourceKey: 'main',
80
+ collectionName: 'missing',
81
+ },
82
+ },
83
+ },
84
+ });
85
+ expect(existing.context.collection).toBeUndefined();
86
+
87
+ action.context.defineProperty('blockModel', { value: { collection: users } });
88
+
89
+ const step = createAssignFieldValuesStep({ settingsFlowKey: 'assignSettings' });
90
+ const schema = step.uiSchema();
91
+ const Editor = schema.editor?.['x-component'] as React.ComponentType;
92
+ expect(Editor).toBeTypeOf('function');
93
+
94
+ const flowSettingsCtx = new FlowRuntimeContext(action as any, 'assignSettings', 'settings');
95
+
96
+ render(
97
+ <FlowEngineProvider engine={engine}>
98
+ <ConfigProvider>
99
+ <App>
100
+ <FlowSettingsContextProvider value={flowSettingsCtx}>
101
+ <Editor />
102
+ </FlowSettingsContextProvider>
103
+ </App>
104
+ </ConfigProvider>
105
+ </FlowEngineProvider>,
106
+ );
107
+
108
+ await waitFor(() => {
109
+ const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
110
+ expect(form?.getStepParams('resourceSettings', 'init')).toEqual({
111
+ dataSourceKey: 'main',
112
+ collectionName: 'users',
113
+ });
114
+ expect(form?.context.collection?.name).toBe('users');
115
+ expect(form?.context.collection?.getFields?.().length).toBeGreaterThan(0);
116
+ });
117
+
118
+ await waitFor(() => {
119
+ expect(screen.getByText('nickname')).toBeInTheDocument();
120
+ const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
121
+ expect(form?.subModels.grid.subModels.items.length).toBe(1);
122
+ });
123
+ });
124
+ });
@@ -15,7 +15,7 @@ import {
15
15
  useFlowEngine,
16
16
  useFlowSettingsContext,
17
17
  } from '@nocobase/flow-engine';
18
- import React, { useEffect, useRef } from 'react';
18
+ import React, { useEffect } from 'react';
19
19
  import { CollectionActionModel } from '../../base/CollectionActionModel';
20
20
  import { RecordActionModel } from '../../base/RecordActionModel';
21
21
  import { AssignFormModel } from './AssignFormModel';
@@ -62,6 +62,39 @@ function getResourceInit(ctx: AssignFieldValuesContext): { dataSourceKey: string
62
62
  return dsKey && collName ? { dataSourceKey: dsKey, collectionName: collName } : undefined;
63
63
  }
64
64
 
65
+ function resolveInitFromFlowSettings(
66
+ blockModel: unknown,
67
+ actionContext: AssignFieldValuesContext | undefined,
68
+ ): { dataSourceKey: string; collectionName: string } | undefined {
69
+ const maybeBlockCollection = (blockModel as { collection?: { dataSourceKey?: string; name?: string } } | undefined)
70
+ ?.collection;
71
+ const collection = maybeBlockCollection || getContextCollection(actionContext);
72
+ const dsKey = collection?.dataSourceKey;
73
+ const collName = collection?.name;
74
+ return dsKey && collName ? { dataSourceKey: dsKey, collectionName: collName } : undefined;
75
+ }
76
+
77
+ function isValidResourceInit(init: unknown): init is {
78
+ dataSourceKey: string;
79
+ collectionName: string;
80
+ } {
81
+ if (!init || typeof init !== 'object') return false;
82
+ const obj = init as { dataSourceKey?: unknown; collectionName?: unknown };
83
+ return (
84
+ typeof obj.dataSourceKey === 'string' &&
85
+ !!obj.dataSourceKey &&
86
+ typeof obj.collectionName === 'string' &&
87
+ !!obj.collectionName
88
+ );
89
+ }
90
+
91
+ function clearResourceContextCache(model: { context?: { removeCache?: (key: string) => void } } | undefined) {
92
+ model?.context?.removeCache?.('dataSource');
93
+ model?.context?.removeCache?.('collection');
94
+ model?.context?.removeCache?.('resource');
95
+ model?.context?.removeCache?.('association');
96
+ }
97
+
65
98
  export function createAssignFormSubModelOptions(ctx: AssignFieldValuesContext) {
66
99
  return {
67
100
  async: true,
@@ -114,21 +147,52 @@ function AssignFieldsEditor(props: { settingsFlowKey: string; clearRecordContext
114
147
  const { model, blockModel } = useFlowSettingsContext();
115
148
  const action = model as AssignFieldValuesModel;
116
149
  const engine = useFlowEngine();
117
- const initializedRef = useRef(false);
118
150
  const [formModel, setFormModel] = React.useState<AssignFormModel | null>(null);
119
151
 
120
152
  useEffect(() => {
121
153
  let cancelled = false;
122
154
  const loadAssignForm = async () => {
155
+ const init = resolveInitFromFlowSettings(blockModel, action?.context);
123
156
  const loaded = (await engine.loadOrCreateModel(
124
- {
125
- parentId: action.uid,
126
- subKey: 'assignForm',
127
- use: 'AssignFormModel',
128
- },
157
+ init
158
+ ? {
159
+ parentId: action.uid,
160
+ subKey: 'assignForm',
161
+ use: 'AssignFormModel',
162
+ stepParams: { resourceSettings: { init } },
163
+ }
164
+ : {
165
+ parentId: action.uid,
166
+ subKey: 'assignForm',
167
+ use: 'AssignFormModel',
168
+ },
129
169
  { skipSave: !model.context.flowSettingsEnabled },
130
170
  )) as AssignFormModel;
131
171
  if (cancelled) return;
172
+
173
+ if (isValidResourceInit(init)) {
174
+ const existingInit = loaded.getStepParams?.('resourceSettings', 'init');
175
+ // Ensure `resourceSettings.init` is available before first render to avoid cached undefined `collection`
176
+ if (
177
+ !isValidResourceInit(existingInit) ||
178
+ existingInit.dataSourceKey !== init.dataSourceKey ||
179
+ existingInit.collectionName !== init.collectionName
180
+ ) {
181
+ loaded.setStepParams?.('resourceSettings', 'init', init);
182
+ }
183
+ clearResourceContextCache(loaded);
184
+ clearResourceContextCache(loaded.subModels?.grid);
185
+ }
186
+
187
+ const prev = action.getStepParams?.(props.settingsFlowKey, ASSIGN_FIELD_VALUES_STEP_KEY) || {};
188
+ loaded.setInitialAssignedValues(prev?.assignedValues || {});
189
+
190
+ const isBulkCollectionAction = model instanceof CollectionActionModel && !(model instanceof RecordActionModel);
191
+ const isBulk = props.clearRecordContext || isBulkCollectionAction;
192
+ if (isBulk && loaded.context?.defineProperty) {
193
+ loaded.context.defineProperty('record', { get: () => undefined, cache: false });
194
+ }
195
+
132
196
  setFormModel(loaded);
133
197
  action.assignFormUid = loaded?.uid || action.assignFormUid;
134
198
  };
@@ -136,31 +200,15 @@ function AssignFieldsEditor(props: { settingsFlowKey: string; clearRecordContext
136
200
  return () => {
137
201
  cancelled = true;
138
202
  };
139
- }, [action, engine, model.context.flowSettingsEnabled]);
140
-
141
- useEffect(() => {
142
- if (initializedRef.current) return;
143
- if (!formModel) return;
144
-
145
- const prev = action.getStepParams?.(props.settingsFlowKey, ASSIGN_FIELD_VALUES_STEP_KEY) || {};
146
- const coll = blockModel?.collection || getContextCollection(action?.context);
147
- const dsKey = coll?.dataSourceKey;
148
- const collName = coll?.name;
149
- if (dsKey && collName) {
150
- formModel.setStepParams('resourceSettings', 'init', {
151
- dataSourceKey: dsKey,
152
- collectionName: collName,
153
- });
154
- }
155
- formModel.setInitialAssignedValues(prev?.assignedValues || {});
156
-
157
- const isBulk =
158
- props.clearRecordContext || (action instanceof CollectionActionModel && !(action instanceof RecordActionModel));
159
- if (isBulk && formModel.context?.defineProperty) {
160
- formModel.context.defineProperty('record', { get: () => undefined, cache: false });
161
- }
162
- initializedRef.current = true;
163
- }, [action, blockModel?.collection, formModel, props.clearRecordContext, props.settingsFlowKey]);
203
+ }, [
204
+ action,
205
+ blockModel,
206
+ engine,
207
+ model,
208
+ model.context.flowSettingsEnabled,
209
+ props.clearRecordContext,
210
+ props.settingsFlowKey,
211
+ ]);
164
212
 
165
213
  return formModel ? <FlowModelRenderer model={formModel} showFlowSettings={false} /> : null;
166
214
  }