zt-devops-cli 0.1.7__tar.gz → 0.1.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zt-devops-cli
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: DevOps 平台迭代管理 CLI
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -58,6 +58,14 @@ zt-devops-cli zteam-project list --project f39507
58
58
  zt-devops-cli sprint list --project k64352
59
59
  ```
60
60
 
61
+ #### 迭代提测列表
62
+ ```bash
63
+ zt-devops-cli sprint test-list --project b9f157 \
64
+ --search "BSC_V26.701.01" \
65
+ --num 1 \
66
+ --size 10
67
+ ```
68
+
61
69
  #### 创建迭代
62
70
 
63
71
  ```bash
@@ -93,6 +101,29 @@ zt-devops-cli task list --project f39507 \
93
101
  --creator zt07905
94
102
  ```
95
103
 
104
+ ### 缺陷管理
105
+
106
+ #### 查询缺陷列表(BUG)
107
+ ```bash
108
+ zt-devops-cli bug list --project b9f157 \
109
+ --num 1 \
110
+ --size 20 \
111
+ --no-remember
112
+ ```
113
+
114
+ #### 新增缺陷(BUG)
115
+ ```bash
116
+ zt-devops-cli bug create --project b9f157 \
117
+ --title "test" \
118
+ --owner zt07905 \
119
+ --zteam-project-id 15678224 \
120
+ --field f05cf2f5dc614dbaa05a13894abb5683=9Clfj3I8fR \
121
+ --field f672b38532614f428828d9a68a514e24=bgEHPFOjFt \
122
+ --field 684f26d36cd747f68dde32104a701956=3129a9cd7315457eb2963bfe19919e72 \
123
+ --field bcee87c140d3475496a2a31c236631f4=vENjmMhXMC \
124
+ --field 8756c5b483434c2bb54bd6bc6586b09b=
125
+ ```
126
+
96
127
  #### 新增任务(TASK)
97
128
  ```bash
98
129
  zt-devops-cli task create --project <project-id> \ # 通过 project list 获取
@@ -47,6 +47,14 @@ zt-devops-cli zteam-project list --project f39507
47
47
  zt-devops-cli sprint list --project k64352
48
48
  ```
49
49
 
50
+ #### 迭代提测列表
51
+ ```bash
52
+ zt-devops-cli sprint test-list --project b9f157 \
53
+ --search "BSC_V26.701.01" \
54
+ --num 1 \
55
+ --size 10
56
+ ```
57
+
50
58
  #### 创建迭代
51
59
 
52
60
  ```bash
@@ -82,6 +90,29 @@ zt-devops-cli task list --project f39507 \
82
90
  --creator zt07905
83
91
  ```
84
92
 
93
+ ### 缺陷管理
94
+
95
+ #### 查询缺陷列表(BUG)
96
+ ```bash
97
+ zt-devops-cli bug list --project b9f157 \
98
+ --num 1 \
99
+ --size 20 \
100
+ --no-remember
101
+ ```
102
+
103
+ #### 新增缺陷(BUG)
104
+ ```bash
105
+ zt-devops-cli bug create --project b9f157 \
106
+ --title "test" \
107
+ --owner zt07905 \
108
+ --zteam-project-id 15678224 \
109
+ --field f05cf2f5dc614dbaa05a13894abb5683=9Clfj3I8fR \
110
+ --field f672b38532614f428828d9a68a514e24=bgEHPFOjFt \
111
+ --field 684f26d36cd747f68dde32104a701956=3129a9cd7315457eb2963bfe19919e72 \
112
+ --field bcee87c140d3475496a2a31c236631f4=vENjmMhXMC \
113
+ --field 8756c5b483434c2bb54bd6bc6586b09b=
114
+ ```
115
+
85
116
  #### 新增任务(TASK)
86
117
  ```bash
87
118
  zt-devops-cli task create --project <project-id> \ # 通过 project list 获取
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "zt-devops-cli"
7
- version = "0.1.7"
7
+ version = "0.1.9"
8
8
  description = "DevOps 平台迭代管理 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -98,6 +98,32 @@ class DevOpsAPI:
