@bangdao-ai/zentao-mcp 1.1.0 → 1.1.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/README.md CHANGED
@@ -141,6 +141,7 @@ pip3 install -r requirements.txt
141
141
  "env": {
142
142
  "ZENTAO_PRODUCT_ID": "365",
143
143
  "ZENTAO_OPENED_BY": "69610",
144
+ "KEY": "643f0f490d1ea0d47520dd270989c99a",
144
145
  "ZENTAO_KEYWORDS": "AI",
145
146
  "ZENTAO_TITLE_PREFIX": ""
146
147
  }
@@ -151,7 +152,8 @@ pip3 install -r requirements.txt
151
152
 
152
153
  **配置说明**:
153
154
  - `ZENTAO_PRODUCT_ID`(必需):产品ID
154
- - `ZENTAO_OPENED_BY`(必需):创建人和指派人ID(同一个人)
155
+ - `ZENTAO_OPENED_BY`(必需):创建人和指派人ID(同一个人),作为API认证的code参数
156
+ - `KEY`(必需):API认证密钥,作为API认证的key参数
155
157
  - `ZENTAO_KEYWORDS`(可选):关键词,默认为空
156
158
  - `ZENTAO_TITLE_PREFIX`(可选):标题前缀,默认为空
157
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bangdao-ai/zentao-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "禅道Bug管理系统MCP工具",
5
5
  "main": "index.js",
6
6
  "bin": {
package/requirements.txt CHANGED
@@ -3,4 +3,5 @@ requests>=2.31.0
3
3
  python-dotenv>=1.0.0
4
4
  fastapi>=0.104.0
5
5
  uvicorn[standard]>=0.24.0
6
+ pytest>=7.0.0
6
7
 
package/src/mcp_server.py CHANGED
@@ -203,10 +203,22 @@ class ZenTaoMCPServer:
203
203
  "enum": ["fixed", "bydesign", "reqchange", "duplicate", "external", "notrepro", "postponed", "willnotfix", "tostory", "history", "configchange"]
204
204
  },
205
205
  "resolved_by": {"type": "string", "description": "解决人(姓名或工号)"},
206
- "resolved_build": {"type": "string", "description": "解决版本ID(如果解决方案是fixed时,解决版本必传)"}
206
+ "resolved_build": {"type": "string", "description": "解决版本ID(可选,如果未提供则默认使用trunk)"}
207
207
  },
208
208
  "required": ["product_id", "bug_ids", "resolution", "resolved_by"]
209
209
  }
210
+ ),
211
+ Tool(
212
+ name="close_bug",
213
+ description="关闭BUG",
214
+ inputSchema={
215
+ "type": "object",
216
+ "properties": {
217
+ "bug_id": {"type": "string", "description": "BUG ID"},
218
+ "comment": {"type": "string", "description": "关闭备注(可选,默认AI关闭)"}
219
+ },
220
+ "required": ["bug_id"]
221
+ }
210
222
  )
211
223
  ]
212
224
 
@@ -230,7 +242,8 @@ class ZenTaoMCPServer:
230
242
  "product_id": product_id,
231
243
  "opened_by": opened_by,
232
244
  "keywords": os.getenv("ZENTAO_KEYWORDS", ""),
233
- "title_prefix": os.getenv("ZENTAO_TITLE_PREFIX", "")
245
+ "title_prefix": os.getenv("ZENTAO_TITLE_PREFIX", ""),
246
+ "role_type": os.getenv("ZENTAO_ROLE_TYPE", "")
234
247
  }
235
248
  temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
236
249
  json.dump(temp_config, temp_file, ensure_ascii=False)
@@ -435,6 +448,13 @@ class ZenTaoMCPServer:
435
448
  result = self.nexus.resolve_bug(product_id, bug_ids, resolution, resolved_by, resolved_build)
436
449
  return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
437
450
 
451
+ elif name == "close_bug":
452
+ bug_id = arguments["bug_id"]
453
+ comment = arguments.get("comment", "AI关闭")
454
+
455
+ result = self.nexus.close_bug(bug_id, comment)
456
+ return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
457
+
438
458
  else:
439
459
  raise ValueError(f"未知的工具: {name}")
440
460
 
@@ -104,13 +104,6 @@ class ZenTaoAPIClient:
104
104
  # 写死的配置
105
105
  BASE_URL = "https://zentao.bangdao-tech.com"
