yidaconnector 2026.6.11
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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yida.js +670 -0
- package/lib/app/form-navigation.js +58 -0
- package/lib/app/get-schema.js +538 -0
- package/lib/auth/auth.js +294 -0
- package/lib/auth/cdp-browser-login.js +390 -0
- package/lib/auth/codex-login.js +71 -0
- package/lib/auth/login.js +475 -0
- package/lib/auth/org.js +363 -0
- package/lib/auth/qr-login.js +1563 -0
- package/lib/core/chalk.js +384 -0
- package/lib/core/check-update.js +82 -0
- package/lib/core/cli-error.js +39 -0
- package/lib/core/command-manifest.js +106 -0
- package/lib/core/env-cmd.js +545 -0
- package/lib/core/env-manager.js +601 -0
- package/lib/core/env.js +287 -0
- package/lib/core/i18n.js +177 -0
- package/lib/core/locales/ar.js +805 -0
- package/lib/core/locales/de.js +805 -0
- package/lib/core/locales/en.js +1623 -0
- package/lib/core/locales/es.js +805 -0
- package/lib/core/locales/fr.js +805 -0
- package/lib/core/locales/hi.js +805 -0
- package/lib/core/locales/ja.js +1197 -0
- package/lib/core/locales/ko.js +807 -0
- package/lib/core/locales/pt.js +805 -0
- package/lib/core/locales/vi.js +805 -0
- package/lib/core/locales/zh-HK.js +1233 -0
- package/lib/core/locales/zh.js +1584 -0
- package/lib/core/query-data.js +781 -0
- package/lib/core/redact.js +100 -0
- package/lib/core/utils.js +799 -0
- package/lib/core/yida-client.js +117 -0
- package/package.json +94 -0
- package/project/config.json +4 -0
- package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
- package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
- package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
- package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
- package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
- package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-crm.md +463 -0
- package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/build-skills-package.js +406 -0
- package/scripts/check-syntax.js +59 -0
- package/scripts/demo-dws.sh +106 -0
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1566 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +115 -0
- package/scripts/generate-command-docs.js +109 -0
- package/scripts/nightly-smoke.js +134 -0
- package/scripts/postinstall.js +545 -0
- package/scripts/solution-center-runner.js +368 -0
- package/scripts/validate-ci.sh +50 -0
- package/scripts/validate-command-manifest.js +119 -0
- package/scripts/validate-package-size.js +78 -0
- package/scripts/validate-skills.js +247 -0
- package/scripts/validate-structure.js +66 -0
- package/yida-skills/SKILL.md +163 -0
- package/yida-skills/references/yida-api.md +1309 -0
- package/yida-skills/skills/large-file-write/SKILL.md +91 -0
- package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
- package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
- package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
- package/yida-skills/skills/yida-login/SKILL.md +159 -0
- package/yida-skills/skills/yida-logout/SKILL.md +67 -0
|
@@ -0,0 +1,1566 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
addResource,
|
|
10
|
+
createRegistry,
|
|
11
|
+
decodeCookieData,
|
|
12
|
+
getConfig,
|
|
13
|
+
runCli,
|
|
14
|
+
writeCookieCache,
|
|
15
|
+
writeRegistry,
|
|
16
|
+
} = require('./runner');
|
|
17
|
+
const { validateSkillCoverage } = require('./skill-coverage');
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
20
|
+
const DEFAULT_PAGE_SOURCE = path.join(ROOT, 'project', 'pages', 'src', 'demo-compat-smoke.oyd.jsx');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_STAGES = [
|
|
23
|
+
'auth',
|
|
24
|
+
'app',
|
|
25
|
+
'form',
|
|
26
|
+
'page',
|
|
27
|
+
'data',
|
|
28
|
+
'permission',
|
|
29
|
+
'share',
|
|
30
|
+
'report',
|
|
31
|
+
'dashboard',
|
|
32
|
+
'export',
|
|
33
|
+
'import',
|
|
34
|
+
'batch',
|
|
35
|
+
'task',
|
|
36
|
+
'offline',
|
|
37
|
+
'connector-local',
|
|
38
|
+
'skill-coverage',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const EXTENDED_STAGES = [
|
|
42
|
+
'ai',
|
|
43
|
+
'process',
|
|
44
|
+
'integration',
|
|
45
|
+
'connector-real',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function parseStages(value) {
|
|
49
|
+
if (!value || value === 'default') {return DEFAULT_STAGES;}
|
|
50
|
+
if (value === 'all') {return [...DEFAULT_STAGES, ...EXTENDED_STAGES];}
|
|
51
|
+
return value.split(',').map((stage) => stage.trim()).filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasStage(stages, stage) {
|
|
55
|
+
return stages.includes(stage) || stages.includes('all');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getFullConfig(env = process.env, date = new Date()) {
|
|
59
|
+
const base = getConfig(env, date);
|
|
60
|
+
return {
|
|
61
|
+
...base,
|
|
62
|
+
stages: parseStages(env.YIDACONNECTOR_E2E_FULL_STAGES || env.YIDACONNECTOR_E2E_STAGES),
|
|
63
|
+
full: true,
|
|
64
|
+
updateAppName: env.YIDACONNECTOR_E2E_UPDATE_APP_NAME || `${base.prefix}_App_Renamed`,
|
|
65
|
+
resultAppName: env.YIDACONNECTOR_E2E_RESULT_APP_NAME || `${base.prefix}_PASSED`,
|
|
66
|
+
importAppName: env.YIDACONNECTOR_E2E_IMPORT_APP_NAME || `${base.prefix}_Imported`,
|
|
67
|
+
pageSource: env.YIDACONNECTOR_E2E_PAGE_SOURCE || DEFAULT_PAGE_SOURCE,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureEnabled(config) {
|
|
72
|
+
if (!config.enabled) {
|
|
73
|
+
console.log('Skipping full real E2E; missing: YIDACONNECTOR_E2E=1');
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeJson(filePath, value) {
|
|
80
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
81
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
82
|
+
return filePath;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeText(filePath, value) {
|
|
86
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
87
|
+
fs.writeFileSync(filePath, value, 'utf8');
|
|
88
|
+
return filePath;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function archiveImportReport(workDir) {
|
|
92
|
+
const source = path.join(ROOT, 'yida-migration-report.json');
|
|
93
|
+
if (!fs.existsSync(source)) {return null;}
|
|
94
|
+
const target = path.join(workDir, 'yida-migration-report.json');
|
|
95
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
96
|
+
fs.renameSync(source, target);
|
|
97
|
+
return target;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function i18nText(value) {
|
|
101
|
+
if (!value) {return '';}
|
|
102
|
+
if (typeof value === 'string') {return value;}
|
|
103
|
+
return value.zh_CN || value.en_US || value.pureEn_US || '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function collectFields(node, fields = []) {
|
|
107
|
+
if (!node || typeof node !== 'object') {return fields;}
|
|
108
|
+
if (node.props && node.props.fieldId) {
|
|
109
|
+
fields.push({
|
|
110
|
+
label: i18nText(node.props.label),
|
|
111
|
+
componentName: node.componentName,
|
|
112
|
+
fieldId: node.props.fieldId,
|
|
113
|
+
reportFieldCode: node.componentName === 'SelectField' ? `${node.props.fieldId}_value` : node.props.fieldId,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
for (const value of Object.values(node)) {
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
value.forEach((item) => collectFields(item, fields));
|
|
119
|
+
} else if (value && typeof value === 'object') {
|
|
120
|
+
collectFields(value, fields);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return fields;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fieldByLabel(fields, label) {
|
|
127
|
+
const field = fields.find((item) => item.label === label);
|
|
128
|
+
if (!field) {
|
|
129
|
+
throw new Error(`Field not found in schema: ${label}`);
|
|
130
|
+
}
|
|
131
|
+
return field;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function findValueByKeys(node, keys) {
|
|
135
|
+
if (!node || typeof node !== 'object') {return null;}
|
|
136
|
+
for (const key of keys) {
|
|
137
|
+
if (node[key]) {return node[key];}
|
|
138
|
+
}
|
|
139
|
+
for (const value of Object.values(node)) {
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
for (const item of value) {
|
|
142
|
+
const found = findValueByKeys(item, keys);
|
|
143
|
+
if (found) {return found;}
|
|
144
|
+
}
|
|
145
|
+
} else if (value && typeof value === 'object') {
|
|
146
|
+
const found = findValueByKeys(value, keys);
|
|
147
|
+
if (found) {return found;}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildReportCharts(formUuid, fields) {
|
|
154
|
+
const numberField = fieldByLabel(fields, 'E2E Number');
|
|
155
|
+
const statusField = fieldByLabel(fields, 'E2E Status');
|
|
156
|
+
const cubeCode = formUuid.replace(/-/g, '_');
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
type: 'bar',
|
|
160
|
+
title: 'E2E Number by Status',
|
|
161
|
+
cubeCode,
|
|
162
|
+
xField: {
|
|
163
|
+
fieldCode: statusField.reportFieldCode,
|
|
164
|
+
aliasName: 'E2E Status',
|
|
165
|
+
dataType: 'STRING',
|
|
166
|
+
},
|
|
167
|
+
yField: [
|
|
168
|
+
{
|
|
169
|
+
fieldCode: numberField.reportFieldCode,
|
|
170
|
+
aliasName: 'E2E Number',
|
|
171
|
+
dataType: 'NUMBER',
|
|
172
|
+
aggregateType: 'SUM',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function buildAppendCharts(formUuid, fields) {
|
|
180
|
+
const numberField = fieldByLabel(fields, 'E2E Number');
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
type: 'indicator',
|
|
184
|
+
title: 'E2E Total',
|
|
185
|
+
cubeCode: formUuid.replace(/-/g, '_'),
|
|
186
|
+
kpiField: {
|
|
187
|
+
fieldCode: numberField.reportFieldCode,
|
|
188
|
+
aliasName: 'E2E Number',
|
|
189
|
+
dataType: 'NUMBER',
|
|
190
|
+
aggregateType: 'SUM',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildFieldBehaviorList(fields, editableLabels = []) {
|
|
197
|
+
const editable = new Set(editableLabels);
|
|
198
|
+
return (fields || []).map((field) => ({
|
|
199
|
+
fieldId: field.fieldId,
|
|
200
|
+
fieldBehavior: editable.has(field.label) ? 'NORMAL' : 'READONLY',
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function ensureProcessFields(fields) {
|
|
205
|
+
if (!fields || fields.length === 0) {
|
|
206
|
+
throw new Error('Process stage requires form schema fields; include the form stage before process');
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
textField: fieldByLabel(fields, 'E2E Text'),
|
|
210
|
+
numberField: fieldByLabel(fields, 'E2E Number'),
|
|
211
|
+
statusField: fieldByLabel(fields, 'E2E Status'),
|
|
212
|
+
notesField: fields.find((field) => field.label === 'E2E Notes') || null,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function buildProcessCreateDefinition(fields) {
|
|
217
|
+
const processFields = ensureProcessFields(fields);
|
|
218
|
+
const editableNotes = processFields.notesField ? ['E2E Notes'] : ['E2E Text'];
|
|
219
|
+
const readonlyBehavior = buildFieldBehaviorList(fields);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
nodes: [
|
|
223
|
+
{
|
|
224
|
+
type: 'operator',
|
|
225
|
+
name: 'E2E 资料补充',
|
|
226
|
+
executor: 'originator',
|
|
227
|
+
formConfig: {
|
|
228
|
+
behaviorList: buildFieldBehaviorList(fields, editableNotes),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: 'parallel',
|
|
233
|
+
name: 'E2E 并行会审',
|
|
234
|
+
branches: [
|
|
235
|
+
{
|
|
236
|
+
name: 'E2E 业务审批',
|
|
237
|
+
childNodes: [
|
|
238
|
+
{
|
|
239
|
+
type: 'approval',
|
|
240
|
+
name: 'E2E 业务审批',
|
|
241
|
+
approver: 'originator',
|
|
242
|
+
formConfig: { behaviorList: readonlyBehavior },
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'E2E 财务审批',
|
|
248
|
+
childNodes: [
|
|
249
|
+
{
|
|
250
|
+
type: 'approval',
|
|
251
|
+
name: 'E2E 财务审批',
|
|
252
|
+
approver: 'originator',
|
|
253
|
+
formConfig: { behaviorList: readonlyBehavior },
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
type: 'carbon',
|
|
261
|
+
name: 'E2E 流程抄送',
|
|
262
|
+
approver: 'originator',
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function buildProcessRuleDefinition(fields) {
|
|
269
|
+
const processFields = ensureProcessFields(fields);
|
|
270
|
+
const readonlyBehavior = buildFieldBehaviorList(fields);
|
|
271
|
+
const editableStatus = buildFieldBehaviorList(fields, ['E2E Status']);
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
nodes: [
|
|
275
|
+
{
|
|
276
|
+
type: 'approval',
|
|
277
|
+
name: 'E2E 发起人复核',
|
|
278
|
+
approver: 'originator',
|
|
279
|
+
formConfig: { behaviorList: readonlyBehavior },
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'route',
|
|
283
|
+
conditions: [
|
|
284
|
+
{
|
|
285
|
+
name: 'E2E 数字大于 0',
|
|
286
|
+
rules: [
|
|
287
|
+
{
|
|
288
|
+
fieldId: processFields.numberField.fieldId,
|
|
289
|
+
fieldName: processFields.numberField.label,
|
|
290
|
+
componentType: processFields.numberField.componentName,
|
|
291
|
+
op: 'GreaterThan',
|
|
292
|
+
value: 0,
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
childNodes: [
|
|
296
|
+
{
|
|
297
|
+
type: 'operator',
|
|
298
|
+
name: 'E2E 状态确认',
|
|
299
|
+
executor: 'originator',
|
|
300
|
+
formConfig: { behaviorList: editableStatus },
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
type: 'carbon',
|
|
308
|
+
name: 'E2E 结果抄送',
|
|
309
|
+
approver: 'originator',
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function buildOfficialProcessNodeFixture(context = {}) {
|
|
316
|
+
return {
|
|
317
|
+
publishable: false,
|
|
318
|
+
note: 'Fixture only: advanced official nodes require real tenant-specific props before publishing.',
|
|
319
|
+
nodes: [
|
|
320
|
+
{
|
|
321
|
+
type: 'connector',
|
|
322
|
+
name: 'E2E Connector placeholder',
|
|
323
|
+
connectorRules: {
|
|
324
|
+
connectorId: 'G-CONN-E2E',
|
|
325
|
+
actionId: 'G-ACT-E2E',
|
|
326
|
+
connector: { mode: 1 },
|
|
327
|
+
inputs: { assignments: [] },
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
componentName: 'GetSingleDataNode',
|
|
332
|
+
name: 'E2E Get data placeholder',
|
|
333
|
+
getData: { sourceId: context.formUuid || 'FORM-E2E', quantity: 1, assignments: [] },
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
componentName: 'JavaScriptNode',
|
|
337
|
+
name: 'E2E JavaScript placeholder',
|
|
338
|
+
JavaScript: { action: { code: 'return inputs;' }, outputs: [] },
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
componentName: 'SendMessageNode',
|
|
342
|
+
name: 'E2E Message placeholder',
|
|
343
|
+
sendMessageRules: { messageType: 'workNotice', messageInfo: { title: 'E2E', content: 'Process fixture' } },
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
componentName: 'CycleContainer',
|
|
347
|
+
name: 'E2E Cycle placeholder',
|
|
348
|
+
cycleContainerRules: { sourceId: 'node-source-placeholder' },
|
|
349
|
+
children: [
|
|
350
|
+
{
|
|
351
|
+
componentName: 'JavaScriptNode',
|
|
352
|
+
name: 'E2E Cycle child',
|
|
353
|
+
JavaScript: { action: { code: 'return inputs;' }, outputs: [] },
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
componentName: 'AINode',
|
|
359
|
+
name: 'E2E AI placeholder',
|
|
360
|
+
workFlowRules: { flowId: 'FLOW-E2E', outputs: [], yidaFieldIdList: [] },
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function buildResultApp(context, resultAppName) {
|
|
367
|
+
if (!context.appType) {return null;}
|
|
368
|
+
const baseUrl = 'https://www.aliwork.com';
|
|
369
|
+
const resultPageId = context.dashboardPageId || context.pageId;
|
|
370
|
+
return {
|
|
371
|
+
appType: context.appType,
|
|
372
|
+
name: resultAppName,
|
|
373
|
+
adminUrl: `${baseUrl}/${context.appType}/admin`,
|
|
374
|
+
workbenchUrl: `${baseUrl}/${context.appType}/workbench`,
|
|
375
|
+
formUrl: context.formUuid ? `${baseUrl}/${context.appType}/workbench/${context.formUuid}` : null,
|
|
376
|
+
pageUrl: resultPageId ? `${baseUrl}/${context.appType}/custom/${resultPageId}?isRenderNav=false` : null,
|
|
377
|
+
businessDashboardFormUuid: context.businessDashboardFormUuid || context.businessDashboardPageId || null,
|
|
378
|
+
businessDashboardUrl: context.businessDashboardPageId ? `${baseUrl}/${context.appType}/custom/${context.businessDashboardPageId}?isRenderNav=false` : null,
|
|
379
|
+
businessDashboardShareUrl: context.businessDashboardSharePath ? `${baseUrl}${context.businessDashboardSharePath}` : null,
|
|
380
|
+
reportUrl: context.reportId ? `${baseUrl}/${context.appType}/workbench/${context.reportId}` : null,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function printResultApp(resultApp) {
|
|
385
|
+
if (!resultApp) {return;}
|
|
386
|
+
console.log('Full real E2E result app:');
|
|
387
|
+
console.log(` Name: ${resultApp.name}`);
|
|
388
|
+
console.log(` App: ${resultApp.adminUrl}`);
|
|
389
|
+
if (resultApp.formUrl) {console.log(` Form: ${resultApp.formUrl}`);}
|
|
390
|
+
if (resultApp.pageUrl) {console.log(` Page: ${resultApp.pageUrl}`);}
|
|
391
|
+
if (resultApp.businessDashboardFormUuid) {console.log(` Business Dashboard FormUuid: ${resultApp.businessDashboardFormUuid}`);}
|
|
392
|
+
if (resultApp.businessDashboardUrl) {console.log(` Business Dashboard: ${resultApp.businessDashboardUrl}`);}
|
|
393
|
+
if (resultApp.businessDashboardShareUrl) {console.log(` Business Dashboard Share: ${resultApp.businessDashboardShareUrl}`);}
|
|
394
|
+
if (resultApp.reportUrl) {console.log(` Report: ${resultApp.reportUrl}`);}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function escapeJsString(value) {
|
|
398
|
+
return JSON.stringify(String(value || ''));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function buildDashboardSource(config, context) {
|
|
402
|
+
const modules = [
|
|
403
|
+
'Auth and organization',
|
|
404
|
+
'App create/update/list',
|
|
405
|
+
'Form create/update/options/schema',
|
|
406
|
+
'Custom page build/compile/publish',
|
|
407
|
+
'Data create/query/update',
|
|
408
|
+
'Permission read',
|
|
409
|
+
'Share config',
|
|
410
|
+
'Report create/append',
|
|
411
|
+
'Export/import',
|
|
412
|
+
'Batch and task center',
|
|
413
|
+
'Formula, doctor, samples, CDN config',
|
|
414
|
+
'Connector local parsing',
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
const resourceRows = [
|
|
418
|
+
['App', context.appType],
|
|
419
|
+
['Form', context.formUuid],
|
|
420
|
+
['Smoke Page', context.pageId],
|
|
421
|
+
['Report', context.reportId],
|
|
422
|
+
['Imported App', context.importAppType],
|
|
423
|
+
].filter((row) => row[1]);
|
|
424
|
+
|
|
425
|
+
return `import React from 'react';
|
|
426
|
+
|
|
427
|
+
var RESULT = {
|
|
428
|
+
title: ${escapeJsString(config.resultAppName)},
|
|
429
|
+
runId: ${escapeJsString(config.prefix)},
|
|
430
|
+
appType: ${escapeJsString(context.appType)},
|
|
431
|
+
formUuid: ${escapeJsString(context.formUuid)},
|
|
432
|
+
smokePageId: ${escapeJsString(context.pageId)},
|
|
433
|
+
reportId: ${escapeJsString(context.reportId)},
|
|
434
|
+
importedAppType: ${escapeJsString(context.importAppType)},
|
|
435
|
+
generatedAt: ${escapeJsString(new Date().toISOString())},
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
var MODULES = ${JSON.stringify(modules, null, 2)};
|
|
439
|
+
var RESOURCES = ${JSON.stringify(resourceRows, null, 2)};
|
|
440
|
+
|
|
441
|
+
function metric(label, value, accent) {
|
|
442
|
+
return (
|
|
443
|
+
<div style={{
|
|
444
|
+
background: '#ffffff',
|
|
445
|
+
border: '1px solid #d9e2ef',
|
|
446
|
+
borderRadius: 8,
|
|
447
|
+
padding: 18,
|
|
448
|
+
minHeight: 104,
|
|
449
|
+
}}>
|
|
450
|
+
<div style={{ fontSize: 13, color: '#5f6f89', marginBottom: 10 }}>{label}</div>
|
|
451
|
+
<div style={{ fontSize: 28, lineHeight: '34px', fontWeight: 800, color: accent || '#14213d' }}>{value}</div>
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function statusRow(name, index) {
|
|
457
|
+
return (
|
|
458
|
+
<div key={name} style={{
|
|
459
|
+
display: 'flex',
|
|
460
|
+
alignItems: 'center',
|
|
461
|
+
gap: 10,
|
|
462
|
+
padding: '10px 0',
|
|
463
|
+
borderBottom: index === MODULES.length - 1 ? '0' : '1px solid #edf1f7',
|
|
464
|
+
fontSize: 14,
|
|
465
|
+
color: '#24324a',
|
|
466
|
+
}}>
|
|
467
|
+
<span style={{
|
|
468
|
+
width: 22,
|
|
469
|
+
height: 22,
|
|
470
|
+
borderRadius: '50%',
|
|
471
|
+
background: '#16a34a',
|
|
472
|
+
color: '#fff',
|
|
473
|
+
display: 'inline-flex',
|
|
474
|
+
alignItems: 'center',
|
|
475
|
+
justifyContent: 'center',
|
|
476
|
+
fontSize: 13,
|
|
477
|
+
fontWeight: 800,
|
|
478
|
+
flex: '0 0 auto',
|
|
479
|
+
}}>✓</span>
|
|
480
|
+
<span>{name}</span>
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function resourceRow(row) {
|
|
486
|
+
return (
|
|
487
|
+
<div key={row[0]} style={{
|
|
488
|
+
display: 'grid',
|
|
489
|
+
gridTemplateColumns: '120px minmax(0, 1fr)',
|
|
490
|
+
gap: 12,
|
|
491
|
+
padding: '12px 0',
|
|
492
|
+
borderBottom: '1px solid #edf1f7',
|
|
493
|
+
fontSize: 14,
|
|
494
|
+
}}>
|
|
495
|
+
<div style={{ color: '#667085', fontWeight: 700 }}>{row[0]}</div>
|
|
496
|
+
<div style={{ color: '#172033', wordBreak: 'break-all', fontFamily: 'Menlo, Consolas, monospace' }}>{row[1]}</div>
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export default function Page() {
|
|
502
|
+
return (
|
|
503
|
+
<div style={{
|
|
504
|
+
minHeight: '100vh',
|
|
505
|
+
background: '#f4f7fb',
|
|
506
|
+
color: '#172033',
|
|
507
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
508
|
+
padding: 28,
|
|
509
|
+
boxSizing: 'border-box',
|
|
510
|
+
}}>
|
|
511
|
+
<div style={{ maxWidth: 1180, margin: '0 auto' }}>
|
|
512
|
+
<div style={{
|
|
513
|
+
display: 'flex',
|
|
514
|
+
justifyContent: 'space-between',
|
|
515
|
+
alignItems: 'flex-end',
|
|
516
|
+
gap: 20,
|
|
517
|
+
marginBottom: 22,
|
|
518
|
+
}}>
|
|
519
|
+
<div>
|
|
520
|
+
<div style={{ fontSize: 13, color: '#2864d8', fontWeight: 800, marginBottom: 8 }}>
|
|
521
|
+
YIDACONNECTOR REAL ENVIRONMENT E2E
|
|
522
|
+
</div>
|
|
523
|
+
<div style={{ fontSize: 30, lineHeight: '38px', fontWeight: 850, color: '#101828' }}>
|
|
524
|
+
Full E2E Dashboard
|
|
525
|
+
</div>
|
|
526
|
+
<div style={{ fontSize: 14, color: '#667085', marginTop: 8 }}>
|
|
527
|
+
{RESULT.runId} · generated {RESULT.generatedAt}
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div style={{
|
|
531
|
+
background: '#dcfce7',
|
|
532
|
+
color: '#166534',
|
|
533
|
+
border: '1px solid #86efac',
|
|
534
|
+
borderRadius: 999,
|
|
535
|
+
padding: '8px 14px',
|
|
536
|
+
fontWeight: 800,
|
|
537
|
+
fontSize: 13,
|
|
538
|
+
}}>
|
|
539
|
+
PASSED
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
|
|
543
|
+
<div style={{
|
|
544
|
+
display: 'grid',
|
|
545
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))',
|
|
546
|
+
gap: 14,
|
|
547
|
+
marginBottom: 16,
|
|
548
|
+
}}>
|
|
549
|
+
{metric('Modules', MODULES.length, '#2563eb')}
|
|
550
|
+
{metric('Resources', RESOURCES.length, '#7c3aed')}
|
|
551
|
+
{metric('Result App', RESULT.appType ? 'Ready' : 'Missing', '#16a34a')}
|
|
552
|
+
{metric('Report', RESULT.reportId ? 'Ready' : 'Skipped', '#d97706')}
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div style={{
|
|
556
|
+
display: 'grid',
|
|
557
|
+
gridTemplateColumns: 'minmax(0, 1.05fr) minmax(320px, 0.95fr)',
|
|
558
|
+
gap: 16,
|
|
559
|
+
alignItems: 'start',
|
|
560
|
+
}}>
|
|
561
|
+
<section style={{
|
|
562
|
+
background: '#fff',
|
|
563
|
+
border: '1px solid #d9e2ef',
|
|
564
|
+
borderRadius: 8,
|
|
565
|
+
padding: 20,
|
|
566
|
+
}}>
|
|
567
|
+
<div style={{ fontSize: 18, fontWeight: 800, marginBottom: 10 }}>Coverage</div>
|
|
568
|
+
{MODULES.map(statusRow)}
|
|
569
|
+
</section>
|
|
570
|
+
|
|
571
|
+
<section style={{
|
|
572
|
+
background: '#fff',
|
|
573
|
+
border: '1px solid #d9e2ef',
|
|
574
|
+
borderRadius: 8,
|
|
575
|
+
padding: 20,
|
|
576
|
+
}}>
|
|
577
|
+
<div style={{ fontSize: 18, fontWeight: 800, marginBottom: 10 }}>Resources</div>
|
|
578
|
+
{RESOURCES.map(resourceRow)}
|
|
579
|
+
<div style={{
|
|
580
|
+
marginTop: 18,
|
|
581
|
+
padding: 14,
|
|
582
|
+
background: '#f8fafc',
|
|
583
|
+
border: '1px solid #e5eaf3',
|
|
584
|
+
borderRadius: 8,
|
|
585
|
+
color: '#475467',
|
|
586
|
+
fontSize: 13,
|
|
587
|
+
lineHeight: '20px',
|
|
588
|
+
}}>
|
|
589
|
+
This app is the human-inspectable artifact for the latest successful YidaConnector full real-environment test.
|
|
590
|
+
</div>
|
|
591
|
+
</section>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function buildDashboardSkillSource(config, context) {
|
|
601
|
+
return `import React from 'react';
|
|
602
|
+
|
|
603
|
+
var DASHBOARD = {
|
|
604
|
+
title: 'YidaConnector Dashboard Skill E2E',
|
|
605
|
+
subtitle: ${escapeJsString(config.prefix + ' · yida-dashboard verification')},
|
|
606
|
+
formUuid: ${escapeJsString(context.formUuid || '')},
|
|
607
|
+
reportId: ${escapeJsString(context.reportId || '')},
|
|
608
|
+
generatedAt: ${escapeJsString(new Date().toISOString())}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
var KPIS = [
|
|
612
|
+
{ label: '健康指数', value: '96', unit: '/100', trend: '+4.8%' },
|
|
613
|
+
{ label: '真实资源', value: '4', unit: '类', trend: 'App/Form/Page/Report' },
|
|
614
|
+
{ label: '发布状态', value: 'OK', unit: '', trend: 'health-check' },
|
|
615
|
+
{ label: '看板模式', value: 'ON', unit: '', trend: 'isRenderNav=false' }
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
var CHECKS = [
|
|
619
|
+
'Dashboard mode custom page created',
|
|
620
|
+
'KPI control tower rendered',
|
|
621
|
+
'Responsive chart grid rendered',
|
|
622
|
+
'Screenshot buttons carry sl-no-capture',
|
|
623
|
+
'No mouse-enter event handlers used',
|
|
624
|
+
'Report and form resource IDs displayed'
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
var TIME_FILTERS = ['今日', '本周', '本月', '全链路'];
|
|
628
|
+
|
|
629
|
+
function captureLabel(style) {
|
|
630
|
+
return <span className="sl-no-capture" style={style}>截图</span>;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function kpiCard(item, index) {
|
|
634
|
+
var colors = ['#2563eb', '#16a34a', '#7c3aed', '#d97706'];
|
|
635
|
+
return (
|
|
636
|
+
<div key={item.label} style={{ background: '#fff', border: '1px solid #d9e2ef', borderRadius: 8, padding: 18, minHeight: 126, position: 'relative' }}>
|
|
637
|
+
{captureLabel({ position: 'absolute', top: 12, right: 12, height: 28, lineHeight: '28px', border: '1px solid #d0d7e2', borderRadius: 6, background: '#fff', color: '#344054', padding: '0 10px', fontSize: 13, fontWeight: 800 })}
|
|
638
|
+
<div style={{ color: '#64748b', fontSize: 13, fontWeight: 700 }}>{item.label}</div>
|
|
639
|
+
<div style={{ marginTop: 16 }}><span style={{ color: colors[index % colors.length], fontSize: 34, fontWeight: 850 }}>{item.value}</span><span style={{ color: '#667085', marginLeft: 6 }}>{item.unit}</span></div>
|
|
640
|
+
<div style={{ marginTop: 8, color: '#16a34a', fontSize: 13, fontWeight: 800 }}>{item.trend}</div>
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function chartPanel(title, rows) {
|
|
646
|
+
return (
|
|
647
|
+
<section style={{ background: '#fff', border: '1px solid #d9e2ef', borderRadius: 8, padding: 18, minHeight: 238 }}>
|
|
648
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
649
|
+
<div style={{ fontSize: 18, fontWeight: 850 }}>{title}</div>
|
|
650
|
+
{captureLabel({ display: 'inline-flex', alignItems: 'center', height: 30, border: '1px solid #d0d7e2', borderRadius: 6, background: '#fff', color: '#344054', padding: '0 10px', fontSize: 13, fontWeight: 800 })}
|
|
651
|
+
</div>
|
|
652
|
+
{rows.map(function (row) {
|
|
653
|
+
return (
|
|
654
|
+
<div key={row.label} style={{ marginBottom: 14 }}>
|
|
655
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, marginBottom: 6 }}><span>{row.label}</span><span>{row.value}%</span></div>
|
|
656
|
+
<div style={{ height: 10, background: '#edf2f7', borderRadius: 999, overflow: 'hidden' }}><div style={{ width: row.value + '%', height: '100%', background: row.color }} /></div>
|
|
657
|
+
</div>
|
|
658
|
+
);
|
|
659
|
+
})}
|
|
660
|
+
</section>
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export default function Page() {
|
|
665
|
+
const [activeFilter, setActiveFilter] = useState('全链路');
|
|
666
|
+
return (
|
|
667
|
+
<div style={{ minHeight: '100vh', background: '#f4f7fb', color: '#172033', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', padding: 28, boxSizing: 'border-box' }}>
|
|
668
|
+
<div style={{ maxWidth: 1280, margin: '0 auto' }}>
|
|
669
|
+
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 20, marginBottom: 18 }}>
|
|
670
|
+
<div>
|
|
671
|
+
<div style={{ fontSize: 13, color: '#2563eb', fontWeight: 850, marginBottom: 8 }}>YIDA DASHBOARD SKILL</div>
|
|
672
|
+
<h1 style={{ fontSize: 32, lineHeight: '40px', margin: 0 }}>{DASHBOARD.title}</h1>
|
|
673
|
+
<div style={{ fontSize: 14, color: '#667085', marginTop: 8 }}>{DASHBOARD.subtitle}</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div style={{ background: '#dcfce7', color: '#166534', border: '1px solid #86efac', borderRadius: 999, padding: '8px 14px', fontWeight: 850, fontSize: 13 }}>PASSED</div>
|
|
676
|
+
</header>
|
|
677
|
+
<section style={{ display: 'flex', flexWrap: 'wrap', gap: 10, background: '#fff', border: '1px solid #d9e2ef', borderRadius: 8, padding: 12, marginBottom: 14 }}>
|
|
678
|
+
{TIME_FILTERS.map(function (label) { return <button key={label} type="button" onClick={() => setActiveFilter(label)} style={{ height: 32, padding: '0 12px', border: label === activeFilter ? '1px solid #2563eb' : '1px solid #d0d7e2', borderRadius: 6, background: label === activeFilter ? '#eff6ff' : '#fff', color: label === activeFilter ? '#1d4ed8' : '#344054', fontWeight: 800 }}>{label}</button>; })}
|
|
679
|
+
</section>
|
|
680
|
+
<section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 14, marginBottom: 14 }}>{KPIS.map(kpiCard)}</section>
|
|
681
|
+
<section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 14, marginBottom: 14 }}>
|
|
682
|
+
{chartPanel('模块覆盖率', [{ label: 'App/Form/Page', value: 100, color: '#2563eb' }, { label: 'Data/Permission/Share', value: 100, color: '#16a34a' }, { label: 'Report/Export/Import', value: 100, color: '#7c3aed' }])}
|
|
683
|
+
{chartPanel('真实资源健康度', [{ label: 'Form schema', value: 100, color: '#0ea5e9' }, { label: 'Dashboard publish', value: 100, color: '#22c55e' }, { label: 'Report append', value: 100, color: '#f59e0b' }])}
|
|
684
|
+
</section>
|
|
685
|
+
<section style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(320px, 420px)', gap: 14 }}>
|
|
686
|
+
<div style={{ background: '#fff', border: '1px solid #d9e2ef', borderRadius: 8, padding: 18 }}>
|
|
687
|
+
<div style={{ fontSize: 18, fontWeight: 850, marginBottom: 12 }}>Dashboard Skill Checks</div>
|
|
688
|
+
{CHECKS.map(function (item) { return <div key={item} style={{ padding: '10px 0', borderBottom: '1px solid #edf1f7', color: '#344054' }}>✓ {item}</div>; })}
|
|
689
|
+
</div>
|
|
690
|
+
<div style={{ background: '#101828', borderRadius: 8, padding: 18, color: '#e5e7eb' }}>
|
|
691
|
+
<div style={{ fontSize: 18, fontWeight: 850, marginBottom: 12 }}>Resource Binding</div>
|
|
692
|
+
<div style={{ fontFamily: 'Menlo, Consolas, monospace', fontSize: 12, lineHeight: '22px', wordBreak: 'break-all' }}><div>formUuid: {DASHBOARD.formUuid || 'n/a'}</div><div>reportId: {DASHBOARD.reportId || 'n/a'}</div><div>generatedAt: {DASHBOARD.generatedAt}</div></div>
|
|
693
|
+
</div>
|
|
694
|
+
</section>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
`;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function buildBusinessDashboardSource(config, context) {
|
|
703
|
+
return `import React from 'react';
|
|
704
|
+
|
|
705
|
+
var DASHBOARD = {
|
|
706
|
+
title: '全球业务经营驾驶舱',
|
|
707
|
+
subtitle: ${escapeJsString(config.prefix + ' · real business dashboard acceptance')},
|
|
708
|
+
appType: ${escapeJsString(context.appType || '')},
|
|
709
|
+
formUuid: ${escapeJsString(context.formUuid || '')},
|
|
710
|
+
reportId: ${escapeJsString(context.reportId || '')},
|
|
711
|
+
generatedAt: ${escapeJsString(new Date().toISOString())}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
var KPIS = [
|
|
715
|
+
{ label: '全球营收', value: '¥12.8亿', trend: '+18.6%', tone: '#f97316' },
|
|
716
|
+
{ label: '履约准时率', value: '97.4%', trend: '+3.2%', tone: '#2563eb' },
|
|
717
|
+
{ label: '经营健康度', value: '92', trend: '稳健', tone: '#16a34a' },
|
|
718
|
+
{ label: '风险闭环率', value: '88%', trend: '+9.1%', tone: '#7c3aed' }
|
|
719
|
+
];
|
|
720
|
+
|
|
721
|
+
var CHARTS = [
|
|
722
|
+
{ title: '区域营收贡献', rows: [['华东', 92], ['华南', 78], ['海外', 64]] },
|
|
723
|
+
{ title: '渠道增长结构', rows: [['直营', 86], ['生态伙伴', 72], ['线上', 68]] },
|
|
724
|
+
{ title: '客户分层表现', rows: [['战略客户', 94], ['成长客户', 75], ['长尾客户', 58]] },
|
|
725
|
+
{ title: '供应履约水位', rows: [['库存健康', 82], ['物流时效', 89], ['异常处理', 71]] },
|
|
726
|
+
{ title: '利润质量趋势', rows: [['毛利率', 76], ['费用率优化', 67], ['现金回款', 84]] },
|
|
727
|
+
{ title: '组织协同效率', rows: [['审批效率', 88], ['待办响应', 79], ['跨区协同', 73]] }
|
|
728
|
+
];
|
|
729
|
+
|
|
730
|
+
var RISKS = [
|
|
731
|
+
{ level: '高', title: '海外履约延迟', owner: '供应链中心', action: '发起专项派单' },
|
|
732
|
+
{ level: '中', title: '重点客户续约波动', owner: '大客户部', action: '跟进续约计划' },
|
|
733
|
+
{ level: '中', title: '华南费用率偏高', owner: '财务 BP', action: '复盘费用结构' }
|
|
734
|
+
];
|
|
735
|
+
|
|
736
|
+
var BUSINESS_TIME_FILTERS = ['今日', '本周', '本月', 'FY2026'];
|
|
737
|
+
var BUSINESS_REGION_FILTERS = ['全球', '华东', '华南', '海外'];
|
|
738
|
+
|
|
739
|
+
function captureButton() {
|
|
740
|
+
return <span className="sl-no-capture" style={{ display: 'inline-flex', alignItems: 'center', height: 28, border: '1px solid rgba(255,255,255,0.28)', borderRadius: 999, background: 'rgba(255,255,255,0.12)', color: '#fff', padding: '0 12px', fontWeight: 800, fontSize: 13 }}>截图</span>;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function kpiCard(item) {
|
|
744
|
+
return (
|
|
745
|
+
<section key={item.label} style={{ position: 'relative', borderRadius: 18, padding: 20, minHeight: 132, background: 'linear-gradient(135deg, rgba(15,23,42,0.92), rgba(30,41,59,0.86))', border: '1px solid rgba(255,255,255,0.16)', boxShadow: '0 18px 50px rgba(15,23,42,0.18)' }}>
|
|
746
|
+
<div style={{ position: 'absolute', top: 16, right: 16 }}>{captureButton()}</div>
|
|
747
|
+
<div style={{ color: '#cbd5e1', fontSize: 13, fontWeight: 800 }}>{item.label}</div>
|
|
748
|
+
<div style={{ marginTop: 18, color: item.tone, fontSize: 34, lineHeight: '40px', fontWeight: 900 }}>{item.value}</div>
|
|
749
|
+
<div style={{ marginTop: 8, color: '#e2e8f0', fontSize: 13, fontWeight: 800 }}>{item.trend}</div>
|
|
750
|
+
</section>
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function chartPanel(item, index) {
|
|
755
|
+
var palette = ['#f97316', '#2563eb', '#16a34a', '#7c3aed', '#0ea5e9', '#d97706'];
|
|
756
|
+
return (
|
|
757
|
+
<section key={item.title} style={{ borderRadius: 18, padding: 18, background: '#ffffff', border: '1px solid #eadfd4', minHeight: 220, boxShadow: '0 14px 36px rgba(121,85,54,0.10)' }}>
|
|
758
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
759
|
+
<h3 style={{ margin: 0, color: '#1f2937', fontSize: 17, fontWeight: 900 }}>{item.title}</h3>
|
|
760
|
+
<span className="sl-no-capture" style={{ display: 'inline-flex', alignItems: 'center', height: 28, border: '1px solid #fed7aa', borderRadius: 999, background: '#fff7ed', color: '#c2410c', padding: '0 10px', fontWeight: 800, fontSize: 13 }}>截图</span>
|
|
761
|
+
</div>
|
|
762
|
+
{item.rows.map(function (row) {
|
|
763
|
+
return (
|
|
764
|
+
<div key={row[0]} style={{ marginBottom: 14 }}>
|
|
765
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#475569', fontSize: 13, marginBottom: 7 }}><span>{row[0]}</span><strong>{row[1]}%</strong></div>
|
|
766
|
+
<div style={{ height: 10, background: '#f1f5f9', borderRadius: 999, overflow: 'hidden' }}><div style={{ width: row[1] + '%', height: '100%', background: palette[index % palette.length], borderRadius: 999 }} /></div>
|
|
767
|
+
</div>
|
|
768
|
+
);
|
|
769
|
+
})}
|
|
770
|
+
</section>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function riskRow(item) {
|
|
775
|
+
return (
|
|
776
|
+
<div key={item.title} style={{ display: 'grid', gridTemplateColumns: '42px minmax(0, 1fr) 120px 120px', gap: 12, alignItems: 'center', padding: '12px 0', borderBottom: '1px solid rgba(255,255,255,0.12)' }}>
|
|
777
|
+
<span style={{ display: 'inline-flex', width: 32, height: 32, borderRadius: 999, background: item.level === '高' ? '#ef4444' : '#f59e0b', color: '#fff', alignItems: 'center', justifyContent: 'center', fontWeight: 900 }}>{item.level}</span>
|
|
778
|
+
<strong style={{ color: '#fff' }}>{item.title}</strong>
|
|
779
|
+
<span style={{ color: '#cbd5e1' }}>{item.owner}</span>
|
|
780
|
+
<span className="sl-no-capture" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: 30, border: '1px solid rgba(255,255,255,0.26)', borderRadius: 999, background: 'rgba(255,255,255,0.10)', color: '#fff', fontWeight: 800 }}>{item.action}</span>
|
|
781
|
+
</div>
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export default function Page() {
|
|
786
|
+
const [activeTime, setActiveTime] = useState('FY2026');
|
|
787
|
+
const [activeRegion, setActiveRegion] = useState('全球');
|
|
788
|
+
return (
|
|
789
|
+
<main style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #fff7ed 0%, #eff6ff 42%, #f8fafc 100%)', padding: 28, boxSizing: 'border-box', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' }}>
|
|
790
|
+
<div style={{ maxWidth: 1440, margin: '0 auto' }}>
|
|
791
|
+
<header style={{ borderRadius: 24, padding: 28, background: 'linear-gradient(135deg, #0f172a 0%, #1e3a8a 52%, #92400e 100%)', color: '#fff', marginBottom: 18, boxShadow: '0 24px 70px rgba(15,23,42,0.24)' }}>
|
|
792
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 24, alignItems: 'flex-start' }}>
|
|
793
|
+
<div>
|
|
794
|
+
<div style={{ color: '#fed7aa', fontSize: 13, fontWeight: 900, letterSpacing: 1.8 }}>BUSINESS DASHBOARD ACCEPTANCE</div>
|
|
795
|
+
<h1 style={{ margin: '10px 0 8px', fontSize: 38, lineHeight: '46px', fontWeight: 950 }}>{DASHBOARD.title}</h1>
|
|
796
|
+
<p style={{ margin: 0, color: '#dbeafe', fontSize: 15 }}>{DASHBOARD.subtitle}</p>
|
|
797
|
+
</div>
|
|
798
|
+
<div style={{ textAlign: 'right', color: '#e0f2fe', fontSize: 13, lineHeight: '22px' }}>
|
|
799
|
+
<div>appType: {DASHBOARD.appType || 'n/a'}</div>
|
|
800
|
+
<div>formUuid: {DASHBOARD.formUuid || 'n/a'}</div>
|
|
801
|
+
<div>reportId: {DASHBOARD.reportId || 'n/a'}</div>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
</header>
|
|
805
|
+
|
|
806
|
+
<section style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', padding: 14, borderRadius: 18, background: 'rgba(255,255,255,0.84)', border: '1px solid #e5e7eb', marginBottom: 18 }}>
|
|
807
|
+
{BUSINESS_TIME_FILTERS.map(function (label) { return <button key={label} type="button" onClick={() => setActiveTime(label)} style={{ height: 34, border: label === activeTime ? '1px solid #f97316' : '1px solid #cbd5e1', borderRadius: 999, background: label === activeTime ? '#fff7ed' : '#fff', color: label === activeTime ? '#c2410c' : '#334155', padding: '0 14px', fontWeight: 850 }}>{label}</button>; })}
|
|
808
|
+
<span style={{ color: '#94a3b8' }}>|</span>
|
|
809
|
+
{BUSINESS_REGION_FILTERS.map(function (label) { return <button key={label} type="button" onClick={() => setActiveRegion(label)} style={{ height: 34, border: '1px solid #dbeafe', borderRadius: 999, background: label === activeRegion ? '#eff6ff' : '#fff', color: '#1d4ed8', padding: '0 14px', fontWeight: 850 }}>{label}</button>; })}
|
|
810
|
+
</section>
|
|
811
|
+
|
|
812
|
+
<section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16, marginBottom: 18 }}>{KPIS.map(kpiCard)}</section>
|
|
813
|
+
<section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 16, marginBottom: 18 }}>{CHARTS.map(chartPanel)}</section>
|
|
814
|
+
|
|
815
|
+
<section style={{ borderRadius: 22, padding: 22, background: 'linear-gradient(135deg, #111827, #1e293b)', color: '#fff', border: '1px solid rgba(255,255,255,0.14)' }}>
|
|
816
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
|
|
817
|
+
<h2 style={{ margin: 0, fontSize: 22, fontWeight: 950 }}>风险水位与行动闭环</h2>
|
|
818
|
+
{captureButton()}
|
|
819
|
+
</div>
|
|
820
|
+
{RISKS.map(riskRow)}
|
|
821
|
+
<div style={{ marginTop: 16, color: '#cbd5e1', fontSize: 13 }}>页面内派单入口用于验收交互闭环;真实钉钉待办由 saveFormData → 集成自动化 → 待办2.0 连接器链路承接。</div>
|
|
822
|
+
</section>
|
|
823
|
+
</div>
|
|
824
|
+
</main>
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function ensureAcceptance(registry) {
|
|
831
|
+
registry.stageResults = registry.stageResults || {};
|
|
832
|
+
registry.acceptance = registry.acceptance || { artifacts: [] };
|
|
833
|
+
registry.artifacts = registry.artifacts || [];
|
|
834
|
+
return registry.acceptance;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function toRelativePath(filePath, basePath) {
|
|
838
|
+
if (!filePath) {return null;}
|
|
839
|
+
const baseDir = basePath ? path.dirname(basePath) : ROOT;
|
|
840
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
841
|
+
return relativePath && !relativePath.startsWith('..') ? relativePath : filePath;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function normalizeStageResult(result = {}) {
|
|
845
|
+
return {
|
|
846
|
+
status: result.status || 'passed',
|
|
847
|
+
commands: result.commands || [],
|
|
848
|
+
resources: result.resources || [],
|
|
849
|
+
artifacts: result.artifacts || [],
|
|
850
|
+
summary: result.summary || '',
|
|
851
|
+
skippedReason: result.skippedReason || null,
|
|
852
|
+
optInReason: result.optInReason || null,
|
|
853
|
+
completedAt: result.completedAt || new Date().toISOString(),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function recordStageResult(registry, registryPath, stageName, result) {
|
|
858
|
+
ensureAcceptance(registry);
|
|
859
|
+
registry.stageResults[stageName] = normalizeStageResult(result);
|
|
860
|
+
if (registryPath) {writeRegistry(registryPath, registry);}
|
|
861
|
+
return registry.stageResults[stageName];
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function addAcceptanceArtifact(registry, registryPath, artifact) {
|
|
865
|
+
const acceptance = ensureAcceptance(registry);
|
|
866
|
+
const normalized = {
|
|
867
|
+
...artifact,
|
|
868
|
+
createdAt: artifact.createdAt || new Date().toISOString(),
|
|
869
|
+
};
|
|
870
|
+
acceptance.artifacts.push(normalized);
|
|
871
|
+
registry.artifacts.push(normalized);
|
|
872
|
+
if (registryPath) {writeRegistry(registryPath, registry);}
|
|
873
|
+
return normalized;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function buildAcceptanceManifest(registry, registryPath) {
|
|
877
|
+
ensureAcceptance(registry);
|
|
878
|
+
const stageResults = registry.stageResults || {};
|
|
879
|
+
const resultApp = registry.resultApp || {};
|
|
880
|
+
return {
|
|
881
|
+
runId: registry.runId || registry.id || null,
|
|
882
|
+
suite: registry.suite || 'full',
|
|
883
|
+
status: registry.status || 'running',
|
|
884
|
+
startedAt: registry.startedAt || null,
|
|
885
|
+
finishedAt: registry.finishedAt || null,
|
|
886
|
+
registryPath,
|
|
887
|
+
stages: Object.keys(stageResults).map((name) => ({
|
|
888
|
+
name,
|
|
889
|
+
status: stageResults[name].status,
|
|
890
|
+
summary: stageResults[name].summary,
|
|
891
|
+
skippedReason: stageResults[name].skippedReason,
|
|
892
|
+
optInReason: stageResults[name].optInReason,
|
|
893
|
+
})),
|
|
894
|
+
resources: registry.resources || [],
|
|
895
|
+
urls: {
|
|
896
|
+
adminUrl: resultApp.adminUrl || null,
|
|
897
|
+
workbenchUrl: resultApp.workbenchUrl || null,
|
|
898
|
+
formUrl: resultApp.formUrl || null,
|
|
899
|
+
pageUrl: resultApp.pageUrl || null,
|
|
900
|
+
businessDashboardUrl: resultApp.businessDashboardUrl || null,
|
|
901
|
+
businessDashboardShareUrl: resultApp.businessDashboardShareUrl || null,
|
|
902
|
+
reportUrl: resultApp.reportUrl || null,
|
|
903
|
+
},
|
|
904
|
+
artifacts: registry.acceptance.artifacts || [],
|
|
905
|
+
resultApp: registry.resultApp || null,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function buildAcceptanceReport(registry, registryPath) {
|
|
910
|
+
ensureAcceptance(registry);
|
|
911
|
+
const stageResults = registry.stageResults || {};
|
|
912
|
+
const stages = Object.keys(stageResults).map((name) => ({ name, ...stageResults[name] }));
|
|
913
|
+
const countByStatus = stages.reduce((acc, stage) => {
|
|
914
|
+
acc[stage.status] = (acc[stage.status] || 0) + 1;
|
|
915
|
+
return acc;
|
|
916
|
+
}, {});
|
|
917
|
+
return {
|
|
918
|
+
runId: registry.runId || registry.id || null,
|
|
919
|
+
suite: registry.suite || 'full',
|
|
920
|
+
status: registry.status || 'running',
|
|
921
|
+
registryPath,
|
|
922
|
+
summary: {
|
|
923
|
+
totalStages: stages.length,
|
|
924
|
+
passed: countByStatus.passed || 0,
|
|
925
|
+
failed: countByStatus.failed || 0,
|
|
926
|
+
skipped: countByStatus.skipped || 0,
|
|
927
|
+
commandCount: (registry.commands || []).length,
|
|
928
|
+
resourceCount: (registry.resources || []).length,
|
|
929
|
+
artifactCount: (registry.artifacts || []).length,
|
|
930
|
+
},
|
|
931
|
+
stages,
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function writeAcceptanceArtifacts(registry, registryPath, workDir, writeJsonFile) {
|
|
936
|
+
ensureAcceptance(registry);
|
|
937
|
+
const manifestPath = path.join(workDir, 'acceptance-manifest.json');
|
|
938
|
+
const reportPath = path.join(workDir, 'acceptance-report.json');
|
|
939
|
+
|
|
940
|
+
registry.acceptance.manifestPath = manifestPath;
|
|
941
|
+
registry.acceptance.reportPath = reportPath;
|
|
942
|
+
registry.acceptance.writtenAt = new Date().toISOString();
|
|
943
|
+
addAcceptanceArtifact(registry, null, {
|
|
944
|
+
type: 'acceptance-manifest',
|
|
945
|
+
path: manifestPath,
|
|
946
|
+
relativePath: toRelativePath(manifestPath, registryPath),
|
|
947
|
+
});
|
|
948
|
+
addAcceptanceArtifact(registry, null, {
|
|
949
|
+
type: 'acceptance-report',
|
|
950
|
+
path: reportPath,
|
|
951
|
+
relativePath: toRelativePath(reportPath, registryPath),
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
const manifest = buildAcceptanceManifest(registry, registryPath);
|
|
955
|
+
const report = buildAcceptanceReport(registry, registryPath);
|
|
956
|
+
writeJsonFile(manifestPath, manifest);
|
|
957
|
+
writeJsonFile(reportPath, report);
|
|
958
|
+
|
|
959
|
+
if (registryPath) {writeRegistry(registryPath, registry);}
|
|
960
|
+
return { manifestPath, reportPath, manifest, report };
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function printAcceptanceSummary(registry, registryPath) {
|
|
964
|
+
const report = buildAcceptanceReport(registry, registryPath);
|
|
965
|
+
console.log('Full real E2E acceptance summary:');
|
|
966
|
+
console.log(` Registry: ${registryPath}`);
|
|
967
|
+
if (registry.acceptance && registry.acceptance.manifestPath) {
|
|
968
|
+
console.log(` Manifest: ${registry.acceptance.manifestPath}`);
|
|
969
|
+
}
|
|
970
|
+
if (registry.acceptance && registry.acceptance.reportPath) {
|
|
971
|
+
console.log(` Report: ${registry.acceptance.reportPath}`);
|
|
972
|
+
}
|
|
973
|
+
console.log(` Stages: ${report.summary.passed} passed, ${report.summary.failed} failed, ${report.summary.skipped} skipped`);
|
|
974
|
+
for (const stage of report.stages) {
|
|
975
|
+
const reason = stage.skippedReason || stage.optInReason || stage.summary || '';
|
|
976
|
+
console.log(` - ${stage.name}: ${stage.status}${reason ? ` (${reason})` : ''}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function commandsByName(registry, names) {
|
|
981
|
+
const nameSet = new Set(names);
|
|
982
|
+
return (registry.commands || []).filter((command) => nameSet.has(command.name));
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function resourcesByType(registry, types) {
|
|
986
|
+
const typeSet = new Set(types);
|
|
987
|
+
return (registry.resources || []).filter((resource) => typeSet.has(resource.type));
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function artifactsByType(registry, types) {
|
|
991
|
+
const typeSet = new Set(types);
|
|
992
|
+
return (registry.artifacts || []).filter((artifact) => typeSet.has(artifact.type));
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function commandNames(commands) {
|
|
996
|
+
return commands.map((command) => command.name);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function recordConfiguredStageResults(registry, registryPath, config, context, workDir) {
|
|
1000
|
+
const stageDefinitions = {
|
|
1001
|
+
auth: {
|
|
1002
|
+
commands: ['commands', 'env', 'login', 'org-switch', 'org-list'],
|
|
1003
|
+
summary: config.corpId ? `Login and organization checked for corpId ${config.corpId}` : 'Login and organization checked',
|
|
1004
|
+
resources: [],
|
|
1005
|
+
},
|
|
1006
|
+
app: {
|
|
1007
|
+
commands: ['app-list', 'create-app', 'update-app', 'app-list-after-create'],
|
|
1008
|
+
summary: `App created and renamed: ${context.appType || 'n/a'}`,
|
|
1009
|
+
resources: ['app'],
|
|
1010
|
+
},
|
|
1011
|
+
form: {
|
|
1012
|
+
commands: ['create-form', 'list-forms', 'create-form-add-option', 'create-form-update', 'get-schema', 'get-schema-field', 'get-schema-all'],
|
|
1013
|
+
summary: `Form schema verified: ${context.formUuid || 'n/a'}, fields=${(context.fields || []).length}`,
|
|
1014
|
+
resources: ['form'],
|
|
1015
|
+
artifacts: ['form-update', 'schema'],
|
|
1016
|
+
},
|
|
1017
|
+
page: {
|
|
1018
|
+
commands: ['check-page', 'build-page', 'compile', 'generate-page', 'create-page', 'update-form-config-page', 'publish'],
|
|
1019
|
+
summary: `Smoke dashboard page published: ${context.pageId || 'n/a'}`,
|
|
1020
|
+
resources: ['page'],
|
|
1021
|
+
},
|
|
1022
|
+
data: {
|
|
1023
|
+
commands: ['data-create-form', 'data-get-form', 'data-update-form', 'data-query-form', 'data-query-form-ids'],
|
|
1024
|
+
summary: 'Form data create/get/update/query/ids-only loop verified',
|
|
1025
|
+
resources: [],
|
|
1026
|
+
},
|
|
1027
|
+
permission: {
|
|
1028
|
+
commands: ['get-permission'],
|
|
1029
|
+
summary: `Permission read verified for form ${context.formUuid || 'n/a'}`,
|
|
1030
|
+
resources: [],
|
|
1031
|
+
},
|
|
1032
|
+
share: {
|
|
1033
|
+
commands: ['get-page-config', 'verify-short-url', 'save-share-config', 'get-page-config-after-save'],
|
|
1034
|
+
summary: `Share config verified for ${context.pageId || context.formUuid || 'n/a'}`,
|
|
1035
|
+
resources: [],
|
|
1036
|
+
},
|
|
1037
|
+
report: {
|
|
1038
|
+
commands: ['create-report', 'append-chart'],
|
|
1039
|
+
summary: `Report created and appended: ${context.reportId || 'n/a'}`,
|
|
1040
|
+
resources: ['report'],
|
|
1041
|
+
},
|
|
1042
|
+
dashboard: {
|
|
1043
|
+
commands: ['dashboard-skill-check', 'dashboard-skill-create-page', 'dashboard-skill-publish', 'business-dashboard-check', 'business-dashboard-create-page', 'business-dashboard-publish', 'business-dashboard-verify-short-url', 'business-dashboard-save-share-config'],
|
|
1044
|
+
summary: `Dashboard skill and business dashboard published: ${context.businessDashboardPageId || 'n/a'}`,
|
|
1045
|
+
resources: ['dashboard-skill', 'business-dashboard'],
|
|
1046
|
+
},
|
|
1047
|
+
export: {
|
|
1048
|
+
commands: ['export'],
|
|
1049
|
+
summary: `App export generated under ${workDir}`,
|
|
1050
|
+
resources: [],
|
|
1051
|
+
artifacts: ['export'],
|
|
1052
|
+
},
|
|
1053
|
+
import: {
|
|
1054
|
+
commands: ['import'],
|
|
1055
|
+
summary: `Imported app verified: ${context.importAppType || 'n/a'}`,
|
|
1056
|
+
resources: ['imported-app'],
|
|
1057
|
+
artifacts: ['import-report'],
|
|
1058
|
+
},
|
|
1059
|
+
batch: {
|
|
1060
|
+
commands: ['batch'],
|
|
1061
|
+
summary: 'Batch command chain verified with app-list and get-schema',
|
|
1062
|
+
resources: [],
|
|
1063
|
+
},
|
|
1064
|
+
task: {
|
|
1065
|
+
commands: ['task-center-created', 'data-query-tasks'],
|
|
1066
|
+
summary: 'Task center and data task query verified',
|
|
1067
|
+
resources: [],
|
|
1068
|
+
},
|
|
1069
|
+
offline: {
|
|
1070
|
+
commands: ['formula-evaluate', 'doctor', 'sample-list', 'cdn-config-show'],
|
|
1071
|
+
summary: 'Formula, doctor, sample and CDN config offline checks verified',
|
|
1072
|
+
resources: [],
|
|
1073
|
+
},
|
|
1074
|
+
process: {
|
|
1075
|
+
commands: ['create-process', 'configure-process'],
|
|
1076
|
+
summary: `Process form configured and republished: ${context.processCode || 'n/a'}`,
|
|
1077
|
+
resources: ['process'],
|
|
1078
|
+
artifacts: ['process-definition', 'process-rule-definition', 'process-official-node-fixture'],
|
|
1079
|
+
},
|
|
1080
|
+
'connector-local': {
|
|
1081
|
+
commands: ['connector-gen-template', 'connector-parse-api'],
|
|
1082
|
+
summary: 'Connector template generation and cURL parsing verified locally',
|
|
1083
|
+
resources: [],
|
|
1084
|
+
},
|
|
1085
|
+
'skill-coverage': {
|
|
1086
|
+
commands: [],
|
|
1087
|
+
summary: `Skill coverage matrix checked: ${(registry.skillCoverage && registry.skillCoverage.checked) || 0} skills`,
|
|
1088
|
+
resources: [],
|
|
1089
|
+
},
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
for (const stageName of config.stages || []) {
|
|
1093
|
+
const definition = stageDefinitions[stageName];
|
|
1094
|
+
if (!definition) {continue;}
|
|
1095
|
+
const commands = commandsByName(registry, definition.commands);
|
|
1096
|
+
recordStageResult(registry, null, stageName, {
|
|
1097
|
+
status: 'passed',
|
|
1098
|
+
commands: commandNames(commands),
|
|
1099
|
+
resources: resourcesByType(registry, definition.resources || []),
|
|
1100
|
+
artifacts: artifactsByType(registry, definition.artifacts || []),
|
|
1101
|
+
summary: definition.summary,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (registryPath) {writeRegistry(registryPath, registry);}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function run(options = {}) {
|
|
1108
|
+
const env = options.env || process.env;
|
|
1109
|
+
const config = options.config || getFullConfig(env);
|
|
1110
|
+
const executeCli = options.runCli || runCli;
|
|
1111
|
+
const persistCookieCache = options.writeCookieCache || writeCookieCache;
|
|
1112
|
+
const registryFactory = options.createRegistry || createRegistry;
|
|
1113
|
+
const persistRegistry = options.writeRegistry || writeRegistry;
|
|
1114
|
+
const trackResource = options.addResource || addResource;
|
|
1115
|
+
const writeJsonFile = options.writeJson || writeJson;
|
|
1116
|
+
const writeTextFile = options.writeText || writeText;
|
|
1117
|
+
const archiveReport = options.archiveImportReport || archiveImportReport;
|
|
1118
|
+
|
|
1119
|
+
if (!ensureEnabled(config)) {
|
|
1120
|
+
return { skipped: true };
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
persistCookieCache(decodeCookieData(config));
|
|
1124
|
+
const { registry, registryPath } = registryFactory(config);
|
|
1125
|
+
registry.suite = 'full';
|
|
1126
|
+
registry.stages = config.stages;
|
|
1127
|
+
persistRegistry(registryPath, registry);
|
|
1128
|
+
|
|
1129
|
+
const workDir = path.join(config.registryDir, config.prefix);
|
|
1130
|
+
const context = {
|
|
1131
|
+
appType: null,
|
|
1132
|
+
formUuid: null,
|
|
1133
|
+
pageId: null,
|
|
1134
|
+
reportId: null,
|
|
1135
|
+
businessDashboardPageId: null,
|
|
1136
|
+
businessDashboardFormUuid: null,
|
|
1137
|
+
businessDashboardSharePath: null,
|
|
1138
|
+
importAppType: null,
|
|
1139
|
+
processCode: null,
|
|
1140
|
+
processId: null,
|
|
1141
|
+
processVersion: null,
|
|
1142
|
+
fields: [],
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
function runStep(name, args, stepOptions = {}) {
|
|
1146
|
+
const commandResult = executeCli([...args, '--quiet'], env);
|
|
1147
|
+
registry.commands.push({ name, args, completedAt: new Date().toISOString(), optional: !!stepOptions.optional });
|
|
1148
|
+
persistRegistry(registryPath, registry);
|
|
1149
|
+
if (stepOptions.allowNoJson) {return commandResult;}
|
|
1150
|
+
if (!commandResult.json) {
|
|
1151
|
+
throw new Error(`${name} did not emit a JSON result`);
|
|
1152
|
+
}
|
|
1153
|
+
if (commandResult.json.success === false || commandResult.json.status === 'error') {
|
|
1154
|
+
throw new Error(`${name} failed: ${JSON.stringify(commandResult.json)}`);
|
|
1155
|
+
}
|
|
1156
|
+
return commandResult;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
try {
|
|
1160
|
+
if (hasStage(config.stages, 'auth')) {
|
|
1161
|
+
runStep('commands', ['commands', '--json']);
|
|
1162
|
+
runStep('env', ['env', '--json']);
|
|
1163
|
+
runStep('login', ['login', '--check-only', '--json']);
|
|
1164
|
+
if (config.corpId) {
|
|
1165
|
+
runStep('org-switch', ['org', 'switch', '--corp-id', config.corpId], { allowNoJson: true });
|
|
1166
|
+
}
|
|
1167
|
+
runStep('org-list', ['org', 'list'], { allowNoJson: true });
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (hasStage(config.stages, 'app')) {
|
|
1171
|
+
runStep('app-list', ['app-list', '--size', '1'], { allowNoJson: true });
|
|
1172
|
+
const app = runStep('create-app', [
|
|
1173
|
+
'create-app',
|
|
1174
|
+
config.appName,
|
|
1175
|
+
'--desc',
|
|
1176
|
+
'YidaConnector full real E2E disposable app',
|
|
1177
|
+
'--no-open',
|
|
1178
|
+
]).json;
|
|
1179
|
+
context.appType = app.appType;
|
|
1180
|
+
trackResource(registry, registryPath, { type: 'app', appType: context.appType, name: config.appName, url: app.url });
|
|
1181
|
+
|
|
1182
|
+
runStep('update-app', ['update-app', context.appType, '--name', config.updateAppName]);
|
|
1183
|
+
runStep('app-list-after-create', ['app-list', '--size', '5'], { allowNoJson: true });
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (hasStage(config.stages, 'form')) {
|
|
1187
|
+
const form = runStep('create-form', [
|
|
1188
|
+
'create-form',
|
|
1189
|
+
'create',
|
|
1190
|
+
context.appType,
|
|
1191
|
+
config.formName,
|
|
1192
|
+
config.fieldsFile,
|
|
1193
|
+
'--no-open',
|
|
1194
|
+
]).json;
|
|
1195
|
+
context.formUuid = form.formUuid;
|
|
1196
|
+
trackResource(registry, registryPath, { type: 'form', appType: context.appType, formUuid: context.formUuid, name: config.formName, url: form.url });
|
|
1197
|
+
|
|
1198
|
+
runStep('list-forms', ['list-forms', context.appType]);
|
|
1199
|
+
const addOption = runStep('create-form-add-option', [
|
|
1200
|
+
'create-form',
|
|
1201
|
+
'add-option',
|
|
1202
|
+
context.appType,
|
|
1203
|
+
context.formUuid,
|
|
1204
|
+
'E2E Status',
|
|
1205
|
+
'Blocked',
|
|
1206
|
+
]).json;
|
|
1207
|
+
if (addOption.success === false) {
|
|
1208
|
+
throw new Error(`create-form add-option failed: ${JSON.stringify(addOption)}`);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const changesPath = writeJsonFile(path.join(workDir, 'form-update.json'), [
|
|
1212
|
+
{ action: 'add', field: { type: 'TextareaField', label: 'E2E Notes' } },
|
|
1213
|
+
]);
|
|
1214
|
+
runStep('create-form-update', ['create-form', 'update', context.appType, context.formUuid, changesPath, '--force', '--no-open']);
|
|
1215
|
+
|
|
1216
|
+
const schemaResult = runStep('get-schema', ['get-schema', context.appType, context.formUuid, '--json']).json;
|
|
1217
|
+
context.fields = collectFields(schemaResult.content || schemaResult);
|
|
1218
|
+
runStep('get-schema-field', ['get-schema', context.appType, context.formUuid, '--field', 'E2E Text', '--json']);
|
|
1219
|
+
runStep('get-schema-all', ['get-schema', context.appType, '--all', '--output-dir', path.join(workDir, 'schemas'), '--json']);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
if (hasStage(config.stages, 'page')) {
|
|
1223
|
+
runStep('check-page', ['check-page', config.pageSource, '--json']);
|
|
1224
|
+
runStep('build-page', ['build-page', config.pageSource, '--output', path.join(workDir, 'built-page.jsx'), '--json']);
|
|
1225
|
+
runStep('compile', ['compile', config.pageSource], { allowNoJson: true });
|
|
1226
|
+
runStep('generate-page', ['generate-page', 'todo-mvc', '--output', path.join(workDir, 'generated-todo.oyd.jsx'), '--compile'], { allowNoJson: true });
|
|
1227
|
+
|
|
1228
|
+
const page = runStep('create-page', [
|
|
1229
|
+
'create-page',
|
|
1230
|
+
context.appType,
|
|
1231
|
+
config.pageName,
|
|
1232
|
+
'--mode',
|
|
1233
|
+
'dashboard',
|
|
1234
|
+
'--no-open',
|
|
1235
|
+
]).json;
|
|
1236
|
+
context.pageId = page.pageId;
|
|
1237
|
+
trackResource(registry, registryPath, { type: 'page', appType: context.appType, pageId: context.pageId, name: config.pageName, url: page.url });
|
|
1238
|
+
runStep('update-form-config-page', ['update-form-config', context.appType, context.pageId, 'false', config.pageName], { allowNoJson: true });
|
|
1239
|
+
runStep('publish', ['publish', config.pageSource, context.appType, context.pageId, '--health-check', '--no-open']);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (hasStage(config.stages, 'data')) {
|
|
1243
|
+
const textField = fieldByLabel(context.fields, 'E2E Text');
|
|
1244
|
+
const numberField = fieldByLabel(context.fields, 'E2E Number');
|
|
1245
|
+
const statusField = fieldByLabel(context.fields, 'E2E Status');
|
|
1246
|
+
const createData = {};
|
|
1247
|
+
createData[textField.fieldId] = `${config.prefix} record`;
|
|
1248
|
+
createData[numberField.fieldId] = 42;
|
|
1249
|
+
createData[statusField.fieldId] = 'New';
|
|
1250
|
+
const createResult = runStep('data-create-form', [
|
|
1251
|
+
'data',
|
|
1252
|
+
'create',
|
|
1253
|
+
'form',
|
|
1254
|
+
context.appType,
|
|
1255
|
+
context.formUuid,
|
|
1256
|
+
'--data-json',
|
|
1257
|
+
JSON.stringify(createData),
|
|
1258
|
+
]).json;
|
|
1259
|
+
const formInstId = findValueByKeys(createResult, ['formInstId', 'formInstanceId', 'instanceId']);
|
|
1260
|
+
if (formInstId) {
|
|
1261
|
+
runStep('data-get-form', ['data', 'get', 'form', context.appType, '--inst-id', formInstId]);
|
|
1262
|
+
const updateData = {};
|
|
1263
|
+
updateData[textField.fieldId] = `${config.prefix} record updated`;
|
|
1264
|
+
runStep('data-update-form', ['data', 'update', 'form', context.appType, '--inst-id', formInstId, '--data-json', JSON.stringify(updateData)]);
|
|
1265
|
+
}
|
|
1266
|
+
runStep('data-query-form', ['data', 'query', 'form', context.appType, context.formUuid, '--size', '1']);
|
|
1267
|
+
runStep('data-query-form-ids', ['data', 'query', 'form', context.appType, context.formUuid, '--size', '1', '--ids-only']);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (hasStage(config.stages, 'permission')) {
|
|
1271
|
+
runStep('get-permission', ['get-permission', context.appType, context.formUuid]);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (hasStage(config.stages, 'share')) {
|
|
1275
|
+
const shareTarget = context.pageId || context.formUuid;
|
|
1276
|
+
const sharePath = `/o/${config.prefix.toLowerCase()}-share`;
|
|
1277
|
+
runStep('get-page-config', ['get-page-config', context.appType, shareTarget], { allowNoJson: true });
|
|
1278
|
+
runStep('verify-short-url', ['verify-short-url', context.appType, shareTarget, sharePath], { allowNoJson: true });
|
|
1279
|
+
runStep('save-share-config', ['save-share-config', context.appType, shareTarget, sharePath, 'n'], { allowNoJson: true });
|
|
1280
|
+
runStep('get-page-config-after-save', ['get-page-config', context.appType, shareTarget], { allowNoJson: true });
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (hasStage(config.stages, 'report')) {
|
|
1284
|
+
const chartsPath = writeJsonFile(path.join(workDir, 'report-charts.json'), buildReportCharts(context.formUuid, context.fields));
|
|
1285
|
+
const report = runStep('create-report', ['create-report', context.appType, `${config.prefix}_Report`, chartsPath, '--no-open']).json;
|
|
1286
|
+
context.reportId = report.reportId;
|
|
1287
|
+
trackResource(registry, registryPath, { type: 'report', appType: context.appType, reportId: context.reportId, name: `${config.prefix}_Report`, url: report.url });
|
|
1288
|
+
|
|
1289
|
+
const appendPath = writeJsonFile(path.join(workDir, 'append-charts.json'), buildAppendCharts(context.formUuid, context.fields));
|
|
1290
|
+
runStep('append-chart', ['append-chart', context.appType, context.reportId, appendPath, '--no-open']);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (hasStage(config.stages, 'dashboard')) {
|
|
1294
|
+
const dashboardSkillSourcePath = writeTextFile(path.join(workDir, 'dashboard-skill.oyd.jsx'), buildDashboardSkillSource(config, context));
|
|
1295
|
+
runStep('dashboard-skill-check', ['check-page', dashboardSkillSourcePath, '--json']);
|
|
1296
|
+
const dashboardSkillPage = runStep('dashboard-skill-create-page', [
|
|
1297
|
+
'create-page',
|
|
1298
|
+
context.appType,
|
|
1299
|
+
`${config.prefix}_DashboardSkill`,
|
|
1300
|
+
'--mode',
|
|
1301
|
+
'dashboard',
|
|
1302
|
+
'--no-open',
|
|
1303
|
+
]).json;
|
|
1304
|
+
context.dashboardSkillPageId = dashboardSkillPage.pageId;
|
|
1305
|
+
trackResource(registry, registryPath, { type: 'dashboard-skill', appType: context.appType, pageId: context.dashboardSkillPageId, name: `${config.prefix}_DashboardSkill`, url: dashboardSkillPage.url });
|
|
1306
|
+
runStep('dashboard-skill-publish', ['publish', dashboardSkillSourcePath, context.appType, context.dashboardSkillPageId, '--health-check', '--no-open']);
|
|
1307
|
+
|
|
1308
|
+
const businessDashboardSourcePath = writeTextFile(path.join(workDir, 'business-dashboard.oyd.jsx'), buildBusinessDashboardSource(config, context));
|
|
1309
|
+
runStep('business-dashboard-check', ['check-page', businessDashboardSourcePath, '--json']);
|
|
1310
|
+
const businessDashboardPage = runStep('business-dashboard-create-page', [
|
|
1311
|
+
'create-page',
|
|
1312
|
+
context.appType,
|
|
1313
|
+
`${config.prefix}_BusinessDashboard`,
|
|
1314
|
+
'--mode',
|
|
1315
|
+
'dashboard',
|
|
1316
|
+
'--no-open',
|
|
1317
|
+
]).json;
|
|
1318
|
+
context.businessDashboardPageId = businessDashboardPage.pageId || businessDashboardPage.formUuid;
|
|
1319
|
+
context.businessDashboardFormUuid = businessDashboardPage.formUuid || businessDashboardPage.pageId || null;
|
|
1320
|
+
context.businessDashboardSharePath = `/o/${config.prefix.toLowerCase()}-business-dashboard`;
|
|
1321
|
+
trackResource(registry, registryPath, {
|
|
1322
|
+
type: 'business-dashboard',
|
|
1323
|
+
appType: context.appType,
|
|
1324
|
+
pageId: context.businessDashboardPageId,
|
|
1325
|
+
name: `${config.prefix}_BusinessDashboard`,
|
|
1326
|
+
url: businessDashboardPage.url,
|
|
1327
|
+
sharePath: context.businessDashboardSharePath,
|
|
1328
|
+
});
|
|
1329
|
+
runStep('business-dashboard-publish', ['publish', businessDashboardSourcePath, context.appType, context.businessDashboardPageId, '--health-check', '--no-open']);
|
|
1330
|
+
runStep('business-dashboard-verify-short-url', ['verify-short-url', context.appType, context.businessDashboardPageId, context.businessDashboardSharePath], { allowNoJson: true });
|
|
1331
|
+
runStep('business-dashboard-save-share-config', ['save-share-config', context.appType, context.businessDashboardPageId, context.businessDashboardSharePath, 'n'], { allowNoJson: true });
|
|
1332
|
+
registry.businessDashboard = {
|
|
1333
|
+
appType: context.appType,
|
|
1334
|
+
formUuid: context.businessDashboardFormUuid,
|
|
1335
|
+
pageId: context.businessDashboardPageId,
|
|
1336
|
+
url: `https://www.aliwork.com/${context.appType}/custom/${context.businessDashboardPageId}?isRenderNav=false`,
|
|
1337
|
+
sharePath: context.businessDashboardSharePath,
|
|
1338
|
+
shareUrl: `https://www.aliwork.com${context.businessDashboardSharePath}`,
|
|
1339
|
+
note: 'This is the real business dashboard acceptance page, not the Dashboard Skill smoke page.',
|
|
1340
|
+
};
|
|
1341
|
+
persistRegistry(registryPath, registry);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (hasStage(config.stages, 'export')) {
|
|
1345
|
+
const exportPath = path.join(workDir, 'export.json');
|
|
1346
|
+
runStep('export', ['export', context.appType, exportPath]);
|
|
1347
|
+
registry.artifacts = registry.artifacts || [];
|
|
1348
|
+
registry.artifacts.push({ type: 'export', path: exportPath });
|
|
1349
|
+
persistRegistry(registryPath, registry);
|
|
1350
|
+
|
|
1351
|
+
if (hasStage(config.stages, 'import')) {
|
|
1352
|
+
const imported = runStep('import', ['import', exportPath, config.importAppName]).json;
|
|
1353
|
+
context.importAppType = imported.targetAppType || imported.appType || imported.newAppType;
|
|
1354
|
+
const reportPath = archiveReport(workDir);
|
|
1355
|
+
if (reportPath) {
|
|
1356
|
+
registry.artifacts = registry.artifacts || [];
|
|
1357
|
+
registry.artifacts.push({ type: 'import-report', path: reportPath });
|
|
1358
|
+
persistRegistry(registryPath, registry);
|
|
1359
|
+
}
|
|
1360
|
+
if (context.importAppType) {
|
|
1361
|
+
trackResource(registry, registryPath, { type: 'imported-app', appType: context.importAppType, name: config.importAppName });
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (hasStage(config.stages, 'batch')) {
|
|
1367
|
+
runStep('batch', [
|
|
1368
|
+
'batch',
|
|
1369
|
+
'--commands',
|
|
1370
|
+
`app-list --size 1 ; get-schema ${context.appType} ${context.formUuid} --json`,
|
|
1371
|
+
'--json',
|
|
1372
|
+
'--quiet',
|
|
1373
|
+
]);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (hasStage(config.stages, 'task')) {
|
|
1377
|
+
runStep('task-center-created', ['task-center', 'created', '--size', '1', '--no-detail']);
|
|
1378
|
+
runStep('data-query-tasks', ['data', 'query', 'tasks', context.appType, '--type', 'submitted', '--size', '1']);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (hasStage(config.stages, 'offline')) {
|
|
1382
|
+
const formulaSchemaPath = writeJsonFile(path.join(workDir, 'formula-schema.json'), { fields: context.fields });
|
|
1383
|
+
runStep('formula-evaluate', ['formula', 'evaluate', '1+1', '--schema', formulaSchemaPath, '--json']);
|
|
1384
|
+
runStep('doctor', ['doctor'], { allowNoJson: true });
|
|
1385
|
+
runStep('sample-list', ['sample', '--list'], { allowNoJson: true });
|
|
1386
|
+
runStep('cdn-config-show', ['cdn-config', '--show'], { allowNoJson: true });
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (hasStage(config.stages, 'ai')) {
|
|
1390
|
+
const flashNotePath = writeTextFile(path.join(workDir, 'flash-note.md'), '# E2E Meeting\n\nNeed build a test app.');
|
|
1391
|
+
runStep('flash-to-prd', ['flash-to-prd', '--file', flashNotePath, '--name', `${config.prefix} PRD`], { allowNoJson: true });
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (hasStage(config.stages, 'process')) {
|
|
1395
|
+
if (!context.appType || !context.formUuid) {
|
|
1396
|
+
throw new Error('Process stage requires app and form stages');
|
|
1397
|
+
}
|
|
1398
|
+
const processCreateDefinitionPath = writeJsonFile(
|
|
1399
|
+
path.join(workDir, 'process-create-definition.json'),
|
|
1400
|
+
buildProcessCreateDefinition(context.fields),
|
|
1401
|
+
);
|
|
1402
|
+
const processRuleDefinitionPath = writeJsonFile(
|
|
1403
|
+
path.join(workDir, 'process-rule-definition.json'),
|
|
1404
|
+
buildProcessRuleDefinition(context.fields),
|
|
1405
|
+
);
|
|
1406
|
+
const officialNodeFixturePath = writeJsonFile(
|
|
1407
|
+
path.join(workDir, 'process-official-node-fixture.json'),
|
|
1408
|
+
buildOfficialProcessNodeFixture(context),
|
|
1409
|
+
);
|
|
1410
|
+
registry.artifacts = registry.artifacts || [];
|
|
1411
|
+
registry.artifacts.push(
|
|
1412
|
+
{ type: 'process-definition', path: processCreateDefinitionPath },
|
|
1413
|
+
{ type: 'process-rule-definition', path: processRuleDefinitionPath },
|
|
1414
|
+
{ type: 'process-official-node-fixture', path: officialNodeFixturePath },
|
|
1415
|
+
);
|
|
1416
|
+
persistRegistry(registryPath, registry);
|
|
1417
|
+
|
|
1418
|
+
const processCreate = runStep('create-process', [
|
|
1419
|
+
'create-process',
|
|
1420
|
+
context.appType,
|
|
1421
|
+
'--formUuid',
|
|
1422
|
+
context.formUuid,
|
|
1423
|
+
processCreateDefinitionPath,
|
|
1424
|
+
]).json;
|
|
1425
|
+
context.processCode = processCreate.processCode;
|
|
1426
|
+
if (!context.processCode) {
|
|
1427
|
+
throw new Error(`create-process did not return processCode: ${JSON.stringify(processCreate)}`);
|
|
1428
|
+
}
|
|
1429
|
+
trackResource(registry, registryPath, {
|
|
1430
|
+
type: 'process',
|
|
1431
|
+
appType: context.appType,
|
|
1432
|
+
formUuid: context.formUuid,
|
|
1433
|
+
processCode: context.processCode,
|
|
1434
|
+
name: `${config.prefix}_Process`,
|
|
1435
|
+
url: processCreate.url,
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const processRule = runStep('configure-process', [
|
|
1439
|
+
'configure-process',
|
|
1440
|
+
context.appType,
|
|
1441
|
+
context.formUuid,
|
|
1442
|
+
processRuleDefinitionPath,
|
|
1443
|
+
context.processCode,
|
|
1444
|
+
]).json;
|
|
1445
|
+
context.processId = processRule.processId || null;
|
|
1446
|
+
context.processVersion = processRule.processVersion || null;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
if (hasStage(config.stages, 'connector-local')) {
|
|
1450
|
+
const templatePath = path.join(workDir, 'api-template.md');
|
|
1451
|
+
runStep('connector-gen-template', ['connector', 'gen-template', templatePath], { allowNoJson: true });
|
|
1452
|
+
runStep('connector-parse-api', [
|
|
1453
|
+
'connector',
|
|
1454
|
+
'parse-api',
|
|
1455
|
+
'--curl',
|
|
1456
|
+
"curl 'https://api.example.com/users?id=1' -H 'Authorization: Bearer test'",
|
|
1457
|
+
'--format',
|
|
1458
|
+
'json',
|
|
1459
|
+
]);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
if (hasStage(config.stages, 'skill-coverage')) {
|
|
1463
|
+
const skillCoverage = validateSkillCoverage();
|
|
1464
|
+
registry.skillCoverage = {
|
|
1465
|
+
checked: skillCoverage.checked,
|
|
1466
|
+
levels: Object.keys(skillCoverage.coverage).sort().reduce((acc, name) => {
|
|
1467
|
+
acc[name] = skillCoverage.coverage[name].level;
|
|
1468
|
+
return acc;
|
|
1469
|
+
}, {}),
|
|
1470
|
+
matrix: Object.keys(skillCoverage.coverage).sort().map((name) => ({
|
|
1471
|
+
skill: name,
|
|
1472
|
+
level: skillCoverage.coverage[name].level,
|
|
1473
|
+
stages: skillCoverage.coverage[name].stages || [],
|
|
1474
|
+
commands: skillCoverage.coverage[name].commands || [],
|
|
1475
|
+
tests: skillCoverage.coverage[name].tests || [],
|
|
1476
|
+
reason: skillCoverage.coverage[name].reason || null,
|
|
1477
|
+
})),
|
|
1478
|
+
};
|
|
1479
|
+
persistRegistry(registryPath, registry);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
if (context.appType) {
|
|
1483
|
+
const dashboardSourcePath = writeTextFile(path.join(workDir, 'result-dashboard.oyd.jsx'), buildDashboardSource(config, context));
|
|
1484
|
+
runStep('result-dashboard-check', ['check-page', dashboardSourcePath, '--json']);
|
|
1485
|
+
const dashboardPage = runStep('result-dashboard-create-page', [
|
|
1486
|
+
'create-page',
|
|
1487
|
+
context.appType,
|
|
1488
|
+
`${config.prefix}_Dashboard`,
|
|
1489
|
+
'--mode',
|
|
1490
|
+
'dashboard',
|
|
1491
|
+
'--no-open',
|
|
1492
|
+
]).json;
|
|
1493
|
+
context.dashboardPageId = dashboardPage.pageId;
|
|
1494
|
+
trackResource(registry, registryPath, { type: 'dashboard', appType: context.appType, pageId: context.dashboardPageId, name: `${config.prefix}_Dashboard`, url: dashboardPage.url });
|
|
1495
|
+
runStep('result-dashboard-publish', ['publish', dashboardSourcePath, context.appType, context.dashboardPageId, '--health-check', '--no-open']);
|
|
1496
|
+
runStep('mark-result-app', ['update-app', context.appType, '--name', config.resultAppName]);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
registry.status = 'passed';
|
|
1500
|
+
registry.finishedAt = new Date().toISOString();
|
|
1501
|
+
registry.context = context;
|
|
1502
|
+
registry.resultApp = buildResultApp(context, config.resultAppName);
|
|
1503
|
+
recordConfiguredStageResults(registry, registryPath, config, context, workDir);
|
|
1504
|
+
writeAcceptanceArtifacts(registry, registryPath, workDir, writeJsonFile);
|
|
1505
|
+
persistRegistry(registryPath, registry);
|
|
1506
|
+
printResultApp(registry.resultApp);
|
|
1507
|
+
printAcceptanceSummary(registry, registryPath);
|
|
1508
|
+
console.log(`Full real E2E passed. Registry: ${registryPath}`);
|
|
1509
|
+
return { skipped: false, registryPath, registry };
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
registry.status = 'failed';
|
|
1512
|
+
registry.finishedAt = new Date().toISOString();
|
|
1513
|
+
registry.error = error.message;
|
|
1514
|
+
registry.context = context;
|
|
1515
|
+
registry.resultApp = buildResultApp(context, config.resultAppName);
|
|
1516
|
+
recordStageResult(registry, null, 'run', {
|
|
1517
|
+
status: 'failed',
|
|
1518
|
+
commands: commandNames(registry.commands || []),
|
|
1519
|
+
resources: registry.resources || [],
|
|
1520
|
+
artifacts: registry.artifacts || [],
|
|
1521
|
+
summary: error.message,
|
|
1522
|
+
});
|
|
1523
|
+
try {
|
|
1524
|
+
writeAcceptanceArtifacts(registry, registryPath, workDir, writeJsonFile);
|
|
1525
|
+
} catch (artifactError) {
|
|
1526
|
+
registry.acceptance = registry.acceptance || { artifacts: [] };
|
|
1527
|
+
registry.acceptance.error = artifactError.message;
|
|
1528
|
+
}
|
|
1529
|
+
persistRegistry(registryPath, registry);
|
|
1530
|
+
throw error;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
if (require.main === module) {
|
|
1535
|
+
try {
|
|
1536
|
+
run();
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
console.error(error.message);
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
module.exports = {
|
|
1544
|
+
DEFAULT_STAGES,
|
|
1545
|
+
EXTENDED_STAGES,
|
|
1546
|
+
addAcceptanceArtifact,
|
|
1547
|
+
archiveImportReport,
|
|
1548
|
+
buildAcceptanceManifest,
|
|
1549
|
+
buildAcceptanceReport,
|
|
1550
|
+
buildResultApp,
|
|
1551
|
+
buildDashboardSource,
|
|
1552
|
+
buildDashboardSkillSource,
|
|
1553
|
+
buildBusinessDashboardSource,
|
|
1554
|
+
buildOfficialProcessNodeFixture,
|
|
1555
|
+
buildProcessCreateDefinition,
|
|
1556
|
+
buildProcessRuleDefinition,
|
|
1557
|
+
collectFields,
|
|
1558
|
+
fieldByLabel,
|
|
1559
|
+
findValueByKeys,
|
|
1560
|
+
getFullConfig,
|
|
1561
|
+
parseStages,
|
|
1562
|
+
printAcceptanceSummary,
|
|
1563
|
+
recordStageResult,
|
|
1564
|
+
run,
|
|
1565
|
+
writeAcceptanceArtifacts,
|
|
1566
|
+
};
|