98
98
  return self._request("GET",
99
99
  f"/ms/vteam/api/user/issue_sprint/{self.project_id}/flat?num=1&count=1&size=20&content=&start_time=&end_time=&state=ACTIVE%2CNOT_STARTED&sort_field=CREATED_TIME&sort_rule=DESC")
100
100
 
101
+ def list_sprint_tests(
102
+ self,
103
+ num: int = 1,
104
+ size: int = 20,
105
+ search: str = "",
106
+ result: Optional[list] = None,
107
+ create_user: Optional[list] = None,
108
+ create_time: Optional[list] = None,
109
+ principal: Optional[list] = None,
110
+ obj_id: Optional[list] = None,
111
+ ) -> dict:
112
+ """查询迭代提测列表"""
113
+ data = {
114
+ "result": result or [],
115
+ "createUser": create_user or [],
116
+ "createTime": create_time or [],
117
+ "principal": principal or [],
118
+ "objId": obj_id or [],
119
+ "search": search,
120
+ }
121
+ return self._request(
122
+ "POST",
123
+ f"/ms/vteam/api/user/test/{self.project_id}/all?num={num}&size={size}",
124
+ data=data,
125
+ )
126
+
101
127
  def list_projects(self) -> dict:
102
128
  """查询项目列表"""
103
129
  return self._request("GET",
@@ -139,6 +165,33 @@ class DevOpsAPI:
139
165
  }
140
166
  return self._request("POST", f"/ms/vteam/api/user/issue/{self.project_id}", data=data)
141
167
 
168
+ def create_bug(
169
+ self,
170
+ title: str,
171
+ model_type_id: str,
172
+ priority: str = "CENTRAL",
173
+ editor_type: str = "RICHTEXT",
174
+ desc: str = "",
175
+ parent_id: str = "",
176
+ demand_classify: str = "-1",
177
+ instance_values: Optional[list] = None,
178
+ ) -> dict:
179
+ """创建 BUG 缺陷"""
180
+ data = {
181
+ "title": title,
182
+ "modelTypeId": model_type_id,
183
+ "priority": priority,
184
+ "editorType": editor_type,
185
+ "desc": desc,
186
+ "parentId": parent_id,
187
+ "relationIssue": {},
188
+ "demandClassify": demand_classify,
189
+ "fileVO": [],
190
+ "labelId": [],
191
+ "instanceValue": instance_values or [],
192
+ }
193
+ return self._request("POST", f"/ms/vteam/api/user/issue/{self.project_id}", data=data)
194
+
142
195
  def list_tasks(
143
196
  self,
144
197
  creator: Optional[str] = None,
@@ -163,3 +216,18 @@ class DevOpsAPI:
163
216
  f"/ms/vteam/api/user/issue/{self.project_id}/table/TASK?num={num}&size={size}&remember={remember_value}",
164
217
  data=data,
165
218
  )
219
+
220
+ def list_bugs(
221
+ self,
222
+ num: int = 1,
223
+ size: int = 20,
224
+ remember: bool = False,
225
+ ) -> dict:
226
+ """查询 BUG 列表(缺陷)"""
227
+ remember_value = str(remember).lower()
228
+ data = [{"name": "exclude", "value": []}]
229
+ return self._request(
230
+ "POST",
231
+ f"/ms/vteam/api/user/issue/{self.project_id}/table/BUG?num={num}&size={size}&remember={remember_value}",
232
+ data=data,
233
+ )
@@ -89,12 +89,12 @@ def open_login_page(page) -> bool:
89
89
 
90
90
  return False
91
91
 
92
- def get_cookies_from_browser() -> dict:
92
+ def get_cookies_from_browser(headless) -> dict:
93
93
  """通过 Playwright 从浏览器获取 cookie"""
94
94
  print("正在启动浏览器(无头模式),请登录...")
95
95
 
96
96
  with sync_playwright() as p:
97
- browser = p.chromium.launch(headless=True)
97
+ browser = p.chromium.launch(headless=headless)
98
98
  context = browser.new_context()