106
106
  TEST_BASE_URL = "https://test-zendao.bangdao-tech.com"
107
- ACCOUNT = "KUBETEST"
108
- SECRET_KEY = "aa287b7b5a4ef5d051f82fb1825ca1ac"
109
- CASE_SECRET_KEY = "db1d522a190f1135c6e7c324bd337fda"
110
- CASE_ACCOUNT = "AUTOTEST"
111
- # 新API的配置
112
- API_KEY = "1acca81c8a4d131cf7c86e772fa64351"
113
- API_CODE = "test"
114
107
 
115
108
  def __init__(self, silent=True, use_test_env=False):
116
109
  """
@@ -121,34 +114,26 @@ class ZenTaoAPIClient:
121
114
  use_test_env (bool): 是否使用测试环境
122
115
  """
123
116
  self.base_url = self.TEST_BASE_URL if use_test_env else self.BASE_URL
124
- self.account = self.ACCOUNT
125
- self.secret_key = self.SECRET_KEY
126
- self.case_secret_key = self.CASE_SECRET_KEY
127
117
  self.silent = silent
128
118
  self.use_test_env = use_test_env
129
-
130
- def generate_bdtoken(self, timestamp):
131
- """生成BDTOKEN(用于BUG创建)"""
132
- token_string = f"accountfmtamp{timestamp}{self.secret_key}"
133
- md5_hash = hashlib.md5()
134
- md5_hash.update(token_string.encode('utf-8'))
135
- return md5_hash.hexdigest()
136
-
137
- def generate_case_bdtoken(self, timestamp):
138
- """生成用例创建用的BDTOKEN"""
139
- token_string = f"accountfmtamp{timestamp}{self.case_secret_key}"
140
- md5_hash = hashlib.md5()
141
- md5_hash.update(token_string.encode('utf-8'))
142
- return md5_hash.hexdigest()
119
+
120
+ # 从环境变量读取 code 和 key
121
+ self.code = os.getenv("ZENTAO_OPENED_BY") or os.getenv("OPENED_BY")
122
+ self.key = os.getenv("ZENTAO_KEY") or os.getenv("KEY")
123
+
124
+ if not self.code:
125
+ raise ValueError("未设置环境变量 ZENTAO_OPENED_BY 或 OPENED_BY(用于 code)")
126
+ if not self.key:
127
+ raise ValueError("未设置环境变量 ZENTAO_KEY 或 KEY(用于 key)")
143
128
 
144
129
  def generate_api_token(self, timestamp):
145
- """生成新API的token(md5(code + key + time),32位小写)"""
146
- token_string = f"{self.API_CODE}{self.API_KEY}{timestamp}"
130
+ """生成统一API的token(md5(code + key + time),32位小写)"""
131
+ token_string = f"{self.code}{self.key}{timestamp}"
147
132
  md5_hash = hashlib.md5()
148
133
  md5_hash.update(token_string.encode('utf-8'))
149
134
  return md5_hash.hexdigest().lower()
150
135
 
