@steedos-labs/plugin-workflow 3.0.21 → 3.0.23
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/APPROVAL_COMMENTS_OPERATIONS.md +673 -0
- package/APPROVAL_COMMENTS_UPGRADE.md +854 -0
- package/main/default/manager/handlers_manager.js +11 -12
- package/main/default/manager/uuflow_manager.js +4 -1
- package/main/default/manager/workflow_manager.js +12 -0
- package/main/default/pages/instance_tasks_detail.page.amis.json +16 -0
- package/main/default/pages/page_instance_print.page.amis.json +1 -1
- package/main/default/routes/api_files.router.js +15 -1
- package/main/default/routes/api_workflow_chart.router.js +41 -9
- package/main/default/routes/api_workflow_next_step.router.js +1 -1
- package/main/default/routes/api_workflow_next_step_users.router.js +7 -2
- package/main/default/routes/flow_form_design.ejs +21 -1
- package/main/default/triggers/amis_form_design.trigger.js +6 -4
- package/main/default/utils/designerManager.js +1 -2
- package/package.json +1 -1
- package/public/workflow/index.css +3 -0
- package/src/rests/approvalCommentsConsole.js +460 -0
- package/src/rests/index.js +5 -1
- package/src/rests/integrationTestApprovalComments.js +96 -0
- package/src/rests/migrateApprovalCommentsField.js +536 -0
- package/src/rests/rollbackApprovalCommentsField.js +319 -0
- package/src/util/templateConverter.js +8 -2
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// HTML Console endpoint - Simplified and Stable Version
|
|
5
|
+
module.exports = {
|
|
6
|
+
rest: {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
fullPath: '/api/workflow/upgrade/approval-comments-console'
|
|
9
|
+
},
|
|
10
|
+
async handler(ctx) {
|
|
11
|
+
const userSession = ctx.meta.user;
|
|
12
|
+
|
|
13
|
+
// Check if user is admin
|
|
14
|
+
if (!userSession || !userSession.is_space_admin) {
|
|
15
|
+
ctx.meta.$statusCode = 403;
|
|
16
|
+
ctx.meta.$responseType = 'text/html';
|
|
17
|
+
return 'Forbidden: Admin access required';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Read documentation files
|
|
21
|
+
const docsPath = path.join(__dirname, '../../');
|
|
22
|
+
const technicalDoc = fs.readFileSync(path.join(docsPath, 'APPROVAL_COMMENTS_UPGRADE.md'), 'utf8');
|
|
23
|
+
const operationsDoc = fs.readFileSync(path.join(docsPath, 'APPROVAL_COMMENTS_OPERATIONS.md'), 'utf8');
|
|
24
|
+
|
|
25
|
+
// Detect environment
|
|
26
|
+
const env = process.env.NODE_ENV || 'development';
|
|
27
|
+
const isProduction = env === 'production';
|
|
28
|
+
|
|
29
|
+
// HTML page with 3 tabs (simplified, stable version)
|
|
30
|
+
const html = `
|
|
31
|
+
<!DOCTYPE html>
|
|
32
|
+
<html lang="zh-CN">
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="UTF-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
36
|
+
<title>审批意见字段升级控制台</title>
|
|
37
|
+
<script src="/unpkg/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
38
|
+
<style>
|
|
39
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
40
|
+
body {
|
|
41
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
42
|
+
background: #f5f5f5;
|
|
43
|
+
padding: 20px;
|
|
44
|
+
}
|
|
45
|
+
.container {
|
|
46
|
+
max-width: 1200px;
|
|
47
|
+
margin: 0 auto;
|
|
48
|
+
background: white;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
header {
|
|
54
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
55
|
+
color: white;
|
|
56
|
+
padding: 30px;
|
|
57
|
+
text-align: center;
|
|
58
|
+
}
|
|
59
|
+
header h1 { font-size: 28px; margin-bottom: 8px; }
|
|
60
|
+
header .env-badge {
|
|
61
|
+
display: inline-block;
|
|
62
|
+
padding: 4px 12px;
|
|
63
|
+
border-radius: 12px;
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
font-weight: bold;
|
|
66
|
+
margin-top: 8px;
|
|
67
|
+
}
|
|
68
|
+
header .env-prod { background: #e53e3e; }
|
|
69
|
+
header .env-test { background: #48bb78; }
|
|
70
|
+
|
|
71
|
+
.tabs {
|
|
72
|
+
display: flex;
|
|
73
|
+
background: #f7fafc;
|
|
74
|
+
border-bottom: 2px solid #e2e8f0;
|
|
75
|
+
}
|
|
76
|
+
.tab {
|
|
77
|
+
flex: 1;
|
|
78
|
+
padding: 16px;
|
|
79
|
+
text-align: center;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
border: none;
|
|
82
|
+
background: none;
|
|
83
|
+
font-size: 15px;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
color: #4a5568;
|
|
86
|
+
transition: all 0.2s;
|
|
87
|
+
}
|
|
88
|
+
.tab:hover { background: #edf2f7; }
|
|
89
|
+
.tab.active {
|
|
90
|
+
color: #667eea;
|
|
91
|
+
border-bottom: 3px solid #667eea;
|
|
92
|
+
background: white;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tab-content {
|
|
96
|
+
padding: 30px;
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
.tab-content.active { display: block; }
|
|
100
|
+
|
|
101
|
+
.doc-content {
|
|
102
|
+
line-height: 1.8;
|
|
103
|
+
color: #2d3748;
|
|
104
|
+
max-height: 600px;
|
|
105
|
+
overflow-y: auto;
|
|
106
|
+
padding: 20px;
|
|
107
|
+
background: #f7fafc;
|
|
108
|
+
border-radius: 6px;
|
|
109
|
+
}
|
|
110
|
+
.doc-content pre {
|
|
111
|
+
background: #1a202c;
|
|
112
|
+
color: #e2e8f0;
|
|
113
|
+
padding: 16px;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
overflow-x: auto;
|
|
116
|
+
margin: 16px 0;
|
|
117
|
+
}
|
|
118
|
+
.doc-content code {
|
|
119
|
+
background: #edf2f7;
|
|
120
|
+
padding: 2px 6px;
|
|
121
|
+
border-radius: 3px;
|
|
122
|
+
color: #e53e3e;
|
|
123
|
+
font-size: 90%;
|
|
124
|
+
}
|
|
125
|
+
.doc-content pre code {
|
|
126
|
+
background: none;
|
|
127
|
+
color: inherit;
|
|
128
|
+
padding: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.operation-panel {
|
|
132
|
+
background: #f7fafc;
|
|
133
|
+
padding: 24px;
|
|
134
|
+
border-radius: 6px;
|
|
135
|
+
margin-bottom: 20px;
|
|
136
|
+
}
|
|
137
|
+
.operation-panel h3 {
|
|
138
|
+
color: #2d3748;
|
|
139
|
+
margin-bottom: 16px;
|
|
140
|
+
font-size: 18px;
|
|
141
|
+
}
|
|
142
|
+
.form-group {
|
|
143
|
+
margin-bottom: 16px;
|
|
144
|
+
}
|
|
145
|
+
.form-group label {
|
|
146
|
+
display: block;
|
|
147
|
+
margin-bottom: 6px;
|
|
148
|
+
color: #4a5568;
|
|
149
|
+
font-weight: 500;
|
|
150
|
+
}
|
|
151
|
+
.form-group input[type="text"],
|
|
152
|
+
.form-group input[type="number"] {
|
|
153
|
+
width: 100%;
|
|
154
|
+
padding: 10px;
|
|
155
|
+
border: 1px solid #cbd5e0;
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
font-size: 14px;
|
|
158
|
+
}
|
|
159
|
+
.form-group input[type="checkbox"] {
|
|
160
|
+
margin-right: 8px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.btn {
|
|
164
|
+
padding: 12px 24px;
|
|
165
|
+
border: none;
|
|
166
|
+
border-radius: 6px;
|
|
167
|
+
font-size: 15px;
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition: all 0.2s;
|
|
171
|
+
margin-right: 12px;
|
|
172
|
+
}
|
|
173
|
+
.btn-primary {
|
|
174
|
+
background: #667eea;
|
|
175
|
+
color: white;
|
|
176
|
+
}
|
|
177
|
+
.btn-primary:hover { background: #5a67d8; }
|
|
178
|
+
.btn-danger {
|
|
179
|
+
background: #f56565;
|
|
180
|
+
color: white;
|
|
181
|
+
}
|
|
182
|
+
.btn-danger:hover { background: #e53e3e; }
|
|
183
|
+
.btn:disabled {
|
|
184
|
+
opacity: 0.5;
|
|
185
|
+
cursor: not-allowed;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.result-panel {
|
|
189
|
+
margin-top: 24px;
|
|
190
|
+
padding: 20px;
|
|
191
|
+
border-radius: 6px;
|
|
192
|
+
display: none;
|
|
193
|
+
}
|
|
194
|
+
.result-panel.success {
|
|
195
|
+
background: #c6f6d5;
|
|
196
|
+
border: 1px solid #9ae6b4;
|
|
197
|
+
color: #22543d;
|
|
198
|
+
}
|
|
199
|
+
.result-panel.error {
|
|
200
|
+
background: #fed7d7;
|
|
201
|
+
border: 1px solid #fc8181;
|
|
202
|
+
color: #742a2a;
|
|
203
|
+
}
|
|
204
|
+
.result-panel h4 {
|
|
205
|
+
margin-bottom: 12px;
|
|
206
|
+
font-size: 16px;
|
|
207
|
+
}
|
|
208
|
+
.result-panel pre {
|
|
209
|
+
background: rgba(0,0,0,0.05);
|
|
210
|
+
padding: 12px;
|
|
211
|
+
border-radius: 4px;
|
|
212
|
+
overflow-x: auto;
|
|
213
|
+
font-size: 13px;
|
|
214
|
+
max-height: 300px;
|
|
215
|
+
overflow-y: auto;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.loading {
|
|
219
|
+
display: inline-block;
|
|
220
|
+
width: 16px;
|
|
221
|
+
height: 16px;
|
|
222
|
+
border: 3px solid rgba(255,255,255,.3);
|
|
223
|
+
border-radius: 50%;
|
|
224
|
+
border-top-color: #fff;
|
|
225
|
+
animation: spin 1s ease-in-out infinite;
|
|
226
|
+
margin-left: 8px;
|
|
227
|
+
}
|
|
228
|
+
@keyframes spin {
|
|
229
|
+
to { transform: rotate(360deg); }
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
232
|
+
</head>
|
|
233
|
+
<body>
|
|
234
|
+
<div class="container">
|
|
235
|
+
<header>
|
|
236
|
+
<h1>审批意见字段升级控制台</h1>
|
|
237
|
+
<p>Approval Comments Migration Console</p>
|
|
238
|
+
<span class="env-badge ${isProduction ? 'env-prod' : 'env-test'}">
|
|
239
|
+
环境: ${isProduction ? 'PRODUCTION' : 'TEST/DEV'}
|
|
240
|
+
</span>
|
|
241
|
+
</header>
|
|
242
|
+
|
|
243
|
+
<div class="tabs">
|
|
244
|
+
<button class="tab" data-tab="tech-doc">📚 技术文档</button>
|
|
245
|
+
<button class="tab" data-tab="ops-manual">📖 操作手册</button>
|
|
246
|
+
<button class="tab active" data-tab="execute">⚙️ 执行操作</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="tab-content" id="tech-doc">
|
|
250
|
+
<div class="doc-content">
|
|
251
|
+
<pre>${technicalDoc.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="tab-content" id="ops-manual">
|
|
256
|
+
<div class="doc-content">
|
|
257
|
+
<pre>${operationsDoc.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div class="tab-content active" id="execute">
|
|
262
|
+
<div class="operation-panel">
|
|
263
|
+
<h3>🧪 集成测试 (Integration Test)</h3>
|
|
264
|
+
<p style="color: #666; margin-bottom: 15px;">
|
|
265
|
+
${isProduction ? '⚠️ 生产环境禁止运行集成测试' : '✅ 测试环境 - 可以运行自动化测试'}
|
|
266
|
+
</p>
|
|
267
|
+
<button class="btn btn-success" onclick="executeIntegrationTest()" ${isProduction ? 'disabled' : ''}>
|
|
268
|
+
🧪 运行自动化集成测试
|
|
269
|
+
</button>
|
|
270
|
+
<div class="result-panel" id="test-result"></div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div class="operation-panel">
|
|
274
|
+
<h3>🚀 升级操作 (Migration)</h3>
|
|
275
|
+
<div class="form-group">
|
|
276
|
+
<label>
|
|
277
|
+
<input type="checkbox" id="migrate-dryrun" checked>
|
|
278
|
+
Dry Run(仅模拟,不实际修改数据)
|
|
279
|
+
</label>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="form-group">
|
|
282
|
+
<label>Flow ID (可选,留空则处理所有符合条件的表单):</label>
|
|
283
|
+
<input type="text" id="migrate-fid" placeholder="示例: 60abc123...">
|
|
284
|
+
</div>
|
|
285
|
+
<div class="form-group">
|
|
286
|
+
<label>Batch Size (批量大小,默认500):</label>
|
|
287
|
+
<input type="number" id="migrate-batchsize" value="500" min="1" max="1000">
|
|
288
|
+
</div>
|
|
289
|
+
<button class="btn btn-primary" id="migrate-btn" onclick="executeMigration()">
|
|
290
|
+
▶ 执行升级
|
|
291
|
+
</button>
|
|
292
|
+
<div class="result-panel" id="migrate-result"></div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div class="operation-panel">
|
|
296
|
+
<h3>↩️ 还原操作 (Rollback)</h3>
|
|
297
|
+
<div class="form-group">
|
|
298
|
+
<label>
|
|
299
|
+
<input type="checkbox" id="rollback-dryrun" checked>
|
|
300
|
+
Dry Run(仅模拟,不实际修改数据)
|
|
301
|
+
</label>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="form-group">
|
|
304
|
+
<label>Flow ID (可选,留空则处理所有符合条件的表单):</label>
|
|
305
|
+
<input type="text" id="rollback-fid" placeholder="示例: 60abc123...">
|
|
306
|
+
</div>
|
|
307
|
+
<div class="form-group">
|
|
308
|
+
<label>Batch Size (批量大小,默认500):</label>
|
|
309
|
+
<input type="number" id="rollback-batchsize" value="500" min="1" max="1000">
|
|
310
|
+
</div>
|
|
311
|
+
<button class="btn btn-danger" id="rollback-btn" onclick="executeRollback()">
|
|
312
|
+
↩️ 执行还原
|
|
313
|
+
</button>
|
|
314
|
+
<div class="result-panel" id="rollback-result"></div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<script>
|
|
320
|
+
// Tab switching
|
|
321
|
+
$('.tab').click(function() {
|
|
322
|
+
const target = $(this).data('tab');
|
|
323
|
+
$('.tab, .tab-content').removeClass('active');
|
|
324
|
+
$(this).addClass('active');
|
|
325
|
+
$('#' + target).addClass('active');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Execute migration
|
|
329
|
+
function executeMigration() {
|
|
330
|
+
const dryRun = $('#migrate-dryrun').is(':checked');
|
|
331
|
+
const fid = $('#migrate-fid').val().trim();
|
|
332
|
+
const batchSize = parseInt($('#migrate-batchsize').val()) || 500;
|
|
333
|
+
|
|
334
|
+
const params = { dryRun, batchSize };
|
|
335
|
+
if (fid) params.fid = fid;
|
|
336
|
+
|
|
337
|
+
executeOperation('/api/workflow/migrateApprovalCommentsField', params, 'migrate-result');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Execute rollback
|
|
341
|
+
function executeRollback() {
|
|
342
|
+
if (!confirm('确定要执行还原操作吗?这将恢复字段到旧格式。')) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const dryRun = $('#rollback-dryrun').is(':checked');
|
|
347
|
+
const fid = $('#rollback-fid').val().trim();
|
|
348
|
+
const batchSize = parseInt($('#rollback-batchsize').val()) || 500;
|
|
349
|
+
|
|
350
|
+
const params = { dryRun, batchSize };
|
|
351
|
+
if (fid) params.fid = fid;
|
|
352
|
+
|
|
353
|
+
executeOperation('/api/workflow/rollbackApprovalCommentsField', params, 'rollback-result');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Execute integration test
|
|
357
|
+
function executeIntegrationTest() {
|
|
358
|
+
const $btn = $('button[onclick="executeIntegrationTest()"]');
|
|
359
|
+
const $result = $('#test-result');
|
|
360
|
+
|
|
361
|
+
// Clear previous results and show button loading state
|
|
362
|
+
$btn.prop('disabled', true).text('⏳ 测试中...');
|
|
363
|
+
$result.removeClass('success error').hide();
|
|
364
|
+
|
|
365
|
+
$.ajax({
|
|
366
|
+
url: '/api/workflow/upgrade/test-approval-comments',
|
|
367
|
+
type: 'GET',
|
|
368
|
+
success: function(data) {
|
|
369
|
+
// Restore button state
|
|
370
|
+
$btn.prop('disabled', false).text('🧪 运行自动化集成测试');
|
|
371
|
+
|
|
372
|
+
// Display test results
|
|
373
|
+
$result.removeClass('error').addClass(data.success ? 'success' : 'error').show();
|
|
374
|
+
|
|
375
|
+
let html = '<h4>' + (data.success ? '✅ 测试通过' : '❌ 测试失败') + '</h4>';
|
|
376
|
+
html += '<p><strong>总测试数:</strong> ' + data.summary.totalTests + '</p>';
|
|
377
|
+
html += '<p><strong>通过:</strong> ' + data.summary.passed + ' | <strong>失败:</strong> ' + data.summary.failed + ' | <strong>跳过:</strong> ' + data.summary.skipped + '</p>';
|
|
378
|
+
html += '<p><strong>执行时间:</strong> ' + data.executionTime + '</p>';
|
|
379
|
+
html += '<hr><h5>测试详情:</h5>';
|
|
380
|
+
data.tests.forEach(function(test) {
|
|
381
|
+
const icon = test.status === 'passed' ? '✅' : test.status === 'failed' ? '❌' : '⏭️';
|
|
382
|
+
html += '<p>' + icon + ' ' + test.name + ': ' + test.message + '</p>';
|
|
383
|
+
});
|
|
384
|
+
if (data.errors.length > 0) {
|
|
385
|
+
html += '<hr><h5>错误信息:</h5>';
|
|
386
|
+
data.errors.forEach(function(err) {
|
|
387
|
+
html += '<p style="color: red;">• ' + err + '</p>';
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
html += '<details style="margin-top:15px"><summary>▼ 展开完整结果</summary><pre style="background:#f5f5f5;padding:10px;border-radius:4px">' + JSON.stringify(data, null, 2) + '</pre></details>';
|
|
391
|
+
$result.html(html);
|
|
392
|
+
},
|
|
393
|
+
error: function(xhr) {
|
|
394
|
+
// Restore button state
|
|
395
|
+
$btn.prop('disabled', false).text('🧪 运行自动化集成测试');
|
|
396
|
+
|
|
397
|
+
// Display error result
|
|
398
|
+
$result.removeClass('success').addClass('error').show();
|
|
399
|
+
const errorMsg = xhr.responseJSON || xhr.responseText || '未知错误';
|
|
400
|
+
$result.html('<h4>❌ 测试失败</h4><pre style="background:#f5f5f5;padding:10px;border-radius:4px">' + JSON.stringify(errorMsg, null, 2) + '</pre></details>');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Execute operation
|
|
406
|
+
function executeOperation(url, params, resultId) {
|
|
407
|
+
const $btn = $('#' + resultId.replace('-result', '-btn'));
|
|
408
|
+
const $result = $('#' + resultId);
|
|
409
|
+
|
|
410
|
+
// Clear previous results and show button loading state
|
|
411
|
+
$btn.prop('disabled', true).text('⏳ 执行中...');
|
|
412
|
+
$result.removeClass('success error').hide();
|
|
413
|
+
|
|
414
|
+
$.ajax({
|
|
415
|
+
url: url,
|
|
416
|
+
type: 'GET',
|
|
417
|
+
data: params,
|
|
418
|
+
success: function(data) {
|
|
419
|
+
// Restore button state
|
|
420
|
+
$btn.prop('disabled', false).text($btn.attr('id') === 'migrate-btn' ? '▶ 执行升级' : '↩️ 执行还原');
|
|
421
|
+
|
|
422
|
+
// Display success result
|
|
423
|
+
$result.removeClass('error').addClass('success').show();
|
|
424
|
+
|
|
425
|
+
let html = '<h4>✅ 操作成功</h4>';
|
|
426
|
+
html += '<hr style="margin: 10px 0;">';
|
|
427
|
+
html += '<div style="line-height: 2;"><strong>📊 统计信息:</strong></div>';
|
|
428
|
+
html += '<p>• 处理表单数: <strong>' + (data.totalForms || 0) + '</strong></p>';
|
|
429
|
+
html += '<p>• 处理字段数: <strong>' + (data.totalFields || 0) + '</strong></p>';
|
|
430
|
+
html += '<p>• 错误数: <strong style="color:' + (data.errors && data.errors.length > 0 ? 'red' : 'green') + '">' + (data.errors ? data.errors.length : 0) + '</strong></p>';
|
|
431
|
+
if (data.errors && data.errors.length > 0) {
|
|
432
|
+
html += '<hr><h5>错误列表:</h5>';
|
|
433
|
+
data.errors.forEach(function(err) {
|
|
434
|
+
html += '<p style="color: red; margin-left: 15px;">• ' + (err.error || err) + '</p>';
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
html += '<details style="margin-top:15px"><summary>▼ 展开完整结果</summary><pre style="max-height:300px;overflow:auto;background:#f5f5f5;padding:10px;border-radius:4px">' + JSON.stringify(data, null, 2) + '</pre></details>';
|
|
438
|
+
$result.html(html);
|
|
439
|
+
},
|
|
440
|
+
error: function(xhr) {
|
|
441
|
+
// Restore button state
|
|
442
|
+
$btn.prop('disabled', false).text($btn.attr('id') === 'migrate-btn' ? '▶ 执行升级' : '↩️ 执行还原');
|
|
443
|
+
|
|
444
|
+
// Display error result
|
|
445
|
+
$result.removeClass('success').addClass('error').show();
|
|
446
|
+
const errorMsg = xhr.responseJSON || xhr.responseText || '未知错误';
|
|
447
|
+
$result.html('<h4>❌ 操作失败</h4><pre style="background:#f5f5f5;padding:10px;border-radius:4px">' + JSON.stringify(errorMsg, null, 2) + '</pre>');
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
</script>
|
|
452
|
+
</body>
|
|
453
|
+
</html>
|
|
454
|
+
`;
|
|
455
|
+
|
|
456
|
+
// Set response type to HTML
|
|
457
|
+
ctx.meta.$responseType = 'text/html';
|
|
458
|
+
return html;
|
|
459
|
+
}
|
|
460
|
+
};
|
package/src/rests/index.js
CHANGED
|
@@ -12,4 +12,8 @@ module.exports = {
|
|
|
12
12
|
...require('./getInstanceServiceSchema'),
|
|
13
13
|
flow_copy: require('./flow_copy'),
|
|
14
14
|
migrateTemplates: require('./migrateTemplates'),
|
|
15
|
-
|
|
15
|
+
migrateApprovalCommentsField: require('./migrateApprovalCommentsField'),
|
|
16
|
+
rollbackApprovalCommentsField: require('./rollbackApprovalCommentsField'),
|
|
17
|
+
integrationTestApprovalComments: require('./integrationTestApprovalComments'),
|
|
18
|
+
approvalCommentsConsole: require('./approvalCommentsConsole'),
|
|
19
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Test for Approval Comments Field Migration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const migrateHandler = require('./migrateApprovalCommentsField');
|
|
6
|
+
const rollbackHandler = require('./rollbackApprovalCommentsField');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
rest: {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
fullPath: '/api/workflow/upgrade/test-approval-comments'
|
|
12
|
+
},
|
|
13
|
+
async handler(ctx) {
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const testResults = {
|
|
16
|
+
success: false,
|
|
17
|
+
environment: process.env.NODE_ENV || 'development',
|
|
18
|
+
summary: { totalTests: 0, passed: 0, failed: 0, skipped: 0 },
|
|
19
|
+
tests: [],
|
|
20
|
+
executionTime: null,
|
|
21
|
+
errors: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Test 1: Environment Check
|
|
26
|
+
testResults.tests.push({ name: '环境检查', status: 'running', startTime: Date.now() });
|
|
27
|
+
|
|
28
|
+
const isProd = testResults.environment === 'production';
|
|
29
|
+
if (isProd) {
|
|
30
|
+
testResults.tests[0].status = 'skipped';
|
|
31
|
+
testResults.tests[0].message = '生产环境禁止运行集成测试';
|
|
32
|
+
testResults.summary.skipped++;
|
|
33
|
+
testResults.summary.totalTests++;
|
|
34
|
+
ctx.meta.$statusCode = 403;
|
|
35
|
+
return { ...testResults, executionTime: `${((Date.now() - startTime) / 1000).toFixed(2)}s` };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
testResults.tests[0].status = 'passed';
|
|
39
|
+
testResults.tests[0].message = '测试环境验证通过';
|
|
40
|
+
testResults.summary.passed++;
|
|
41
|
+
testResults.summary.totalTests++;
|
|
42
|
+
|
|
43
|
+
// Test 2: Migration
|
|
44
|
+
testResults.tests.push({ name: '执行迁移操作', status: 'running', startTime: Date.now() });
|
|
45
|
+
try {
|
|
46
|
+
const migrateResult = await migrateHandler.handler(ctx);
|
|
47
|
+
if (migrateResult.totalForms === 0) {
|
|
48
|
+
testResults.tests[1].status = 'skipped';
|
|
49
|
+
testResults.tests[1].message = '没有找到需要迁移的表单';
|
|
50
|
+
testResults.summary.skipped++;
|
|
51
|
+
} else {
|
|
52
|
+
testResults.tests[1].status = 'passed';
|
|
53
|
+
testResults.tests[1].message = `成功处理 ${migrateResult.totalForms} 个表单, ${migrateResult.totalFields || 0} 个字段`;
|
|
54
|
+
testResults.summary.passed++;
|
|
55
|
+
}
|
|
56
|
+
testResults.summary.totalTests++;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
testResults.tests[1].status = 'failed';
|
|
59
|
+
testResults.tests[1].message = `迁移失败: ${error.message}`;
|
|
60
|
+
testResults.summary.failed++;
|
|
61
|
+
testResults.summary.totalTests++;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Test 3: Rollback
|
|
65
|
+
testResults.tests.push({ name: '执行回滚操作', status: 'running', startTime: Date.now() });
|
|
66
|
+
try {
|
|
67
|
+
const rollbackResult = await rollbackHandler.handler(ctx);
|
|
68
|
+
if (rollbackResult.totalForms === 0) {
|
|
69
|
+
testResults.tests[2].status = 'skipped';
|
|
70
|
+
testResults.tests[2].message = '没有找到需要回滚的表单';
|
|
71
|
+
testResults.summary.skipped++;
|
|
72
|
+
} else {
|
|
73
|
+
testResults.tests[2].status = 'passed';
|
|
74
|
+
testResults.tests[2].message = `成功回滚 ${rollbackResult.totalForms} 个表单, ${rollbackResult.totalFields || 0} 个字段`;
|
|
75
|
+
testResults.summary.passed++;
|
|
76
|
+
}
|
|
77
|
+
testResults.summary.totalTests++;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
testResults.tests[2].status = 'failed';
|
|
80
|
+
testResults.tests[2].message = `回滚失败: ${error.message}`;
|
|
81
|
+
testResults.summary.failed++;
|
|
82
|
+
testResults.summary.totalTests++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
testResults.success = testResults.summary.failed === 0;
|
|
86
|
+
testResults.executionTime = `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
|
|
87
|
+
return testResults;
|
|
88
|
+
|
|
89
|
+
} catch (error) {
|
|
90
|
+
testResults.errors.push(error.message);
|
|
91
|
+
testResults.executionTime = `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
|
|
92
|
+
ctx.meta.$statusCode = 500;
|
|
93
|
+
return testResults;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|