99
99
  page = context.new_page()
100
100
 
@@ -156,10 +156,10 @@ def get_cookies_from_browser() -> dict:
156
156
 
157
157
  return cookie_dict
158
158
 
159
- def login() -> dict:
159
+ def login(headless) -> dict:
160
160
  """手动登录 - 强制打开浏览器重新获取 cookie"""
161
161
  print("正在启动浏览器(无头模式),请重新登录...")
162
- cookies = get_cookies_from_browser()
162
+ cookies = get_cookies_from_browser(headless)
163
163
  save_cookies(cookies)
164
164
  print("登录成功,cookie 已缓存!")
165
165
  return cookies
@@ -7,6 +7,19 @@ from .auth import login
7
7
  from .config import config
8
8
 
9
9
 
10
+ def _parse_custom_fields(entries):
11
+ result = []
12
+ for item in entries:
13
+ if "=" not in item:
14
+ raise click.ClickException(f"--field 参数格式错误: {item},应为 fieldId=value")
15
+ field_id, value = item.split("=", 1)
16
+ field_id = field_id.strip()
17
+ if not field_id:
18
+ raise click.ClickException(f"--field 参数格式错误: {item},fieldId 不能为空")
19
+ result.append({"fieldId": field_id, "value": value})
20
+ return result
21
+
22
+
10
23
  def _normalize_cell(value):
11
24
  if value is None:
12
25
  return "-"
@@ -102,10 +115,14 @@ def cli(ctx, project, output_format):
102
115
 
103
116
 
104
117
  @cli.command("login")
105
- def cmd_login():
106
- """登录 - 打开浏览器重新获取 cookie"""
107
- login()
108
-
118
+ @click.option(
119
+ "--headed",
120
+ is_flag=True,
121
+ help="显示浏览器窗口,用于无环境变量时的手动登录",
122
+ )
123
+ def cmd_login(headed):
124
+ """登录 - 打开浏览器重新获取 cookie(默认无头;需手动登录时请传 --headed)"""
125
+ login(headless=not headed)
109
126
 
110
127
  @cli.group()
111
128
  def sprint():
@@ -124,6 +141,11 @@ def issue():
124
141
  """需求管理命令组"""
125
142
  pass
126
143
 
144
+ @cli.group()
145
+ def bug():
146
+ """缺陷管理命令组"""
147
+ pass
148
+
127
149
 
128
150
  @cli.group()
129
151
  def task():
@@ -292,6 +314,72 @@ def list_sprint(ctx, project):
292
314
  click.echo(f"错误: {e}", err=True)
293
315
 
294
316
 