151
- def _call_new_api(self, function_name, params=None, form_data=None):
136
+ def _call_new_api(self, function_name, params=None, form_data=None, files=None, m_param='allneedlist'):
152
137
  """
153
138
  调用新的API接口(使用code、time、token认证)
154
139
 
@@ -156,6 +141,8 @@ class ZenTaoAPIClient:
156
141
  function_name: API函数名(f参数)
157
142
  params: URL参数(字典)
158
143
  form_data: POST form-data数据(字典或列表,列表格式用于数组参数)
144
+ files: 文件上传(字典,用于文件上传接口)
145
+ m_param: m参数值(默认allneedlist,某些接口可能需要其他值如bug)
159
146
 
160
147
  Returns:
161
148
  dict: API响应结果
@@ -165,10 +152,10 @@ class ZenTaoAPIClient:
165
152
 
166
153
  api_url = f"{self.base_url}/zentaopms/www/api.php"
167
154
  url_params = {
168
- 'code': self.API_CODE,
155
+ 'code': self.code,
169
156
  'time': current_timestamp,
170
157
  'token': token,
171
- 'm': 'allneedlist',
158
+ 'm': m_param,
172
159
  'f': function_name
173
160
  }
174
161
 
@@ -180,8 +167,73 @@ class ZenTaoAPIClient:
180
167
  'User-Agent': 'Python-ZenTao-Client/1.0'
181
168
  }
182
169
 
170
+ # 打印请求信息
171
+ print("\n" + "=" * 80)
172
+ print("【禅道API请求信息】")
173
+ print("=" * 80)
174
+ print(f"API URL: {api_url}")
175
+ print(f"\n【Headers】")
176
+ for key, value in headers.items():
177
+ print(f" {key}: {value}")
178
+
179
+ print(f"\n【Token生成信息】")
180
+ print(f" code: {self.code}")
181
+ print(f" key: {self.key[:10]}...{self.key[-10:] if len(self.key) > 20 else self.key}") # 只显示部分key,保护隐私
182
+ print(f" time: {current_timestamp}")
183
+ print(f" token生成公式: md5(code + key + time)")
184
+ print(f" token生成字符串: {self.code}{self.key}{current_timestamp}")
185
+ print(f" token: {token}")
186
+
187
+ print(f"\n【URL参数】")
188
+ for key, value in url_params.items():
189
+ print(f" {key}: {value}")
190
+
191
+ # 处理form_data用于显示
192
+ display_form_data = form_data
193
+ if isinstance(form_data, dict):
194
+ # 检查是否有数组类型的值
195
+ processed_data = []
196
+ for key, value in form_data.items():
197
+ if isinstance(value, (list, tuple)):
198
+ for item in value:
199
+ processed_data.append((key, str(item)))
200
+ else:
201
+ processed_data.append((key, str(value)))
202
+ display_form_data = processed_data
203
+
204
+ if display_form_data:
205
+ print(f"\n【Body (form-data)】")
206
+ if isinstance(display_form_data, list):
207
+ for item in display_form_data:
208
+ if isinstance(item, tuple):
209
+ key, value = item
210
+ # 如果是文件,只显示文件名
211
+ if hasattr(value, 'read'):
212
+ print(f" {key}: <文件对象>")
213
+ else:
214
+ print(f" {key}: {value}")
215
+ else:
216
+ print(f" {item}")
217
+ elif isinstance(display_form_data, dict):
218
+ for key, value in display_form_data.items():
219
+ print(f" {key}: {value}")
220
+
221
+ if files:
222
+ print(f"\n【Files】")
223
+ for key, file_info in files.items():
224
+ if isinstance(file_info, tuple):
225
+ filename = file_info[0] if len(file_info) > 0 else "unknown"
226
+ print(f" {key}: {filename}")
227
+ else:
228
+ print(f" {key}: {file_info}")
229
+
230
+ print("=" * 80)
231
+
183
232
  try:
184
- if form_data:
233
+ if files:
234
+ # 文件上传
235
+ response = requests.post(api_url, params=url_params, headers=headers, data=form_data, files=files, timeout=30)
236
+ elif form_data:
185
237
  # 如果form_data是列表,直接使用(用于数组参数)
186
238
  # 如果是字典,需要检查是否有数组类型的值
187
239
  if isinstance(form_data, dict):
@@ -200,9 +252,24 @@ class ZenTaoAPIClient:
200
252
  else:
201
253
  response = requests.post(api_url, params=url_params, headers=headers, timeout=30)
202
254
 
255
+ # 打印响应信息
256
+ print("\n【禅道API响应信息】")
257
+ print("=" * 80)
258
+ print(f"状态码: {response.status_code}")
259
+ print(f"响应Headers:")
260
+ for key, value in response.headers.items():
261
+ print(f" {key}: {value}")
262
+
203
263
  try:
204
- return response.json()
264
+ response_json = response.json()
265
+ print(f"\n响应Body (JSON):")
266
+ print(json.dumps(response_json, ensure_ascii=False, indent=2))
267
+ print("=" * 80 + "\n")
268
+ return response_json
205
269
  except json.JSONDecodeError:
270
+ print(f"\n响应Body (非JSON):")
271
+ print(response.text[:1000]) # 只显示前1000个字符
272
+ print("=" * 80 + "\n")
206
273
  return {
207
274
  "status": 0,
208
275
  "message": "fail",
@@ -211,6 +278,9 @@ class ZenTaoAPIClient:
211
278
  "raw_response": response.text
212
279
  }
213
280
  except requests.exceptions.RequestException as e:
281
+ print(f"\n【请求异常】")
282
+ print(f"错误信息: {str(e)}")
283
+ print("=" * 80 + "\n")
214
284
  return {
215
285
  "status": 0,
216
286
  "message": "fail",
@@ -299,14 +369,14 @@ class ZenTaoAPIClient:
299
369
 
300
370
  return self._call_new_api('getBugList', params=params, form_data=form_data if form_data else None)
301
371
 
302
- def resolve_bug(self, product_id, bug_ids, resolution, resolved_by, resolved_build=None):
372
+ def resolve_bug(self, product_id, bug_ids, resolution="fixed", resolved_by=None, resolved_build=None):
303
373
  """
