@steedos-labs/plugin-workflow 3.0.43 → 3.0.45

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.
Files changed (29) hide show
  1. package/designer/dist/amis-renderer/amis-renderer.css +1 -1
  2. package/designer/dist/amis-renderer/amis-renderer.js +1 -1
  3. package/designer/dist/assets/{index-C3K_lyOl.css → index-B5zmn1Wt.css} +1 -1
  4. package/designer/dist/assets/{index-XZWBSnmF.js → index-D7W5yEF4.js} +268 -268
  5. package/designer/dist/index.html +3 -3
  6. package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +1 -1
  7. package/main/default/objects/instances/buttons/instance_new.button.yml +1 -1
  8. package/main/default/objects/workflow_designer_backups.object.yml +5 -0
  9. package/main/default/pages/flowdetail.page.amis.json +0 -31
  10. package/main/default/routes/am.router.js +1 -2
  11. package/main/default/routes/api_workflow_ai_form_design_stream.router.js +36 -1
  12. package/main/default/routes/api_workflow_next_step.router.js +8 -1
  13. package/main/default/routes/api_workflow_next_step_users.router.js +4 -1
  14. package/main/default/routes/api_workflow_submit.router.js +31 -2
  15. package/main/default/routes/flow_form_design.ejs +2 -1
  16. package/main/default/routes/object_workflows.router.js +3 -3
  17. package/main/default/test/test_migrateApprovalCommentsField.js +220 -0
  18. package/main/default/test/test_rollbackApprovalCommentsField.js +235 -0
  19. package/main/default/triggers/amis_form_design.trigger.js +2 -1
  20. package/main/default/utils/designerManager.js +2 -5
  21. package/package.json +1 -1
  22. package/public/amis-renderer/amis-renderer.css +1 -1
  23. package/public/amis-renderer/amis-renderer.js +1 -1
  24. package/public/workflow/index.css +24 -1
  25. package/src/rests/approvalCommentsConsole.js +9 -0
  26. package/src/rests/getPageSchema.js +58 -0
  27. package/src/rests/index.js +1 -0
  28. package/src/rests/migrateApprovalCommentsField.js +31 -12
  29. package/src/rests/rollbackApprovalCommentsField.js +32 -21