317
+ @sprint.command("test-list")
318
+ @click.option("--project", "-p", default=None, help="项目 ID")
319
+ @click.option("--num", default=1, show_default=True, type=int, help="页码")
320
+ @click.option("--size", default=20, show_default=True, type=int, help="每页条数")
321
+ @click.option("--search", default="", show_default=True, help="搜索关键词(如迭代名称)")
322
+ @click.option("--result", "results", multiple=True, help="提测结果,可重复传入")
323
+ @click.option("--create-user", "create_users", multiple=True, help="创建人账号,可重复传入")
324
+ @click.option("--create-time", "create_times", multiple=True, help="创建时间筛选值,可重复传入")
325
+ @click.option("--principal", "principals", multiple=True, help="负责人账号,可重复传入")
326
+ @click.option("--obj-id", "obj_ids", multiple=True, help="对象 ID,可重复传入")
327
+ @click.pass_context
328
+ def list_sprint_tests(ctx, project, num, size, search, results, create_users, create_times, principals, obj_ids):
329
+ """查询迭代提测列表"""
330
+ project_id = project or ctx.obj["project"]
331
+ if not project_id:
332
+ click.echo("错误: 请通过 -p 指定项目 ID 或在配置中设置 default_project")
333
+ raise click.Abort()
334
+ api = DevOpsAPI(project_id)
335
+
336
+ try:
337
+ payload = api.list_sprint_tests(
338
+ num=num,
339
+ size=size,
340
+ search=search,
341
+ result=list(results),
342
+ create_user=list(create_users),
343
+ create_time=list(create_times),
344
+ principal=list(principals),
345
+ obj_id=list(obj_ids),
346
+ )
347
+ data = payload.get("data", {}) if isinstance(payload, dict) else {}
348
+ test_list = data.get("content", []) if isinstance(data, dict) else []
349
+ if not isinstance(test_list, list):
350
+ test_list = []
351
+
352
+ if ctx.obj.get("output_format") == "json":
353
+ click.echo(
354
+ json.dumps(
355
+ test_list,
356
+ ensure_ascii=False,
357
+ indent=2,
358
+ default=str,
359
+ )
360
+ )
361
+ return
362
+
363
+ render_table(
364
+ headers=["提测ID", "迭代名称", "提测标题", "提测结果", "负责人", "创建人", "创建时间"],
365
+ rows=[
366
+ [
367
+ s.get("id"),
368
+ s.get("objName") or s.get("sprintName"),
369
+ s.get("title") or s.get("name"),
370
+ s.get("result"),
371
+ s.get("principal"),
372
+ s.get("createUser"),
373
+ s.get("createTime"),
374
+ ]
375
+ for s in test_list
376
+ ],
377
+ no_truncate_headers=["提测ID", "迭代名称", "提测标题"],
378
+ )
379
+ except Exception as e:
380
+ click.echo(f"错误: {e}", err=True)
381
+
382
+
295
383
  @project.command("list")
296
384
  @click.pass_context
297
385
  def list_project(ctx):
@@ -478,6 +566,177 @@ def list_task(ctx, project, start_date, end_date, creator, num, size, remember):
478
566
  click.echo(f"错误: {e}", err=True)
479
567
 
480
568
 