304
374
  批量解决BUG
305
375
 
306
376
  Args:
307
377
  product_id: 产品ID(必传)
308
378
  bug_ids: BUG ID列表(必传),可以是单个ID或ID列表
309
- resolution: 解决方案(必传),可选值:
379
+ resolution: 解决方案(可选,默认fixed),可选值:
310
380
  fixed: 修复解决
311
381
  bydesign: 设计如此
312
382
  reqchange: 修改需求
@@ -319,18 +389,14 @@ class ZenTaoAPIClient:
319
389
  history: 历史遗留
320
390
  configchange: 调整配置
321
391
  resolved_by: 解决人(必传,姓名或工号)
322
- resolved_build: 解决版本ID(如果解决方案是fixed时,解决版本必传)
392
+ resolved_build: 解决版本ID(可选,如果未提供则默认使用"trunk")
323
393
 
324
394
  Returns:
325
395
  dict: 解决结果
326
396
  """
327
- if resolution == 'fixed' and not resolved_build:
328
- return {
329
- "status": 0,
330
- "message": "fail",
331
- "info": "解决方案为fixed时,解决版本(resolvedBuild)必传",
332
- "data": {}
333
- }
397
+ # 如果没有提供 resolved_build,使用默认值 "trunk"
398
+ if not resolved_build:
399
+ resolved_build = "trunk"
334
400
 
335
401
  params = {'productID': product_id}
336
402
  form_data = {
@@ -349,83 +415,38 @@ class ZenTaoAPIClient:
349
415
 
350
416
  return self._call_new_api('resolveBug', params=params, form_data=form_data)
351
417
 
352
- def create_bug_by_api(self, bug_data, use_form_data=True):
353
- """通过API创建bug"""
354
- current_timestamp = int(time.time())
355
- bdtoken = self.generate_bdtoken(current_timestamp)
356
-
357
- url = f"{self.base_url}/zentaopms/www/index.php"
358
- params = {
359
- 'm': 'allneedlist',
360
- 'f': 'createbugbyapi',
361
- 'account': self.account,
362
- 'tamp': current_timestamp
363
- }
418
+ def close_bug(self, bug_id, comment="AI关闭"):
419
+ """
420
+ 关闭BUG
364
421
 
365
- headers = {
366
- 'BDTOKEN': bdtoken,
367
- 'User-Agent': 'Python-ZenTao-Client/1.0'
368
- }
422
+ Args:
423
+ bug_id: BUG ID(必传)
424
+ comment: 关闭备注(可选,默认"AI关闭")
369
425
 
