zentao-api 0.6.9 → 0.7.1
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/dist/browser/zentao-api.global.js +18 -18
- package/dist/modules/override.js +325 -2
- package/dist/version.js +2 -2
- package/package.json +1 -1
package/dist/modules/override.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
|
-
import { extendModuleAction, defineModuleActions } from './define.js';
|
|
1
|
+
import { extendModuleAction, defineModules, defineModuleActions } from './define.js';
|
|
2
2
|
import { extractResult } from './resolve.js';
|
|
3
3
|
import { snapshotToMarkdown } from '../utils/doc-helper/markdown.js';
|
|
4
4
|
import { isRecord } from '../utils/index.js';
|
|
5
|
+
import { request } from '../request/index.js';
|
|
6
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
7
|
+
/** 这些内置接口要求 JSON,不能把 HTTP 200 的 PHP/SQL 错误文本视为成功。 */
|
|
8
|
+
const requestJSON = async ({ request: command, body, bodyType, client, timeout, insecure }) => {
|
|
9
|
+
const response = await client.request(command.path, {
|
|
10
|
+
method: command.action.method.toUpperCase(),
|
|
11
|
+
query: command.query, body, bodyType, timeout, insecure, responseType: 'response',
|
|
12
|
+
});
|
|
13
|
+
const responseText = await response.text();
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(responseText);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {
|
|
19
|
+
status: 'fail',
|
|
20
|
+
message: `Invalid JSON response from ${command.module}/${command.action.name}.`,
|
|
21
|
+
httpStatus: response.status,
|
|
22
|
+
responseText,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
5
26
|
/**
|
|
6
27
|
* 内置覆盖 / 扩展定义。
|
|
7
28
|
*
|
|
@@ -64,6 +85,264 @@ import { isRecord } from '../utils/index.js';
|
|
|
64
85
|
* @internal
|
|
65
86
|
*/
|
|
66
87
|
export function applyBuiltinOverrides() {
|
|
88
|
+
defineModules({
|
|
89
|
+
name: 'db',
|
|
90
|
+
display: '数据库',
|
|
91
|
+
actions: [
|
|
92
|
+
{
|
|
93
|
+
name: 'query',
|
|
94
|
+
minVersion: ['22.7', 'biz13.7', 'max8.7', 'ipd5.7'],
|
|
95
|
+
display: '执行 SQL 查询',
|
|
96
|
+
description: '返回查询结果行及分页;使用 raw: true 可获取原始响应中的 SQL、列信息和执行耗时。',
|
|
97
|
+
type: 'list',
|
|
98
|
+
method: 'post',
|
|
99
|
+
path: '/db/query',
|
|
100
|
+
resultType: 'list',
|
|
101
|
+
resultGetter: 'rows',
|
|
102
|
+
pagerGetter: { pageID: 'page', recPerPage: 'limit', recTotal: 'total' },
|
|
103
|
+
request: requestJSON,
|
|
104
|
+
requestBody: {
|
|
105
|
+
required: true,
|
|
106
|
+
schema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
required: ['sql'],
|
|
109
|
+
properties: {
|
|
110
|
+
sql: { type: 'string', description: 'SQL 查询语句' },
|
|
111
|
+
page: { type: 'integer', description: '页码,默认 1' },
|
|
112
|
+
limit: { type: 'integer', description: '每页记录数,默认 100' },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
example: { sql: 'select * from zt_config', page: 4, limit: 3 },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'tables',
|
|
120
|
+
minVersion: ['22.7', 'biz13.7', 'max8.7', 'ipd5.7'],
|
|
121
|
+
display: '获取数据库表列表',
|
|
122
|
+
type: 'list',
|
|
123
|
+
method: 'get',
|
|
124
|
+
path: '/db/tables',
|
|
125
|
+
resultType: 'list',
|
|
126
|
+
resultGetter: 'tables',
|
|
127
|
+
request: requestJSON,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'table',
|
|
131
|
+
minVersion: ['22.7', 'biz13.7', 'max8.7', 'ipd5.7'],
|
|
132
|
+
display: '获取数据库表结构',
|
|
133
|
+
description: 'meta 返回表信息、主键和列定义;sql 返回包含表信息、dialect 和建表 SQL 的对象。',
|
|
134
|
+
type: 'get',
|
|
135
|
+
method: 'get',
|
|
136
|
+
path: '/db/tables/{table}',
|
|
137
|
+
pathParams: { table: '数据库表名,例如 zt_config' },
|
|
138
|
+
params: [{
|
|
139
|
+
name: 'type',
|
|
140
|
+
type: 'string',
|
|
141
|
+
description: '表结构返回格式',
|
|
142
|
+
defaultValue: 'meta',
|
|
143
|
+
options: [{ value: 'meta', label: '元数据' }, { value: 'sql', label: '建表 SQL' }],
|
|
144
|
+
}],
|
|
145
|
+
resultType: 'object',
|
|
146
|
+
resultGetter: data => isRecord(data) ? data.table ?? data : data,
|
|
147
|
+
request: requestJSON,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
// 知识库接口尚未纳入 OpenAPI,需部署对应商业知识库扩展。
|
|
152
|
+
const knowledgeMinVersion = ['biz13.7', 'max8.7', 'ipd5.7'];
|
|
153
|
+
const knowledgeType = {
|
|
154
|
+
type: 'string',
|
|
155
|
+
description: '知识类型,省略或空字符串表示不限;text/file 不可与非空 objectType 同时使用。',
|
|
156
|
+
options: [
|
|
157
|
+
{ value: 'object', label: '对象知识' },
|
|
158
|
+
{ value: 'text', label: '文本知识' },
|
|
159
|
+
{ value: 'file', label: '文件知识' },
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
const knowledgeObjectType = {
|
|
163
|
+
type: 'string',
|
|
164
|
+
description: '来源对象类型;单独指定时按 type=object 筛选,每次只接受一个编码。',
|
|
165
|
+
options: [
|
|
166
|
+
{ value: 'story', label: '需求' },
|
|
167
|
+
{ value: 'task', label: '任务' },
|
|
168
|
+
{ value: 'case', label: '测试用例' },
|
|
169
|
+
{ value: 'bug', label: 'Bug' },
|
|
170
|
+
{ value: 'plan', label: '产品计划' },
|
|
171
|
+
{ value: 'release', label: '发布' },
|
|
172
|
+
{ value: 'feedback', label: '反馈' },
|
|
173
|
+
{ value: 'ticket', label: '工单' },
|
|
174
|
+
{ value: 'doc', label: '文档(含接口文档)' },
|
|
175
|
+
{ value: 'issue', label: '问题' },
|
|
176
|
+
{ value: 'risk', label: '风险' },
|
|
177
|
+
{ value: 'opportunity', label: '机会' },
|
|
178
|
+
{ value: 'practice', label: '最佳实践' },
|
|
179
|
+
{ value: 'component', label: '组件' },
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
defineModules([
|
|
183
|
+
{
|
|
184
|
+
name: 'knowledgelib',
|
|
185
|
+
display: '知识库',
|
|
186
|
+
description: '需部署商业知识库扩展,仅返回已发布且当前用户可访问的知识库。',
|
|
187
|
+
actions: [],
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'knowledge',
|
|
191
|
+
display: '知识',
|
|
192
|
+
description: '需部署商业知识库扩展,支持浏览知识、向量搜索、正文关键词搜索和读取已保存正文。',
|
|
193
|
+
actions: [],
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
defineModuleActions('knowledgelib', {
|
|
197
|
+
name: 'list',
|
|
198
|
+
minVersion: knowledgeMinVersion,
|
|
199
|
+
display: '获取知识库列表',
|
|
200
|
+
type: 'list',
|
|
201
|
+
method: 'get',
|
|
202
|
+
path: '/ai/knowledgelibs',
|
|
203
|
+
resultType: 'list',
|
|
204
|
+
resultGetter: 'data',
|
|
205
|
+
pagerGetter: 'pager',
|
|
206
|
+
request: requestJSON,
|
|
207
|
+
params: [
|
|
208
|
+
{
|
|
209
|
+
name: 'type',
|
|
210
|
+
type: 'string',
|
|
211
|
+
description: '库类型,省略或空字符串时合并两类可见库',
|
|
212
|
+
options: [{ value: 'my', label: '我的知识库' }, { value: 'team', label: '组织知识库' }],
|
|
213
|
+
},
|
|
214
|
+
{ name: 'keyword', type: 'string', description: '知识库名称或描述关键词,首尾空白会被移除' },
|
|
215
|
+
{ name: 'pageID', type: 'number', defaultValue: 1, description: '页码,从 1 开始的正整数' },
|
|
216
|
+
{ name: 'recPerPage', type: 'number', defaultValue: 20, description: '每页条数,范围 1~100' },
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
defineModuleActions('knowledge', [
|
|
220
|
+
{
|
|
221
|
+
name: 'list',
|
|
222
|
+
minVersion: knowledgeMinVersion,
|
|
223
|
+
display: '获取知识库内知识列表',
|
|
224
|
+
description: '返回当前用户可见的知识条目,按本地 ID 降序排列;不支持标题关键词查询。',
|
|
225
|
+
type: 'list',
|
|
226
|
+
method: 'get',
|
|
227
|
+
path: '/ai/knowledgelibs/{libID}/knowledges',
|
|
228
|
+
pathParams: { libID: '本地知识库 ID,正整数' },
|
|
229
|
+
resultType: 'list',
|
|
230
|
+
resultGetter: 'data',
|
|
231
|
+
pagerGetter: 'pager',
|
|
232
|
+
request: requestJSON,
|
|
233
|
+
params: [
|
|
234
|
+
{ name: 'type', ...knowledgeType },
|
|
235
|
+
{ name: 'objectType', ...knowledgeObjectType },
|
|
236
|
+
{ name: 'pageID', type: 'number', defaultValue: 1, description: '页码,从 1 开始的正整数' },
|
|
237
|
+
{ name: 'recPerPage', type: 'number', defaultValue: 20, description: '每页条数,范围 1~100' },
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: 'embeddingsSearch',
|
|
242
|
+
minVersion: knowledgeMinVersion,
|
|
243
|
+
display: '多知识库向量搜索',
|
|
244
|
+
description: '仅检索已有索引,需 ai.searchknowledgelib 权限。按匹配度降序返回片段,不分页;用 knowledgeID 获取完整正文,chunkID 仅标识片段。',
|
|
245
|
+
type: 'list',
|
|
246
|
+
method: 'post',
|
|
247
|
+
path: '/ai/knowledges/embeddingssearch',
|
|
248
|
+
resultType: 'list',
|
|
249
|
+
resultGetter: 'data',
|
|
250
|
+
request: requestJSON,
|
|
251
|
+
requestBody: {
|
|
252
|
+
required: true,
|
|
253
|
+
mediaType: 'application/json',
|
|
254
|
+
schema: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
required: ['keyword', 'libIDs'],
|
|
257
|
+
example: { keyword: '如何处理接口请求超时', libIDs: [12, 18], minSimilarity: 0.7, limit: 5 },
|
|
258
|
+
properties: {
|
|
259
|
+
keyword: { type: 'string', minLength: 1, description: '搜索问题或关键词,去除首尾空白后不能为空' },
|
|
260
|
+
libIDs: {
|
|
261
|
+
type: 'array',
|
|
262
|
+
minItems: 1,
|
|
263
|
+
items: { type: 'integer', minimum: 1 },
|
|
264
|
+
description: '本地知识库 ID 的非空正整数数组,例如 [12,18];不接受字符串元素,重复 ID 自动去重',
|
|
265
|
+
},
|
|
266
|
+
type: knowledgeType,
|
|
267
|
+
objectType: knowledgeObjectType,
|
|
268
|
+
minSimilarity: { type: 'number', minimum: 0, maximum: 1, defaultValue: 0.5, description: '最小匹配度,范围 [0,1]' },
|
|
269
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, defaultValue: 5, description: '整次多库搜索最多返回的片段数,范围 1~100' },
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'search',
|
|
276
|
+
minVersion: knowledgeMinVersion,
|
|
277
|
+
display: '多知识库正文关键词搜索',
|
|
278
|
+
description: '需 ai.searchknowledgelib 权限,仅按字面文本匹配已保存的 content,标题不参与,不依赖向量索引。返回完整正文及 contentType,每条知识只返回一次,按知识 ID 降序分页。',
|
|
279
|
+
type: 'list',
|
|
280
|
+
method: 'post',
|
|
281
|
+
path: '/ai/knowledges/search',
|
|
282
|
+
resultType: 'list',
|
|
283
|
+
resultGetter: 'data',
|
|
284
|
+
pagerGetter: 'pager',
|
|
285
|
+
request: requestJSON,
|
|
286
|
+
requestBody: {
|
|
287
|
+
required: true,
|
|
288
|
+
mediaType: 'application/json',
|
|
289
|
+
schema: {
|
|
290
|
+
type: 'object',
|
|
291
|
+
required: ['keywords', 'libIDs'],
|
|
292
|
+
example: { keywords: ['登录', '超时'], libIDs: [12, 18], matchMode: 'any', pageID: 1, recPerPage: 20 },
|
|
293
|
+
properties: {
|
|
294
|
+
keywords: {
|
|
295
|
+
type: 'array',
|
|
296
|
+
minItems: 1,
|
|
297
|
+
items: { type: 'string', minLength: 1 },
|
|
298
|
+
description: '非空关键词数组,每项去除首尾空白后为 1~200 个 Unicode 字符,去重后最多 20 项;%、_ 和反斜杠按普通字符匹配',
|
|
299
|
+
},
|
|
300
|
+
libIDs: {
|
|
301
|
+
type: 'array',
|
|
302
|
+
minItems: 1,
|
|
303
|
+
items: { type: 'integer', minimum: 1 },
|
|
304
|
+
description: '本地知识库 ID 的非空正整数数组,例如 [12,18];不接受字符串元素,重复 ID 自动去重',
|
|
305
|
+
},
|
|
306
|
+
matchMode: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
defaultValue: 'any',
|
|
309
|
+
description: '正文关键词匹配方式;仅省略时使用 any,空字符串和 null 无效',
|
|
310
|
+
options: [{ value: 'any', label: '包含任一关键词' }, { value: 'all', label: '同一条正文包含全部关键词' }],
|
|
311
|
+
},
|
|
312
|
+
type: knowledgeType,
|
|
313
|
+
objectType: knowledgeObjectType,
|
|
314
|
+
pageID: { type: 'integer', minimum: 1, defaultValue: 1, description: '页码,从 1 开始的正整数' },
|
|
315
|
+
recPerPage: { type: 'integer', minimum: 1, maximum: 100, defaultValue: 20, description: '每页条数,范围 1~100' },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'get',
|
|
322
|
+
minVersion: knowledgeMinVersion,
|
|
323
|
+
display: '获取知识详细内容',
|
|
324
|
+
description: '返回已保存的完整正文及来源信息,不触发文件提取、知识同步或索引更新。按 contentType 解释正文,尚未保存正文时 content 可为空。',
|
|
325
|
+
type: 'get',
|
|
326
|
+
method: 'get',
|
|
327
|
+
path: '/ai/knowledges/{knowledgeID}',
|
|
328
|
+
pathParams: { knowledgeID: '本地知识条目 ID,正整数;不可使用 chunkID 或来源对象 objectID' },
|
|
329
|
+
resultType: 'object',
|
|
330
|
+
resultGetter: 'data',
|
|
331
|
+
request: requestJSON,
|
|
332
|
+
},
|
|
333
|
+
]);
|
|
334
|
+
for (const [moduleName, actionName] of [['product', 'create'], ['product', 'update'], ['feedback', 'close']]) {
|
|
335
|
+
extendModuleAction(moduleName, actionName, { request: requestJSON });
|
|
336
|
+
}
|
|
337
|
+
// 产品描述是字符串;仅修正该字段,保留上游 OpenAPI 数据和其他请求体字段。
|
|
338
|
+
for (const actionName of ['create', 'update']) {
|
|
339
|
+
extendModuleAction('product', actionName, (action) => {
|
|
340
|
+
const properties = action.requestBody.schema.properties;
|
|
341
|
+
properties.desc.type = 'string';
|
|
342
|
+
delete properties.desc.items;
|
|
343
|
+
return action;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
67
346
|
// 创建执行时,需要添加产品字段
|
|
68
347
|
extendModuleAction('execution', 'create', (action) => {
|
|
69
348
|
const required = action.requestBody.schema?.required;
|
|
@@ -101,8 +380,8 @@ export function applyBuiltinOverrides() {
|
|
|
101
380
|
schema.properties.reviewer ??= {
|
|
102
381
|
type: 'array',
|
|
103
382
|
items: { type: 'string' },
|
|
104
|
-
description: '评审人员',
|
|
105
383
|
};
|
|
384
|
+
schema.properties.reviewer.description = '评审人员,如果无需评审则不传';
|
|
106
385
|
if (schema.required) {
|
|
107
386
|
schema.required = schema.required.filter(name => name !== 'reviewer');
|
|
108
387
|
}
|
|
@@ -121,6 +400,50 @@ export function applyBuiltinOverrides() {
|
|
|
121
400
|
});
|
|
122
401
|
});
|
|
123
402
|
});
|
|
403
|
+
// confirmClose 是控制器参数,留在 JSON body 中会被误当成反馈表字段。
|
|
404
|
+
extendModuleAction('feedback', 'close', {
|
|
405
|
+
beforeRequest: async (command) => {
|
|
406
|
+
const data = { ...command.data };
|
|
407
|
+
const query = { ...command.query };
|
|
408
|
+
if (Object.hasOwn(data, 'confirmClose')) {
|
|
409
|
+
query.confirmClose = data.confirmClose;
|
|
410
|
+
delete data.confirmClose;
|
|
411
|
+
}
|
|
412
|
+
return { data, query };
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
// 确认表单会重置缺省字段;仅补齐可写字段,不回传详情中的其他属性。
|
|
416
|
+
extendModuleAction('bug', 'confirm', {
|
|
417
|
+
request: async ({ request: command, body, bodyType, client, timeout, insecure, options }) => {
|
|
418
|
+
const data = { ...body };
|
|
419
|
+
const fields = ['status', 'assignedTo', 'type', 'pri', 'deadline', 'mailto'];
|
|
420
|
+
const missing = fields.filter(field => data[field] === undefined);
|
|
421
|
+
if (missing.length) {
|
|
422
|
+
const response = await request('bug/get', { id: command.id }, {
|
|
423
|
+
client, timeout, insecure, throwOnFail: true,
|
|
424
|
+
forceRefreshConfig: options.forceRefreshConfig,
|
|
425
|
+
skipVersionCheckOnConfigError: options.skipVersionCheckOnConfigError,
|
|
426
|
+
});
|
|
427
|
+
const current = response.data;
|
|
428
|
+
if (!isRecord(current) || missing.some(field => current[field] === undefined)) {
|
|
429
|
+
throw new ZentaoError('E_API_FAILED', { message: 'Bug details are incomplete; confirmation was not sent.' });
|
|
430
|
+
}
|
|
431
|
+
for (const field of missing) {
|
|
432
|
+
data[field] = field === 'mailto' && typeof current[field] === 'string'
|
|
433
|
+
? current[field].split(',').filter(Boolean)
|
|
434
|
+
: current[field];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (typeof data.status !== 'string' || !data.status.trim()) {
|
|
438
|
+
throw new ZentaoError('E_INVALID_PARAM', { param: 'status', value: String(data.status) });
|
|
439
|
+
}
|
|
440
|
+
// 已关闭 Bug 的指派人是特殊标记,API 会把它当成账号校验,需调用方明确指定。
|
|
441
|
+
if (data.assignedTo === 'closed') {
|
|
442
|
+
throw new ZentaoError('E_INVALID_PARAM', { param: 'assignedTo', value: 'closed' });
|
|
443
|
+
}
|
|
444
|
+
return requestJSON({ request: command, body: data, bodyType, client, timeout, insecure, options });
|
|
445
|
+
},
|
|
446
|
+
});
|
|
124
447
|
// 修改 acl 字段默认值为 open
|
|
125
448
|
[
|
|
126
449
|
['product', 'create'],
|
package/dist/version.js
CHANGED