569
+ @bug.command("list")
570
+ @click.option("--project", "-p", required=True, help="项目 ID")
571
+ @click.option("--num", default=1, show_default=True, type=int, help="页码")
572
+ @click.option("--size", default=20, show_default=True, type=int, help="每页条数")
573
+ @click.option("--remember/--no-remember", default=False, show_default=True, help="是否启用后端记忆筛选条件")
574
+ @click.pass_context
575
+ def list_bugs(ctx, project, num, size, remember):
576
+ """查询缺陷列表(BUG)"""
577
+ def _extract_bug_list(payload):
578
+ if not isinstance(payload, dict):
579
+ return []
580
+ data = payload.get("data")
581
+ if isinstance(data, list):
582
+ return data
583
+ if not isinstance(data, dict):
584
+ return []
585
+
586
+ content = None
587
+ if isinstance(data.get("content"), list):
588
+ content = data.get("content")
589
+ else:
590
+ records = data.get("records")
591
+ if isinstance(records, dict) and isinstance(records.get("content"), list):
592
+ content = records.get("content")
593
+
594
+ if not isinstance(content, list):
595
+ return []
596
+
597
+ def _unwrap_value(v):
598
+ if not isinstance(v, dict):
599
+ return v
600
+ for key in ("value", "label", "name", "displayName", "text", "title"):
601
+ if key in v and v.get(key) not in (None, ""):
602
+ return v.get(key)
603
+ return None
604
+
605
+ result = []
606
+ for item in content:
607
+ if not isinstance(item, dict):
608
+ continue
609
+ prop = item.get("property")
610
+ if isinstance(prop, dict):
611
+ flattened = {}
612
+ for k, v in prop.items():
613
+ flattened[k] = _unwrap_value(v)
614
+ result.append(flattened)
615
+ continue
616
+
617
+ normalized = {}
618
+ for k, v in item.items():
619
+ normalized[k] = _unwrap_value(v)
620
+ result.append(normalized)
621
+ return result
622
+
623
+ project_id = project or ctx.obj["project"]
624
+ if not project_id:
625
+ click.echo("错误: 请通过 -p 指定项目 ID 或在配置中设置 default_project")
626
+ raise click.Abort()
627
+ api = DevOpsAPI(project_id)
628
+
629
+ try:
630
+ result = api.list_bugs(
631
+ num=num,
632
+ size=size,
633
+ remember=remember,
634
+ )
635
+ bug_list = _extract_bug_list(result)
636
+
637
+ if ctx.obj.get("output_format") == "json":
638
+ click.echo(
639
+ json.dumps(
640
+ bug_list,
641
+ ensure_ascii=False,
642
+ indent=2,
643
+ default=str,
644
+ )
645
+ )
646
+ return
647
+
648
+ render_table(
649
+ headers=["缺陷ID", "标题", "状态", "创建人", "负责人", "优先级", "严重程度"],
650
+ rows=[
651
+ [
652
+ b.get("id") or b.get("issue_id"),
653
+ b.get("title") or b.get("name"),
654
+ b.get("state") or b.get("status"),
655
+ b.get("createUser") or b.get("creator"),
656
+ b.get("currentOwner") or b.get("handler"),
657
+ b.get("priority"),
658
+ b.get("severity"),
659
+ ]
660
+ for b in bug_list
661
+ ],
662
+ no_truncate_headers=["缺陷ID", "标题"],
663
+ )
664
+ except Exception as e:
665
+ click.echo(f"错误: {e}", err=True)
666
+
667
+
668
+ @bug.command("create")
669
+ @click.option("--project", "-p", required=True, help="项目 ID")
670
+ @click.option("--title", "-t", required=True, help="缺陷标题")
671
+ @click.option("--model-type-id", default="808fb2fd3da74f1da4c772712e10c665", show_default=True, help="缺陷模型 ID")
672
+ @click.option("--priority", default="CENTRAL", show_default=True, help="优先级")
673
+ @click.option("--editor-type", default="RICHTEXT", show_default=True, help="编辑器类型")
674
+ @click.option(
675
+ "--desc",
676
+ default="<h2><span style=\"font-size: 16px;\">【前置条件】</span></h2>\n"
677
+ "<h2><span style=\"font-size: 16px;\">【操作步骤】</span></h2>\n"
678
+ "<h2><span style=\"font-size: 16px;\">【实际结果】</span></h2>\n"
679
+ "<h2><span style=\"font-size: 16px;\">【预期结果】</span></h2>",
680
+ show_default=True,
681
+ help="缺陷描述(支持 HTML)",
682
+ )
683
+ @click.option("--parent-id", default="", show_default=True, help="父缺陷 ID")
684
+ @click.option("--demand-classify", default="-1", show_default=True, help="需求分类")
685
+ @click.option("--owner", required=True, help="提单人账号,例如 zt07905")
686
+ @click.option("--handler", default="", show_default=True, help="处理人账号,默认与 --owner 一致")
687
+ @click.option("--zteam-project-id", required=True, help="ZTeam 项目 ID")
688
+ @click.option("--field", "custom_fields", multiple=True, help="自定义字段,格式: fieldId=value,可重复")
689
+ @click.pass_context
690
+ def create_bug(
691
+ ctx,
692
+ project,
693
+ title,
694
+ model_type_id,
695
+ priority,
696
+ editor_type,
697
+ desc,
698
+ parent_id,
699
+ demand_classify,
700
+ owner,
701
+ handler,
702
+ zteam_project_id,
703
+ custom_fields,
704
+ ):
705
+ """创建缺陷(BUG)"""
706
+ project_id = project or ctx.obj["project"]
707
+ if not project_id:
708
+ click.echo("错误: 请通过 -p 指定项目 ID 或在配置中设置 default_project")
709
+ raise click.Abort()
710
+
711
+ # 基础字段沿用页面创建缺陷抓包;其余字段可通过 --field 传入
712
+ instance_values = [
713
+ {"fieldId": "456e808faf85419b912535da923b7f22", "value": owner},
714
+ {"fieldId": "5abdcb4c783e11edbef4fa819a160800", "value": zteam_project_id},
715
+ {"fieldId": "3cd53ac875944b599f5e70e035327d42", "value": handler or owner},
716
+ ]
717
+ instance_values.extend(_parse_custom_fields(custom_fields))
718
+
719
+ api = DevOpsAPI(project_id)
720
+ try:
721
+ result = api.create_bug(
722
+ title=title,
723
+ model_type_id=model_type_id,
724
+ priority=priority,
725
+ editor_type=editor_type,
726
+ desc=desc,
727
+ parent_id=parent_id,
728
+ demand_classify=demand_classify,
729
+ instance_values=instance_values,
730
+ )
731
+ if ctx.obj.get("output_format") == "json":
732
+ click.echo(json.dumps(result, ensure_ascii=False, indent=2, default=str))
733
+ return
734
+ bug_id = result.get("data", {}).get("id") if isinstance(result, dict) else None
735
+ click.echo(f"创建成功! 缺陷 ID: {bug_id or '-'}")
736
+ except Exception as e:
737
+ click.echo(f"错误: {e}", err=True)
738
+
739
+
481
740
  @task.command("create")