370
- try:
371
- if use_form_data:
372
- response = requests.post(url, params=params, headers=headers, data=bug_data, timeout=30)
373
- else:
374
- headers['Content-Type'] = 'application/json'
375
- response = requests.post(url, params=params, headers=headers, json=bug_data, timeout=30)
376
-
377
- try:
378
- return response.json()
379
- except json.JSONDecodeError:
380
- return {
381
- "status": -1,
382
- "message": "响应不是有效的JSON格式",
383
- "raw_response": response.text
384
- }
385
- except requests.exceptions.RequestException as e:
426
+ Returns:
427
+ dict: 关闭结果
428
+ """
429
+ params = {'bugID': bug_id}
430
+ form_data = {'comment': comment}
431
+ result = self._call_new_api('close', params=params, form_data=form_data, m_param='bug')
432
+
433
+ # 如果返回的是"Array"字符串,表示操作成功(禅道某些接口的特殊返回格式)
434
+ if isinstance(result, dict) and result.get("raw_response") == "Array":
386
435
  return {
387
- "status": -1,
388
- "message": f"请求失败: {str(e)}"
436
+ "status": 1,
437
+ "message": "success",
438
+ "info": "success"
389
439
  }
440
+
441
+ return result
442
+
443
+ def create_bug_by_api(self, bug_data, use_form_data=True):
444
+ """通过API创建bug"""
445
+ return self._call_new_api('createbugbyapi', form_data=bug_data)
390
446
 
391
447
  def create_case_by_api(self, case_data, use_form_data=True):
392
448
  """通过API创建测试用例"""
393
- current_timestamp = int(time.time())
394
- bdtoken = self.generate_case_bdtoken(current_timestamp)
395
-
396
- url = f"{self.base_url}/zentaopms/www/index.php"
397
- params = {
398
- 'm': 'allneedlist',
399
- 'f': 'createcasebyapi',
400
- 'account': self.CASE_ACCOUNT,
401
- 'tamp': current_timestamp
402
- }
403
-
404
- headers = {
405
- 'BDTOKEN': bdtoken,
406
- 'User-Agent': 'Python-ZenTao-Client/1.0'
407
- }
408
-
409
- try:
410
- if use_form_data:
411
- response = requests.post(url, params=params, headers=headers, data=case_data, timeout=30)
412
- else:
413
- headers['Content-Type'] = 'application/json'
414
- response = requests.post(url, params=params, headers=headers, json=case_data, timeout=30)
415
-
416
- try:
417
- return response.json()
418
- except json.JSONDecodeError:
419
- return {
420
- "status": -1,
421
- "message": "响应不是有效的JSON格式",
422
- "raw_response": response.text
423
- }
424
- except requests.exceptions.RequestException as e:
425
- return {
426
- "status": -1,
427
- "message": f"请求失败: {str(e)}"
428
- }
449
+ return self._call_new_api('createcasebyapi', form_data=case_data)
429
450
 
430
451
  def upload_image_to_bug(self, bug_id, image_path, custom_filename=None):
431
452
  """上传图片到BUG附件"""
@@ -435,25 +456,6 @@ class ZenTaoAPIClient:
435
456
  "message": f"图片文件不存在: {image_path}"
436
457
  }
437
458
 
438
- current_timestamp = int(datetime.now().timestamp())
439
- url = f"{self.base_url}/zentaopms/www/index.php"
440
- params = {
441
- 'm': 'allneedlist',
442
- 'f': 'uploadbyapi',
443
- 'account': self.CASE_ACCOUNT,
444
- 'tamp': current_timestamp
445
- }
446
-
447
- token_string = f"accountfmtamp{current_timestamp}{self.case_secret_key}"
448
- md5_hash = hashlib.md5()
449
- md5_hash.update(token_string.encode('utf-8'))
450
- bdtoken = md5_hash.hexdigest()
451
-
452
- headers = {
453
- 'BDTOKEN': bdtoken,
454
- 'User-Agent': 'Python-ZenTao-Client/1.0'
455
- }
456
-
457
459
  filename = custom_filename or os.path.basename(image_path)
458
460
 
459
461
  try:
@@ -461,22 +463,8 @@ class ZenTaoAPIClient:
461
463
  files = {
462
464
  'files[]': (filename, f, 'application/octet-stream')
463
465
  }
464
- data = {'id': bug_id}
465
- response = requests.post(url, params=params, headers=headers, files=files, data=data, timeout=30)
466
-
467
- try:
468
- return response.json()
469
- except json.JSONDecodeError:
470
- return {
471
- "status": -1,
472
- "message": "响应不是有效的JSON格式",
473
- "raw_response": response.text
474
- }
475
- except requests.exceptions.RequestException as e:
476
- return {
477
- "status": -1,
478
- "message": f"请求失败: {str(e)}"
479
- }
466
+ form_data = {'id': bug_id}
467
+ return self._call_new_api('uploadbyapi', form_data=form_data, files=files)
480
468
  except Exception as e:
481
469
  return {
482
470
  "status": -1,
@@ -681,6 +669,15 @@ class ZenTaoNexus:
681
669
  LOWER_BUG = "0"
682
670
  REGRESSION = "0"
683
671
 
672
+ # 职位类型到任务类型的映射
673
+ ROLE_TO_TASK_TYPE = {
674
+ "测试": "4", # 测试
675
+ "前端": "8", # 前端开发
676
+ "后端": "3", # 后端开发
677
+ "其它": "20", # 其他
678
+ "其他": "20" # 其他(兼容两种写法)
679
+ }
680
+
684
681
  def __init__(self, config_path: str = None):
685
682
  """
686
683
  初始化ZenTao Nexus工具