@@ -306,11 +306,13 @@ tbody .color-priority-muted *{
306
306
 
307
307
  .steedos-instance-detail-wrapper{
308
308
  height: calc(100vh - 101px);
309
+ height: calc(100dvh - 101px);
309
310
  }
310
311
 
311
312
  @media (max-width: 768px){
312
313
  .steedos-instance-detail-wrapper{
313
314
  height: calc(100vh - 64px);
315
+ height: calc(100dvh - 64px);
314
316
  }
315
317
  }
316
318
 
@@ -553,7 +555,28 @@ tbody .color-priority-muted *{
553
555
  background: var(--button-primary-default-bg-color) !important
554
556
  }
555
557
 
556
- .ant-btn-color-primary:not[disabled] {
558
+ .ant-btn-color-primary {
557
559
  background: var(--button-primary-default-bg-color) !important;
558
560
  border-color: var(--button-primary-default-bg-color) !important;
561
+ }
562
+
563
+ .ant-btn-color-primary:disabled{
564
+ border-color: #d9d9d9 !important;
565
+ color: rgba(0, 0, 0, 0.25) !important;
566
+ background: rgba(0, 0, 0, 0.04) !important;
567
+ box-shadow: none !important;
568
+ }
569
+
570
+ .select-popup-high-z {
571
+ z-index: 9999 !important;
572
+ }
573
+
574
+ .md\:grid-cols-2 {
575
+ @media (width >= 48rem) {
576
+ grid-template-columns: repeat(2, minmax(0, 1fr));
577
+ }
578
+ }
579
+
580
+ .ant-modal-root .multiselect-form-mode.ant-select-multiple .ant-select-selector .ant-select-selection-wrap{
581
+ min-height: 36px !important;
559
582
  }
@@ -273,6 +273,15 @@ module.exports = {
273
273
  </div>
274
274
 
275
275
  <div class="tab-content active" id="execute">
276
+ <div style="background:#fffbe6;border-left:4px solid #d97706;border-radius:6px;padding:20px 24px;margin-bottom:24px;">
277
+ <p style="font-weight:bold;font-size:16px;margin-bottom:12px;">⚠️ 注意事项</p>
278
+ <ol style="padding-left:20px;line-height:2;color:#2d3748;">
279
+ <li>升级和还原脚本不但会修改表单<strong>当前版本</strong>(<code style="background:#edf2f7;padding:2px 6px;border-radius:3px;color:#e53e3e;">current</code> 中的字段),也会修改<strong>历史版本</strong>(<code style="background:#edf2f7;padding:2px 6px;border-radius:3px;color:#e53e3e;">historys</code> 中的字段)。</li>
280
+ <li>升级和还原脚本会<strong>递归处理嵌套 section 分组字段</strong>,即 <code style="background:#edf2f7;padding:2px 6px;border-radius:3px;color:#e53e3e;">current.fields[*].fields[*]</code> 和 <code style="background:#edf2f7;padding:2px 6px;border-radius:3px;color:#e53e3e;">historys[*].fields[*].fields[*]</code> 中的字段均会被处理。</li>
281
+ <li>升级后,如果在流程的<strong>表单设计器中保存过</strong>,该表单就<strong>无法还原到最初的状态</strong>了。因此,对于可能需要执行还原操作的表单,务必在升级后先测试确认一次,确认正常后再在表单设计器中执行保存操作。</li>
282
+ </ol>
283
+ </div>
284
+
276
285
  <div class="operation-panel">
277
286
  <h3>🧪 集成测试 (Integration Test)</h3>
278
287
  <p style="color: #666; margin-bottom: 15px;">
@@ -0,0 +1,58 @@
1
+ const objectql = require('@steedos/objectql');
2
+
3
+ module.exports = {
4
+ rest: {
5
+ method: 'GET',
6
+ fullPath: '/api/workflow/pages/schema'
7
+ },
8
+ params: {
9
+ pageId: { type: 'string' }
10
+ },
11
+ async handler(ctx) {
12
+ const { pageId } = ctx.params;
13
+ const userSession = ctx.meta.user;
14
+
15
+ if (!pageId) {
16
+ return {};
17
+ }
18
+
19
+ // 直接 broker.call page.getMeSchema,完全绕过 vm2 沙箱
20
+ // 参考 steedos-platform/services/service-pages/main/default/routes/page_schema.router.js
21
+ // flow_selector 这个 page 的 type 固定为 'page'
22
+ const result = await objectql.getSteedosSchema().broker.call('page.getMeSchema', {
23
+ type: 'page',
24
+ pageId: pageId
25
+ }, {
26
+ meta: { user: userSession }
27
+ });
28
+
29
+ if (!result || !result.schema) {
30
+ return {};
31
+ }
32
+
33
+ // result.schema 可能是字符串(JSON)或对象,统一处理
34
+ let schema = result.schema;
35
+ if (typeof schema === 'string') {
36
+ try {
37
+ schema = JSON.parse(schema);
38
+ } catch (e) {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ // 返回与旧接口一致的结构,包含 inject + assets(tailwind CSS)+ body
44
+ // 参考 steedos-platform/services/service-pages/main/default/functions/pages_schema.function.yml
45
+ return {
46
+ type: 'inject',
47
+ assets: [
48
+ {
49
+ type: 'css',
50
+ id: 'page-css',
51
+ src: `/api/v6/pages/${pageId}/current/tailwind.css`,
52
+ location: 'start'
53
+ }
54
+ ],
55
+ body: [schema]
56
+ };
57
+ }
58
+ };
@@ -16,4 +16,5 @@ module.exports = {
16
16
  rollbackApprovalCommentsField: require('./rollbackApprovalCommentsField'),
17
17
  integrationTestApprovalComments: require('./integrationTestApprovalComments'),
18
18
  approvalCommentsConsole: require('./approvalCommentsConsole'),
19
+ getPageSchema: require('./getPageSchema'),
19
20
  }
@@ -7,9 +7,8 @@
7
7
  * from old format to new steedos-field format with config object.
8
8
  */
9
9
 
10
- const { MongoClient } = require('mongodb');
11
-
12
- // MongoDB connection
10
+ // MongoDB connection (lazy-loaded so pure helper functions are testable without the driver)
11
+ let MongoClient = null;
13
12
  let client = null;
14
13
  let db = null;
15
14
 
@@ -18,6 +17,10 @@ async function connectToMongoDB() {
18
17
  return db;
19
18
  }
20
19
 
20
+ if (!MongoClient) {
21
+ MongoClient = require('mongodb').MongoClient;
22
+ }
23
+
21
24
  const mongoUrl = process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/steedos';
22
25
  try {
23
26
  client = new MongoClient(mongoUrl);
@@ -313,7 +316,8 @@ function processAmisSchema(amisSchemaStr) {
313
316
  }
314
317
 
315
318
  /**
316
- * Process fields array and return bulk operations
319
+ * Process fields array and return bulk operations.
320
+ * Recursively handles nested fields inside section/panel/group containers.
317
321
  */
318
322
  function processFieldsArray(fields, pathPrefix) {
319
323
  const updates = [];
@@ -325,12 +329,13 @@ function processFieldsArray(fields, pathPrefix) {
325
329
 
326
330
  for (let i = 0; i < fields.length; i++) {
327
331
  const field = fields[i];
332
+ const fieldPath = `${pathPrefix}.${i}`;
328
333
  const transformation = transformField(field);
329
334
 
330
335
  if (transformation) {
331
336
  updates.push({
332
337
  index: i,
333
- path: `${pathPrefix}.${i}`,
338
+ path: fieldPath,
334
339
  update: transformation,
335
340
  fieldId: field._id || field.code || field.name, // Track field for counting
336
341
  hasAmisField: !!field._amisField // Track if we need to unset _amisField
@@ -338,15 +343,22 @@ function processFieldsArray(fields, pathPrefix) {
338
343
 
339
344
  // Unset original properties that are being backed up
340
345
  if (field._amisField) {
341
- unsets.push(`${pathPrefix}.${i}._amisField`);
346
+ unsets.push(`${fieldPath}._amisField`);
342
347
  }
343
348
  if (field.formula) {
344
- unsets.push(`${pathPrefix}.${i}.formula`);
349
+ unsets.push(`${fieldPath}.formula`);
345
350
  }
346
351
  if (field.default_value) {
347
- unsets.push(`${pathPrefix}.${i}.default_value`);
352
+ unsets.push(`${fieldPath}.default_value`);
348
353
  }
349
354
  }
355
+
356
+ // Recursively process nested fields inside container types (section/panel/group/etc.)
357
+ if (Array.isArray(field.fields)) {
358
+ const nested = processFieldsArray(field.fields, `${fieldPath}.fields`);
359
+ for (const u of nested.updates) updates.push(u);
360
+ for (const u of nested.unsets) unsets.push(u);
361
+ }
350
362
  }
351
363
 
352
364
  return { updates, unsets };
@@ -362,6 +374,13 @@ module.exports = {
362
374
  dryRun: { type: 'boolean', optional: true, convert: true },
363
375
  fid: { type: 'string', optional: true } // Flow ID to process single flow
364
376
  },
377
+ // Exported for testing
378
+ _helpers: {
379
+ matchesApprovalPattern,
380
+ transformField,
381
+ processFieldsArray,
382
+ processAmisSchema
383
+ },
365
384
  async handler(ctx) {
366
385
  const { user } = ctx.meta;
367
386
 
@@ -422,10 +441,10 @@ module.exports = {
422
441
  formUpdated = true;
423
442
  formFieldCount += updates.length; // Count fields, not operations
424
443
 
425
- // Build $set updates for current.fields
444
+ // Build $set updates for current.fields (including nested section/panel/group fields)
426
445
  for (const update of updates) {
427
446
  for (const key in update.update) {
428
- formUpdate[`current.fields.${update.index}.${key}`] = update.update[key];
447
+ formUpdate[`${update.path}.${key}`] = update.update[key];
429
448
  }
430
449
  }
431
450
 
@@ -450,10 +469,10 @@ module.exports = {
450
469
  formUpdated = true;
451
470
  formFieldCount += updates.length; // Count fields, not operations
452
471
 
453
- // Build $set updates for historys.fields
472
+ // Build $set updates for historys.fields (including nested section/panel/group fields)
454
473
  for (const update of updates) {
455
474
  for (const key in update.update) {
456
- formUpdate[`historys.${h}.fields.${update.index}.${key}`] = update.update[key];
475
+ formUpdate[`${update.path}.${key}`] = update.update[key];
457
476
  }
458
477
  }
459
478
 
@@ -7,9 +7,8 @@
7
7
  * back to their original input type format.
8
8
  */
9
9
 
10
- const { MongoClient } = require('mongodb');
11
-
12
- // MongoDB connection
10
+ // MongoDB connection (lazy-loaded so pure helper functions are testable without the driver)
11
+ let MongoClient = null;
13
12
  let client = null;
14
13
  let db = null;
15
14
 
@@ -18,6 +17,10 @@ async function connectToMongoDB() {
18
17
  return db;
19
18
  }
20
19
 
20
+ if (!MongoClient) {
21
+ MongoClient = require('mongodb').MongoClient;
22
+ }
23
+
21
24
  const mongoUrl = process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/steedos';
22
25
  try {
23
26
  client = new MongoClient(mongoUrl);
@@ -53,6 +56,7 @@ function processFieldsArrayForRollback(fields, pathPrefix, formId, formName) {
53
56
 
54
57
  for (let i = 0; i < fields.length; i++) {
55
58
  const field = fields[i];
59
+ const fieldPath = `${pathPrefix}.${i}`;
56
60
 
57
61
  // Only rollback fields that were transformed (Idempotency principle)
58
62
  if (field.__approval_comments_transformed === true) {
@@ -63,7 +67,7 @@ function processFieldsArrayForRollback(fields, pathPrefix, formId, formName) {
63
67
  const originalType = field.__approval_comments_original_type || 'input';
64
68
  updates.push({
65
69
  index: i,
66
- path: `${pathPrefix}.${i}.type`,
70
+ path: `${fieldPath}.type`,
67
71
  value: originalType
68
72
  });
69
73
 
@@ -71,7 +75,7 @@ function processFieldsArrayForRollback(fields, pathPrefix, formId, formName) {
71
75
  if (field.__approval_comments_original_formula) {
72
76
  updates.push({
73
77
  index: i,
74
- path: `${pathPrefix}.${i}.formula`,
78
+ path: `${fieldPath}.formula`,
75
79
  value: field.__approval_comments_original_formula
76
80
  });
77
81
  }
@@ -80,7 +84,7 @@ function processFieldsArrayForRollback(fields, pathPrefix, formId, formName) {
80
84
  if (field.__approval_comments_original_default_value) {
81
85
  updates.push({
82
86
  index: i,
83
- path: `${pathPrefix}.${i}.default_value`,
87
+ path: `${fieldPath}.default_value`,
84
88
  value: field.__approval_comments_original_default_value
85
89
  });
86
90
  }
@@ -89,29 +93,38 @@ function processFieldsArrayForRollback(fields, pathPrefix, formId, formName) {
89
93
  if (field.__approval_comments_original__amisField) {
90
94
  updates.push({
91
95
  index: i,
92
- path: `${pathPrefix}.${i}._amisField`,
96
+ path: `${fieldPath}._amisField`,
93
97
  value: field.__approval_comments_original__amisField
94
98
  });
95
99
  }
96
100
 
97
101
  // Remove added properties
98
- unsets.push(`${pathPrefix}.${i}.__approval_comments_transformed`);
99
- unsets.push(`${pathPrefix}.${i}.config`);
100
- unsets.push(`${pathPrefix}.${i}.__approval_comments_original__amisField`);
101
- unsets.push(`${pathPrefix}.${i}.__approval_comments_original_type`); // Remove backup
102
- unsets.push(`${pathPrefix}.${i}.__approval_comments_original_formula`); // Remove backup
103
- unsets.push(`${pathPrefix}.${i}.__approval_comments_original_default_value`); // Remove backup
102
+ unsets.push(`${fieldPath}.__approval_comments_transformed`);
103
+ unsets.push(`${fieldPath}.config`);
104
+ unsets.push(`${fieldPath}.__approval_comments_original__amisField`);
105
+ unsets.push(`${fieldPath}.__approval_comments_original_type`); // Remove backup
106
+ unsets.push(`${fieldPath}.__approval_comments_original_formula`); // Remove backup
107
+ unsets.push(`${fieldPath}.__approval_comments_original_default_value`); // Remove backup
104
108
  } catch (error) {
105
109
  // Silent Failure principle: Record error but continue processing
106
110
  fieldErrors.push({
107
111
  formId: formId,
108
112
  formName: formName,
109
- fieldPath: `${pathPrefix}.${i}`,
113
+ fieldPath: fieldPath,
110
114
  fieldCode: field.code || field.name || 'unknown',
111
115
  error: error.message
112
116
  });
113
117
  }
114
118
  }
119
+
120
+ // Recursively process nested fields inside container types (section/panel/group/etc.)
121
+ if (Array.isArray(field.fields)) {
122
+ const nested = processFieldsArrayForRollback(field.fields, `${fieldPath}.fields`, formId, formName);
123
+ for (const u of nested.updates) updates.push(u);
124
+ for (const u of nested.unsets) unsets.push(u);
125
+ fieldCount += nested.fieldCount;
126
+ for (const e of nested.fieldErrors) fieldErrors.push(e);
127
+ }
115
128
  }
116
129
 
117
130
  return { updates, unsets, fieldCount, fieldErrors };
@@ -127,6 +140,10 @@ module.exports = {
127
140
  dryRun: { type: 'boolean', optional: true, convert: true },
128
141
  fid: { type: 'string', optional: true } // Flow ID to rollback single flow
129
142
  },
143
+ // Exported for testing
144
+ _helpers: {
145
+ processFieldsArrayForRollback
146
+ },
130
147
  async handler(ctx) {
131
148
  const { user } = ctx.meta;
132
149
 
@@ -140,13 +157,7 @@ module.exports = {
140
157
  const formsCollection = await getCollection('forms');
141
158
 
142
159
  // Build query filter
143
- const query = {
144
- state: 'enabled', // Only process enabled forms
145
- $or: [
146
- { 'current.fields.__approval_comments_transformed': true },
147
- { 'historys.fields.__approval_comments_transformed': true }
148
- ]
149
- };
160
+ const query = { state: 'enabled' }; // Only process enabled forms
150
161
 
151
162
  // If fid is provided, query flows collection to get form ID
152
163
  if (fid) {