482
741
  @click.option("--project", "-p", required=True, help="项目 ID")
483
742
  @click.option("--title", "-t", required=True, help="任务标题")
@@ -519,18 +778,6 @@ def create_task(
519
778
  click.echo("错误: 请通过 -p 指定项目 ID 或在配置中设置 default_project")
520
779
  raise click.Abort()
521
780
 
522
- def _parse_custom_fields(entries):
523
- result = []
524
- for item in entries:
525
- if "=" not in item:
526
- raise click.ClickException(f"--field 参数格式错误: {item},应为 fieldId=value")
527
- field_id, value = item.split("=", 1)
528
- field_id = field_id.strip()
529
- if not field_id:
530
- raise click.ClickException(f"--field 参数格式错误: {item},fieldId 不能为空")
531
- result.append({"fieldId": field_id, "value": value})
532
- return result
533
-
534
781
  # 与浏览器抓包参数保持一致,便于直接复现页面创建行为
535
782
  instance_values = [
536
783
  {"fieldId": "456e808faf85419b912535da923b7f22", "value": owner},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zt-devops-cli
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: DevOps 平台迭代管理 CLI
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -58,6 +58,14 @@ zt-devops-cli zteam-project list --project f39507
58
58
  zt-devops-cli sprint list --project k64352
59
59
  ```
60
60
 
61
+ #### 迭代提测列表
62
+ ```bash
63
+ zt-devops-cli sprint test-list --project b9f157 \
64
+ --search "BSC_V26.701.01" \
65
+ --num 1 \
66
+ --size 10
67
+ ```
68
+
61
69
  #### 创建迭代
62
70
 
63
71
  ```bash
@@ -93,6 +101,29 @@ zt-devops-cli task list --project f39507 \
93
101
  --creator zt07905
94
102
  ```
95
103
 
104
+ ### 缺陷管理
105
+
106
+ #### 查询缺陷列表(BUG)
107
+ ```bash
108
+ zt-devops-cli bug list --project b9f157 \
109
+ --num 1 \
110
+ --size 20 \
111
+ --no-remember
112
+ ```
113
+
114
+ #### 新增缺陷(BUG)
115
+ ```bash
116
+ zt-devops-cli bug create --project b9f157 \
117
+ --title "test" \
118
+ --owner zt07905 \
119
+ --zteam-project-id 15678224 \
120
+ --field f05cf2f5dc614dbaa05a13894abb5683=9Clfj3I8fR \
121
+ --field f672b38532614f428828d9a68a514e24=bgEHPFOjFt \
122
+ --field 684f26d36cd747f68dde32104a701956=3129a9cd7315457eb2963bfe19919e72 \
123
+ --field bcee87c140d3475496a2a31c236631f4=vENjmMhXMC \
124
+ --field 8756c5b483434c2bb54bd6bc6586b09b=
125
+ ```
126
+
96
127
  #### 新增任务(TASK)
97
128
  ```bash
98
129
  zt-devops-cli task create --project <project-id> \ # 通过 project list 获取
File without changes