@@ -719,6 +716,10 @@ class ZenTaoNexus:
719
716
  env_title_prefix = os.getenv("ZENTAO_TITLE_PREFIX") or os.getenv("TITLE_PREFIX")
720
717
  self.title_prefix = env_title_prefix or self.config_loader.get("title_prefix", "")
721
718
 
719
+ # 职位类型(用于创建任务时自动设置任务类型)
720
+ env_role_type = os.getenv("ZENTAO_ROLE_TYPE") or os.getenv("ROLE_TYPE")
721
+ self.role_type = env_role_type or self.config_loader.get("role_type", "")
722
+
722
723
  # 创建API客户端
723
724
  self.client = ZenTaoAPIClient(silent=True)
724
725
 
@@ -835,7 +836,8 @@ class ZenTaoNexus:
835
836
  "product_id": self.product_id,
836
837
  "opened_by": self.opened_by,
837
838
  "keywords": self.keywords,
838
- "title_prefix": self.title_prefix
839
+ "title_prefix": self.title_prefix,
840
+ "role_type": self.role_type
839
841
  }
840
842
 
841
843
  def upload_image_to_bug(self, bug_id, image_path, custom_filename=None):
@@ -855,7 +857,43 @@ class ZenTaoNexus:
855
857
  return self.client.get_story_detail(story_id)
856
858
 
857
859
  def create_task_by_api(self, task_data):
858
- """创建任务"""
860
+ """
861
+ 创建任务
862
+
863
+ Args:
864
+ task_data: 任务数据(字典,form-data格式)
865
+ 如果 task_data 中没有 'mold' 字段,且配置了职位类型(role_type),
866
+ 则根据职位类型自动设置任务类型
867
+ 自动设置字段:
868
+ - estStarted: 当前日期(如果未指定)
869
+ - deadline: 当前日期+3天(如果未指定)
870
+ - pri: 2(如果未指定)
871
+ - openedBy: 配置的创建人(如果未指定)
872
+
873
+ Returns:
874
+ dict: 创建结果,包含taskID
875
+ """
876
+ # 如果 task_data 中没有指定 mold,且配置了职位类型,则自动设置
877
+ if 'mold' not in task_data or (task_data.get('mold') == '' or task_data.get('mold') is None):
878
+ if self.role_type and self.role_type in self.ROLE_TO_TASK_TYPE:
879
+ task_data['mold'] = self.ROLE_TO_TASK_TYPE[self.role_type]
880
+
881
+ # 自动设置预计开始时间(当前日期)
882
+ if 'estStarted' not in task_data or (task_data.get('estStarted') == '' or task_data.get('estStarted') is None):
883
+ task_data['estStarted'] = datetime.now().strftime("%Y-%m-%d")
884
+
885
+ # 自动设置预计结束时间(当前日期+3天)
886
+ if 'deadline' not in task_data or (task_data.get('deadline') == '' or task_data.get('deadline') is None):
887
+ task_data['deadline'] = (datetime.now() + timedelta(days=3)).strftime("%Y-%m-%d")
888
+
889
+ # 自动设置优先级为2
890
+ if 'pri' not in task_data or (task_data.get('pri') == '' or task_data.get('pri') is None):
891
+ task_data['pri'] = '2'
892
+
893
+ # 自动设置创建人
894
+ if 'openedBy' not in task_data or (task_data.get('openedBy') == '' or task_data.get('openedBy') is None):
895
+ task_data['openedBy'] = self.opened_by
896
+
859
897
  return self.client.create_task_by_api(task_data)
860
898
 
861
899
  def assign_task(self, task_id, assigned_to, comment=None):
@@ -868,9 +906,13 @@ class ZenTaoNexus:
868
906
  return self.client.get_bug_list(product_id, assigned_to, opened_by, resolved_by,
869
907
  confirmed, status, start_date, end_date)
870
908
 
871
- def resolve_bug(self, product_id, bug_ids, resolution, resolved_by, resolved_build=None):
909
+ def resolve_bug(self, product_id, bug_ids, resolution="fixed", resolved_by=None, resolved_build=None):
872
910
  """批量解决BUG"""
873
911
  return self.client.resolve_bug(product_id, bug_ids, resolution, resolved_by, resolved_build)
912
+
913
+ def close_bug(self, bug_id, comment="AI关闭"):
914
+ """关闭BUG"""
915
+ return self.client.close_bug(bug_id, comment)
874
916
 
875
917
 
876
918
  # 便捷函数