@hecom/codearts 0.1.0 → 0.2.0

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/bin/cli.js CHANGED
@@ -38,40 +38,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const commander_1 = require("commander");
41
+ const dotenv_1 = __importDefault(require("dotenv"));
41
42
  const fs = __importStar(require("fs"));
42
43
  const path = __importStar(require("path"));
43
- const dotenv_1 = __importDefault(require("dotenv"));
44
- const daily_command_1 = require("../commands/daily.command");
44
+ const bug_command_1 = require("../commands/bug.command");
45
45
  const config_command_1 = require("../commands/config.command");
46
+ const daily_command_1 = require("../commands/daily.command");
46
47
  const work_hour_command_1 = require("../commands/work-hour.command");
47
48
  const global_config_1 = require("../utils/global-config");
48
49
  // 读取 package.json 中的版本号
49
50
  const packageJsonPath = path.join(__dirname, '../../package.json');
50
51
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
51
- const version = packageJson.version || '0.0.1';
52
+ const version = packageJson.version;
52
53
  const program = new commander_1.Command();
53
- program.name('codearts').description('华为云 CodeArts API 工时统计分析工具').version(version);
54
+ program.name('codearts').description('华为云 CodeArts 统计分析工具').version(version);
54
55
  // 全局选项(环境变量覆盖)
55
- program
56
- .option('--project-id <id>', '项目 ID')
57
- .option('--role-id <ids>', '角色 ID(支持逗号分隔,如: 1,2,3)')
58
- .option('--username <username>', 'IAM 用户名')
59
- .option('--password <password>', 'IAM 密码(建议使用 .env 文件)')
60
- .option('--domain <domain>', '华为云账号名')
61
- .option('--region <region>', '华为云区域')
62
- .option('--iam-endpoint <url>', 'IAM 认证端点')
63
- .option('--codearts-url <url>', 'CodeArts API 地址');
56
+ program.option('--role-id <ids>', '角色 ID(支持逗号分隔,如: 1,2,3),优先级高于环境变量 ROLE_ID');
64
57
  // config 命令 - 交互式配置向导
65
58
  program
66
59
  .command('config')
67
- .description('交互式配置向导\n\n引导用户创建或更新全局配置文件')
60
+ .description('交互式配置向导,引导用户创建或更新全局配置文件\n\n')
68
61
  .action(async () => {
69
62
  await (0, config_command_1.configCommand)();
70
63
  });
71
64
  // daily 命令
72
65
  program
73
66
  .command('daily [date]')
74
- .description('生成日报统计\n\n示例:\n $ codearts daily\n $ codearts daily 2026-01-15\n $ codearts daily --project-id abc123 --role-id 1,2')
67
+ .description('生成日报统计\n\n示例:\n $ codearts daily\n $ codearts daily 2026-01-15\n')
75
68
  .action(async (date) => {
76
69
  const opts = program.opts();
77
70
  await (0, daily_command_1.dailyCommand)(date, opts);
@@ -79,36 +72,19 @@ program
79
72
  // work-hour 命令
80
73
  program
81
74
  .command('work-hour [year]')
82
- .description('生成年度工时统计\n\n示例:\n $ codearts work-hour\n $ codearts work-hour 2025\n $ codearts work-hour 2025 --role-id 1,2,3')
75
+ .description('生成年度工时统计\n\n示例:\n $ codearts work-hour\n $ codearts work-hour 2025\n')
83
76
  .action(async (year) => {
84
77
  const opts = program.opts();
85
78
  await (0, work_hour_command_1.workHourCommand)(year, opts);
86
79
  });
87
- // 添加帮助信息
88
- program.addHelpText('after', `
89
- 环境变量:
90
- 命令行参数优先于环境变量。可通过 .env 文件配置以下变量:
91
-
92
- HUAWEI_CLOUD_IAM_ENDPOINT IAM 认证端点
93
- HUAWEI_CLOUD_REGION 华为云区域
94
- HUAWEI_CLOUD_USERNAME IAM 用户名
95
- HUAWEI_CLOUD_PASSWORD IAM 密码
96
- HUAWEI_CLOUD_DOMAIN 华为云账号名
97
- CODEARTS_BASE_URL CodeArts API 地址
98
- PROJECT_ID 项目 ID
99
- ROLE_ID 角色 ID(支持逗号分隔)
100
-
101
- 配置优先级:
102
- 命令行参数 > 环境变量 > 默认值
103
-
104
- 快速开始:
105
- 1. 运行配置向导: codearts config
106
- 2. 生成日报: codearts daily
107
- 3. 生成年度工时统计: codearts work-hour
108
-
109
- 更多信息:
110
- https://github.com/hecom-rn/hecom-codearts
111
- `);
80
+ // bug-rate 命令
81
+ program
82
+ .command('bug-rate <iterations>')
83
+ .description('按迭代统计产品缺陷率,支持多个迭代(逗号分隔)\n\n示例:\n $ codearts bug-rate "迭代1,迭代2"\n')
84
+ .action(async (iterations) => {
85
+ const opts = program.opts();
86
+ await (0, bug_command_1.bugCommand)(iterations, opts);
87
+ });
112
88
  // 检查配置并自动执行 config 命令
113
89
  async function checkConfigAndRun() {
114
90
  const args = process.argv.slice(2);
@@ -0,0 +1,7 @@
1
+ import { CliOptions } from '../utils/config-loader';
2
+ /**
3
+ * bug 命令入口
4
+ * @param iterationTitlesStr 迭代标题(支持逗号、分号、空格、竖线、顿号等分隔符)
5
+ * @param cliOptions CLI 选项
6
+ */
7
+ export declare function bugCommand(iterationTitlesStr: string, cliOptions?: CliOptions): Promise<void>;
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugCommand = bugCommand;
4
+ const business_service_1 = require("../services/business.service");
5
+ const types_1 = require("../types");
6
+ const config_loader_1 = require("../utils/config-loader");
7
+ /**
8
+ * 判断 Bug 是否属于需求变更或产品设计问题
9
+ * @param bug Bug 工作项
10
+ * @returns 是否属于需求变更或产品设计问题
11
+ */
12
+ function isRequirementOrDesignBug(bug) {
13
+ // 检查 customValueNew(v2 版本的自定义字段,是对象格式)
14
+ const defectAnalysisValue = bug.customValueNew?.custom_field32;
15
+ if (defectAnalysisValue) {
16
+ return (defectAnalysisValue === types_1.DefectAnalysisType.REQUIREMENT_CHANGE ||
17
+ defectAnalysisValue === types_1.DefectAnalysisType.PRODUCT_DESIGN);
18
+ }
19
+ return false;
20
+ }
21
+ /**
22
+ * 生成 Bug 统计报告
23
+ */
24
+ async function generateBugReport(businessService, projectId, roleIds, iterationTitles) {
25
+ console.log(`\n正在统计迭代中的 Bug 数据...`);
26
+ console.log(`目标迭代: ${iterationTitles.join(', ')}`);
27
+ console.log(`角色过滤: ${roleIds.length > 0 ? roleIds.join(', ') : '无(保留所有人)'}`);
28
+ console.log(`统计规则: 统计所有 Bug,同时标记"需求变更问题"和"产品设计问题"`);
29
+ // Step 1: 获取指定角色的成员列表(如果启用了角色过滤)
30
+ const targetMemberIds = new Set();
31
+ if (roleIds.length > 0) {
32
+ for (const roleId of roleIds) {
33
+ const members = await businessService.getMembersByRoleId(projectId, roleId);
34
+ members.forEach((member) => targetMemberIds.add(member.user_num_id));
35
+ }
36
+ console.log(`\n目标角色成员数: ${targetMemberIds.size} 人`);
37
+ }
38
+ else {
39
+ console.log(`\n角色过滤: 已禁用,将统计所有人`);
40
+ }
41
+ // Step 2: 获取所有 Story
42
+ const stories = await businessService.getStoriesByIterationTitles(projectId, iterationTitles);
43
+ if (stories.length === 0) {
44
+ console.log('未找到任何 Story');
45
+ return;
46
+ }
47
+ // Step 3: 过滤出目标角色成员处理的 Story(如果启用了角色过滤)
48
+ const filteredStories = roleIds.length > 0
49
+ ? stories.filter((story) => {
50
+ const assignedUserId = story.assigned_user?.id;
51
+ return assignedUserId && targetMemberIds.has(assignedUserId);
52
+ })
53
+ : stories;
54
+ if (filteredStories.length === 0) {
55
+ console.log(roleIds.length > 0 ? '未找到目标角色成员处理的 Story' : '未找到任何 Story');
56
+ return;
57
+ }
58
+ if (roleIds.length > 0) {
59
+ console.log(`\n找到 ${stories.length} 个 Story,其中 ${filteredStories.length} 个由目标角色成员处理,正在统计 Bug...`);
60
+ }
61
+ else {
62
+ console.log(`\n找到 ${filteredStories.length} 个 Story,正在统计 Bug...`);
63
+ }
64
+ // Step 4: 获取每个 Story 的子工作项(Bug)
65
+ const bugStatsMap = new Map();
66
+ for (const story of filteredStories) {
67
+ const childIssues = await businessService.getChildIssues(projectId, String(story.id));
68
+ // 过滤出所有 Bug 类型的子工作项(tracker_id = 3)
69
+ const allBugs = childIssues.filter((issue) => issue.tracker.id === 3);
70
+ if (allBugs.length === 0) {
71
+ continue;
72
+ }
73
+ // 统计产品问题 Bug(需求变更 + 产品设计)
74
+ const productBugs = allBugs.filter((bug) => isRequirementOrDesignBug(bug));
75
+ // Step 3: 按 Story 的处理人统计 Bug
76
+ const assignedUserId = story.assigned_user?.id;
77
+ const assignedUserName = story.assigned_user?.nick_name;
78
+ if (!assignedUserId) {
79
+ continue;
80
+ }
81
+ if (!bugStatsMap.has(assignedUserId)) {
82
+ bugStatsMap.set(assignedUserId, {
83
+ assignedUser: assignedUserName,
84
+ userId: assignedUserId,
85
+ totalBugCount: 0,
86
+ productBugCount: 0,
87
+ productDefectRate: 0,
88
+ stories: [],
89
+ });
90
+ }
91
+ const userStats = bugStatsMap.get(assignedUserId);
92
+ userStats.totalBugCount += allBugs.length;
93
+ userStats.productBugCount += productBugs.length;
94
+ userStats.stories.push({
95
+ storyName: story.name,
96
+ storyId: story.id,
97
+ totalBugCount: allBugs.length,
98
+ productBugCount: productBugs.length,
99
+ });
100
+ }
101
+ // 计算每个人的产品缺陷率
102
+ bugStatsMap.forEach((stats) => {
103
+ stats.productDefectRate =
104
+ stats.totalBugCount > 0 ? (stats.productBugCount / stats.totalBugCount) * 100 : 0;
105
+ });
106
+ // Step 4: 输出统计结果
107
+ console.log('\n');
108
+ console.log('='.repeat(80));
109
+ console.log(`Bug 统计报告 [${iterationTitles.join(', ')}]`);
110
+ console.log('='.repeat(80));
111
+ console.log('');
112
+ if (bugStatsMap.size === 0) {
113
+ console.log('未找到任何 Bug');
114
+ return;
115
+ }
116
+ // 按总 Bug 数量降序排列
117
+ const sortedStats = Array.from(bugStatsMap.values()).sort((a, b) => b.totalBugCount - a.totalBugCount);
118
+ let totalBugs = 0;
119
+ let totalProductBugs = 0;
120
+ sortedStats.forEach((stats) => {
121
+ console.log(`\x1b[31m${stats.assignedUser}: 总 Bug ${stats.totalBugCount} 个 | 产品问题 ${stats.productBugCount} 个 | 产品缺陷率 ${stats.productDefectRate.toFixed(1)}%\x1b[0m`);
122
+ // 输出该用户的 Story 及其 Bug 数量
123
+ stats.stories.forEach((story) => {
124
+ console.log(` - ${story.storyName} (总 ${story.totalBugCount} 个 Bug,其中产品问题 ${story.productBugCount} 个)`);
125
+ });
126
+ totalBugs += stats.totalBugCount;
127
+ totalProductBugs += stats.productBugCount;
128
+ console.log('');
129
+ });
130
+ const overallProductDefectRate = totalBugs > 0 ? (totalProductBugs / totalBugs) * 100 : 0;
131
+ console.log('='.repeat(80));
132
+ console.log(`总计: ${totalBugs} 个 Bug(产品问题 ${totalProductBugs} 个,产品缺陷率 ${overallProductDefectRate.toFixed(1)}%),涉及 ${sortedStats.length} 位处理人`);
133
+ console.log('='.repeat(80));
134
+ }
135
+ /**
136
+ * bug 命令入口
137
+ * @param iterationTitlesStr 迭代标题(支持逗号、分号、空格、竖线、顿号等分隔符)
138
+ * @param cliOptions CLI 选项
139
+ */
140
+ async function bugCommand(iterationTitlesStr, cliOptions = {}) {
141
+ try {
142
+ if (!iterationTitlesStr || iterationTitlesStr.trim() === '') {
143
+ throw new Error('请指定至少一个迭代标题\n示例: codearts bug "迭代1,迭代2" 或 "迭代1 迭代2" 或 "迭代1;迭代2"');
144
+ }
145
+ // 解析迭代标题(支持多种分隔符:逗号、分号、空格、竖线、顿号等)
146
+ const iterationTitles = iterationTitlesStr
147
+ .split(/[,,;;|\s、]+/)
148
+ .map((title) => title.trim())
149
+ .filter((title) => title.length > 0);
150
+ const { projectId, roleIds, config } = (0, config_loader_1.loadConfig)(cliOptions);
151
+ const businessService = new business_service_1.BusinessService(config);
152
+ await generateBugReport(businessService, projectId, roleIds, iterationTitles);
153
+ }
154
+ catch (error) {
155
+ console.error('执行过程中发生错误:', error);
156
+ process.exit(1);
157
+ }
158
+ }
@@ -1,3 +1,4 @@
1
1
  export { dailyCommand } from './daily.command';
2
2
  export { workHourCommand } from './work-hour.command';
3
3
  export { configCommand } from './config.command';
4
+ export { bugCommand } from './bug.command';
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configCommand = exports.workHourCommand = exports.dailyCommand = void 0;
3
+ exports.bugCommand = exports.configCommand = exports.workHourCommand = exports.dailyCommand = void 0;
4
4
  var daily_command_1 = require("./daily.command");
5
5
  Object.defineProperty(exports, "dailyCommand", { enumerable: true, get: function () { return daily_command_1.dailyCommand; } });
6
6
  var work_hour_command_1 = require("./work-hour.command");
7
7
  Object.defineProperty(exports, "workHourCommand", { enumerable: true, get: function () { return work_hour_command_1.workHourCommand; } });
8
8
  var config_command_1 = require("./config.command");
9
9
  Object.defineProperty(exports, "configCommand", { enumerable: true, get: function () { return config_command_1.configCommand; } });
10
+ var bug_command_1 = require("./bug.command");
11
+ Object.defineProperty(exports, "bugCommand", { enumerable: true, get: function () { return bug_command_1.bugCommand; } });
@@ -1,4 +1,4 @@
1
- import { AddIssueNotesRequest, AddIssueNotesResponse, ApiResponse, CachedToken, HuaweiCloudConfig, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse } from '../types';
1
+ import { ApiResponse, CachedToken, HuaweiCloudConfig, ListChildIssuesV2Response, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse } from '../types';
2
2
  /**
3
3
  * 华为云CodeArts API服务类
4
4
  * 支持IAM Token认证和CodeArts API调用
@@ -55,6 +55,11 @@ export declare class ApiService {
55
55
  * 获取指定工作项的详细信息
56
56
  */
57
57
  getIssueById(projectId: string, issueId: string): Promise<ApiResponse<unknown>>;
58
+ /**
59
+ * 查询子工作项 (ListChildIssuesV4)
60
+ * 获取指定工作项的所有子工作项
61
+ */
62
+ getChildIssues(projectId: string, issueId: string, pageSize?: number, pageNo?: number): Promise<ApiResponse<ListChildIssuesV2Response>>;
58
63
  /**
59
64
  * 创建工作项
60
65
  */
@@ -83,10 +88,6 @@ export declare class ApiService {
83
88
  * 按用户查询工时(单项目)
84
89
  */
85
90
  showProjectWorkHours(projectId: string, params?: ShowProjectWorkHoursRequest): Promise<ApiResponse<ShowProjectWorkHoursResponse>>;
86
- /**
87
- * 工作项添加评论
88
- */
89
- addIssueNotes(params: AddIssueNotesRequest): Promise<ApiResponse<AddIssueNotesResponse>>;
90
91
  /**
91
92
  * 获取当前Token信息(用于调试)
92
93
  */
@@ -310,6 +310,26 @@ class ApiService {
310
310
  method: 'GET',
311
311
  });
312
312
  }
313
+ /**
314
+ * 查询子工作项 (ListChildIssuesV4)
315
+ * 获取指定工作项的所有子工作项
316
+ */
317
+ async getChildIssues(projectId, issueId, pageSize = 100, pageNo = 1) {
318
+ // return this.request(`/v4/projects/${projectId}/issues/${issueId}/child`, {
319
+ // method: 'GET',
320
+ // });
321
+ return this.request('/v2/issues/child-issue-list', {
322
+ method: 'POST',
323
+ data: {
324
+ parentId: issueId,
325
+ projectUUId: projectId,
326
+ queryType: 'basic',
327
+ subject: '',
328
+ pageSize,
329
+ pageNo,
330
+ },
331
+ });
332
+ }
313
333
  /**
314
334
  * 创建工作项
315
335
  */
@@ -379,15 +399,6 @@ class ApiService {
379
399
  },
380
400
  });
381
401
  }
382
- /**
383
- * 工作项添加评论
384
- */
385
- async addIssueNotes(params) {
386
- return this.request('/v2/issues/update-issue-notes', {
387
- method: 'POST',
388
- data: params,
389
- });
390
- }
391
402
  /**
392
403
  * 获取当前Token信息(用于调试)
393
404
  */
@@ -1,4 +1,4 @@
1
- import { AllWorkHourStats, HuaweiCloudConfig, IssueDetailResponseV2, IssueItem, IterationInfo, ProjectMember, WorkHourStats, WorkProgressStats } from '../types';
1
+ import { AllWorkHourStats, HuaweiCloudConfig, IssueItem, IssueItemV2, IterationInfo, ProjectMember, WorkHourStats, WorkProgressStats } from '../types';
2
2
  import { ApiService } from './api.service';
3
3
  /**
4
4
  * 业务服务类
@@ -42,7 +42,6 @@ export declare class BusinessService {
42
42
  * @returns Task和Story类型的工作项列表
43
43
  */
44
44
  getWorkloadByIterationAndUsers(projectId: string, iterationId: number, userIds: string[]): Promise<IssueItem[]>;
45
- addIssueNote(projectId: string, issueId: number, content: string): Promise<IssueDetailResponseV2>;
46
45
  /**
47
46
  * 统计工作项进度信息
48
47
  * @param issues 工作项列表
@@ -57,6 +56,20 @@ export declare class BusinessService {
57
56
  * @returns 工时统计结果,包括总工时和按用户分组的工时详情
58
57
  */
59
58
  getDailyWorkHourStats(projectId: string, userIds: string[], date: string): Promise<WorkHourStats>;
59
+ /**
60
+ * 根据迭代标题获取迭代信息
61
+ * @param projectId 项目ID
62
+ * @param iterationTitles 迭代标题列表
63
+ * @returns 匹配的迭代信息列表
64
+ */
65
+ getIterationsByTitles(projectId: string, iterationTitles: string[]): Promise<IterationInfo[]>;
66
+ /**
67
+ * 根据迭代标题获取所有 Story
68
+ * @param projectId 项目ID
69
+ * @param iterationTitles 迭代标题列表
70
+ * @returns Story 类型的工作项列表
71
+ */
72
+ getStoriesByIterationTitles(projectId: string, iterationTitles: string[]): Promise<IssueItem[]>;
60
73
  /**
61
74
  * 查询指定用户在指定时间段内的所有工时统计(按人和领域分组)
62
75
  * @param projectId 项目ID
@@ -66,4 +79,11 @@ export declare class BusinessService {
66
79
  * @returns 工时统计结果,按用户和领域两个维度分组
67
80
  */
68
81
  getAllWorkHourStats(projectId: string, userIds: string[], beginDate: string, endDate: string): Promise<AllWorkHourStats>;
82
+ /**
83
+ * 获取指定工作项的所有子工作项(处理分页)
84
+ * @param projectId 项目ID
85
+ * @param issueId 父工作项ID
86
+ * @returns 所有子工作项列表
87
+ */
88
+ getChildIssues(projectId: string, issueId: string): Promise<IssueItemV2[]>;
69
89
  }
@@ -103,17 +103,6 @@ class BusinessService {
103
103
  }
104
104
  return issuesResponse.data?.issues || [];
105
105
  }
106
- async addIssueNote(projectId, issueId, content) {
107
- const result = await this.apiService.addIssueNotes({
108
- projectUUId: projectId,
109
- id: String(issueId),
110
- notes: content,
111
- });
112
- if (result.data?.status === 'success') {
113
- return result.data.result.issue;
114
- }
115
- throw new Error(`添加工作项备注失败: ${result.data?.status || '未知错误'}`);
116
- }
117
106
  /**
118
107
  * 统计工作项进度信息
119
108
  * @param issues 工作项列表
@@ -210,6 +199,62 @@ class BusinessService {
210
199
  userStats,
211
200
  };
212
201
  }
202
+ /**
203
+ * 根据迭代标题获取迭代信息
204
+ * @param projectId 项目ID
205
+ * @param iterationTitles 迭代标题列表
206
+ * @returns 匹配的迭代信息列表
207
+ */
208
+ async getIterationsByTitles(projectId, iterationTitles) {
209
+ const iterationsResponse = await this.apiService.getIterations(projectId, {
210
+ include_deleted: false,
211
+ });
212
+ if (!iterationsResponse.success) {
213
+ throw new Error(`获取迭代列表失败: ${iterationsResponse.error || '未知错误'}`);
214
+ }
215
+ const iterations = iterationsResponse.data?.iterations || [];
216
+ // 过滤出标题匹配的迭代
217
+ return iterations.filter((iteration) => iterationTitles.includes(iteration.name));
218
+ }
219
+ /**
220
+ * 根据迭代标题获取所有 Story
221
+ * @param projectId 项目ID
222
+ * @param iterationTitles 迭代标题列表
223
+ * @returns Story 类型的工作项列表
224
+ */
225
+ async getStoriesByIterationTitles(projectId, iterationTitles) {
226
+ // Step 1: 根据标题获取迭代信息
227
+ const iterations = await this.getIterationsByTitles(projectId, iterationTitles);
228
+ if (iterations.length === 0) {
229
+ return [];
230
+ }
231
+ // Step 2: 提取迭代ID
232
+ const iterationIds = iterations.map((iteration) => iteration.id);
233
+ // Step 3: 分页查询所有 Story(tracker_id = 7)
234
+ const allStories = [];
235
+ const pageSize = 100;
236
+ let offset = 0;
237
+ let hasMore = true;
238
+ while (hasMore) {
239
+ const issuesResponse = await this.apiService.getIssues(projectId, {
240
+ iteration_ids: iterationIds,
241
+ tracker_ids: [7], // 7=Story
242
+ include_deleted: false,
243
+ limit: pageSize,
244
+ offset: offset,
245
+ });
246
+ if (!issuesResponse.success) {
247
+ throw new Error(`获取Story列表失败: ${issuesResponse.error || '未知错误'}`);
248
+ }
249
+ const stories = issuesResponse.data?.issues || [];
250
+ allStories.push(...stories);
251
+ // 判断是否还有更多数据
252
+ const total = issuesResponse.data?.total || 0;
253
+ offset += pageSize;
254
+ hasMore = offset < total;
255
+ }
256
+ return allStories;
257
+ }
213
258
  /**
214
259
  * 查询指定用户在指定时间段内的所有工时统计(按人和领域分组)
215
260
  * @param projectId 项目ID
@@ -326,5 +371,29 @@ class BusinessService {
326
371
  userStats,
327
372
  };
328
373
  }
374
+ /**
375
+ * 获取指定工作项的所有子工作项(处理分页)
376
+ * @param projectId 项目ID
377
+ * @param issueId 父工作项ID
378
+ * @returns 所有子工作项列表
379
+ */
380
+ async getChildIssues(projectId, issueId) {
381
+ const allChildIssues = [];
382
+ const pageSize = 100;
383
+ let pageNo = 1;
384
+ let hasMore = true;
385
+ while (hasMore) {
386
+ const response = await this.apiService.getChildIssues(projectId, issueId, pageSize, pageNo);
387
+ if (!response.success || !response.data) {
388
+ throw new Error(`获取子工作项失败: ${response.error || '未知错误'}`);
389
+ }
390
+ const childIssues = response.data.result.issues || [];
391
+ allChildIssues.push(...childIssues);
392
+ const total = response.data.result.total_count || 0;
393
+ hasMore = pageNo * pageSize < total;
394
+ pageNo++;
395
+ }
396
+ return allChildIssues;
397
+ }
329
398
  }
330
399
  exports.BusinessService = BusinessService;
@@ -95,29 +95,12 @@ export interface ApiResponse<T = unknown> {
95
95
  message?: string;
96
96
  error?: string;
97
97
  }
98
- export interface RawData {
99
- id: string;
100
- timestamp: string;
101
- content: unknown;
102
- metadata?: Record<string, unknown>;
103
- }
104
- export interface ProcessedData {
105
- id: string;
106
- processedAt: string;
107
- result: unknown;
108
- summary?: string;
109
- }
110
98
  export interface RequestOptions {
111
99
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
112
100
  headers?: Record<string, string>;
113
101
  params?: Record<string, unknown>;
114
102
  data?: unknown;
115
103
  }
116
- export interface ProjectType {
117
- SCRUM: 'scrum';
118
- NORMAL: 'normal';
119
- XBOARD: 'xboard';
120
- }
121
104
  export interface ProjectQueryParams {
122
105
  offset?: number;
123
106
  limit?: number;
@@ -338,124 +321,142 @@ export interface AddIssueNotesRequest {
338
321
  projectUUId: string;
339
322
  type?: string;
340
323
  }
341
- export interface UserVO {
342
- assigned_nick_name?: string;
343
- first_name?: string;
344
- id?: number;
345
- identifier?: string;
346
- last_name?: string;
347
- name?: string;
324
+ export interface ListChildIssuesV4Response {
325
+ issues: IssueItem[];
326
+ total: number;
348
327
  }
349
- export interface CustomFieldV2 {
350
- name?: string;
351
- value?: string;
352
- new_name?: string;
328
+ export interface IssueUserV2 {
329
+ firstName: string;
330
+ lastName: string;
331
+ identifier: string;
332
+ imageId?: string;
333
+ name: string;
334
+ id: number;
335
+ assignedNickName?: string;
336
+ authorNickName?: string;
337
+ closederNickName?: string;
353
338
  }
354
- export interface IssueDetailCustomFieldV2 {
355
- custom_field?: string;
356
- field_name?: string;
357
- value?: string;
358
- field_type?: string;
359
- description?: string;
339
+ export interface IssueParentV2 {
340
+ subject: string;
341
+ id: number;
360
342
  }
361
- export interface DomainVO {
362
- id?: number;
363
- name?: string;
343
+ export interface IssueProjectV2 {
344
+ identifier: string;
345
+ name: string;
346
+ id: number;
347
+ type: string;
364
348
  }
365
- export interface ProjectVO {
366
- identifier?: string;
367
- name?: string;
368
- id?: number;
369
- project_type?: string;
349
+ export interface IssueTrackerV2 {
350
+ name: string;
351
+ id: number;
370
352
  }
371
- export interface IterationVO {
372
- id?: number;
373
- name?: string;
353
+ export interface IssueSeverityV2 {
354
+ name: string;
355
+ id: number;
374
356
  }
375
- export interface StoryPointVO {
376
- id?: number;
377
- name?: string;
357
+ export interface IssuePriorityV2 {
358
+ name: string;
359
+ id: number;
378
360
  }
379
- export interface ModuleVO {
380
- id?: number;
381
- name?: string;
361
+ export interface IssueStatusV2 {
362
+ name: string;
363
+ id: number;
382
364
  }
383
- export interface ParentIssueVO {
384
- id?: number;
385
- name?: string;
365
+ export interface IssueStatusAttributeV2 {
366
+ name: string;
367
+ id: number;
386
368
  }
387
- export interface PriorityVO {
388
- id?: number;
389
- name?: string;
369
+ export interface IssueDomainV2 {
370
+ name: string;
371
+ id: number;
390
372
  }
391
- export interface SeverityVO {
373
+ export interface IssueModuleV2 {
392
374
  id?: number;
393
375
  name?: string;
394
376
  }
395
- export interface StatusVO {
377
+ export interface IssueDeveloperV2 {
396
378
  id?: number;
397
379
  name?: string;
398
380
  }
399
- export interface EnvVO {
400
- id?: number;
401
- name?: string;
381
+ export interface IssueFixedVersionV2 {
382
+ name: string;
383
+ id: number;
402
384
  }
403
- export interface TrackerVO {
404
- id?: number;
405
- name?: string;
385
+ export interface IssueOrderV2 {
386
+ name: number;
387
+ id: number;
406
388
  }
407
- export interface IssueAccessoryV2 {
408
- attachment_id?: number;
409
- issue_id?: number;
410
- creator_num_id?: number;
411
- created_date?: string;
412
- file_name?: string;
413
- container_type?: string;
414
- disk_file_name?: string;
415
- digest?: string;
416
- disk_directory?: string;
417
- creator_id?: string;
418
- }
419
- export interface IssueDetailResponseV2 {
420
- actual_work_hours?: number;
421
- assigned_cc_user?: UserVO[];
422
- assigned_to?: UserVO;
423
- start_date?: string;
424
- created_on?: string;
425
- author?: UserVO;
426
- custom_fields?: CustomFieldV2[];
427
- custom_value_new?: IssueDetailCustomFieldV2;
428
- developer?: UserVO;
429
- domain?: DomainVO;
430
- done_ratio?: number;
431
- end_time?: string;
432
- expected_work_hours?: number;
433
- id?: number;
434
- project?: ProjectVO;
435
- iteration?: IterationVO;
436
- story_point?: StoryPointVO;
437
- module?: ModuleVO;
438
- subject?: string;
439
- parent_issue?: ParentIssueVO;
440
- priority?: PriorityVO;
441
- severity?: SeverityVO;
442
- status?: StatusVO;
443
- release_dev?: string;
444
- find_release_dev?: string;
445
- env?: EnvVO;
446
- tracker?: TrackerVO;
447
- updated_on?: string;
448
- closed_time?: string;
449
- description?: string;
450
- accessories_list?: IssueAccessoryV2[];
451
- inner_text?: string;
452
- }
453
- export interface AddIssueNotesResult {
454
- issue: IssueDetailResponseV2;
455
- }
456
- export interface AddIssueNotesResponse {
457
- result: AddIssueNotesResult;
458
- status: string;
389
+ export interface IssueCustomValueNewV2 {
390
+ [key: string]: string;
391
+ }
392
+ export interface IssueItemV2 {
393
+ id: number;
394
+ subject: string;
395
+ description: string;
396
+ updated_on: string;
397
+ created_on: string;
398
+ closed_on: string;
399
+ parent_issue?: IssueParentV2;
400
+ parent_issue_id?: number;
401
+ project: IssueProjectV2;
402
+ done_ratio: number;
403
+ findReleaseDev: string;
404
+ tracker: IssueTrackerV2;
405
+ releaseDev: string;
406
+ customValueNew?: IssueCustomValueNewV2;
407
+ order?: IssueOrderV2;
408
+ assigned_to: IssueUserV2;
409
+ status_attribute: IssueStatusAttributeV2;
410
+ severity: IssueSeverityV2;
411
+ isParent: boolean;
412
+ author: IssueUserV2;
413
+ module: IssueModuleV2;
414
+ expected_work_hours: number;
415
+ priority: IssuePriorityV2;
416
+ actual_work_hours: number;
417
+ is_watcher: boolean;
418
+ deleted: boolean;
419
+ fixed_version?: IssueFixedVersionV2;
420
+ is_archived: boolean;
421
+ domain: IssueDomainV2;
422
+ developer: IssueDeveloperV2;
423
+ closeder?: IssueUserV2;
424
+ position: string;
425
+ closed_flag: number;
426
+ assigned_cc_user: IssueUserV2[];
427
+ status: IssueStatusV2;
428
+ }
429
+ export interface ListChildIssuesV2Response {
430
+ result: {
431
+ total_count: number;
432
+ issues: IssueItemV2[];
433
+ };
434
+ status: 'success';
435
+ }
436
+ /**
437
+ * 自定义字段类型枚举
438
+ */
439
+ export type CustomFieldType = 'textbox' | 'textarea' | 'checkbox' | 'radio' | 'select' | 'date' | 'number';
440
+ /**
441
+ * 缺陷技术分析选项枚举
442
+ * custom_field32(缺陷技术分析)字段的选项值枚举定义
443
+ */
444
+ export declare enum DefectAnalysisType {
445
+ FUNCTION_IMPLEMENTATION = "\u529F\u80FD\u5B9E\u73B0\u95EE\u9898",
446
+ REQUIREMENT_CHANGE = "\u9700\u6C42\u53D8\u66F4\u95EE\u9898",
447
+ LEGACY_ISSUE = "\u5386\u53F2\u9057\u7559\u95EE\u9898",
448
+ CODE_LOGIC = "\u4EE3\u7801\u903B\u8F91\u95EE\u9898",
449
+ USER_INTERFACE = "\u7528\u6237\u754C\u9762\u95EE\u9898",
450
+ INTERFACE = "\u63A5\u53E3\u95EE\u9898",
451
+ DATA = "\u6570\u636E\u95EE\u9898",
452
+ PERFORMANCE = "\u6027\u80FD\u95EE\u9898",
453
+ ENVIRONMENT = "\u73AF\u5883\u95EE\u9898",
454
+ COMPATIBILITY = "\u517C\u5BB9\u6027\u95EE\u9898",
455
+ PRODUCT_DESIGN = "\u4EA7\u54C1\u8BBE\u8BA1\u95EE\u9898",
456
+ OPTIMIZATION_SUGGESTION = "\u4F18\u5316\u5EFA\u8BAE\u95EE\u9898",
457
+ TECH_REQUIREMENT_CHANGE = "\u6280\u672F\u5F15\u8D77\u7684\u9700\u6C42\u53D8\u66F4\u95EE\u9898",
458
+ USAGE_AND_CONFIG = "\u4F7F\u7528\u53CA\u914D\u7F6E\u95EE\u9898",
459
+ OTHER = "\u5176\u4ED6\u95EE\u9898"
459
460
  }
460
461
  /**
461
462
  * 用户工作项统计信息
@@ -1,2 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefectAnalysisType = void 0;
4
+ /**
5
+ * 缺陷技术分析选项枚举
6
+ * custom_field32(缺陷技术分析)字段的选项值枚举定义
7
+ */
8
+ var DefectAnalysisType;
9
+ (function (DefectAnalysisType) {
10
+ DefectAnalysisType["FUNCTION_IMPLEMENTATION"] = "\u529F\u80FD\u5B9E\u73B0\u95EE\u9898";
11
+ DefectAnalysisType["REQUIREMENT_CHANGE"] = "\u9700\u6C42\u53D8\u66F4\u95EE\u9898";
12
+ DefectAnalysisType["LEGACY_ISSUE"] = "\u5386\u53F2\u9057\u7559\u95EE\u9898";
13
+ DefectAnalysisType["CODE_LOGIC"] = "\u4EE3\u7801\u903B\u8F91\u95EE\u9898";
14
+ DefectAnalysisType["USER_INTERFACE"] = "\u7528\u6237\u754C\u9762\u95EE\u9898";
15
+ DefectAnalysisType["INTERFACE"] = "\u63A5\u53E3\u95EE\u9898";
16
+ DefectAnalysisType["DATA"] = "\u6570\u636E\u95EE\u9898";
17
+ DefectAnalysisType["PERFORMANCE"] = "\u6027\u80FD\u95EE\u9898";
18
+ DefectAnalysisType["ENVIRONMENT"] = "\u73AF\u5883\u95EE\u9898";
19
+ DefectAnalysisType["COMPATIBILITY"] = "\u517C\u5BB9\u6027\u95EE\u9898";
20
+ DefectAnalysisType["PRODUCT_DESIGN"] = "\u4EA7\u54C1\u8BBE\u8BA1\u95EE\u9898";
21
+ DefectAnalysisType["OPTIMIZATION_SUGGESTION"] = "\u4F18\u5316\u5EFA\u8BAE\u95EE\u9898";
22
+ DefectAnalysisType["TECH_REQUIREMENT_CHANGE"] = "\u6280\u672F\u5F15\u8D77\u7684\u9700\u6C42\u53D8\u66F4\u95EE\u9898";
23
+ DefectAnalysisType["USAGE_AND_CONFIG"] = "\u4F7F\u7528\u53CA\u914D\u7F6E\u95EE\u9898";
24
+ DefectAnalysisType["OTHER"] = "\u5176\u4ED6\u95EE\u9898";
25
+ })(DefectAnalysisType || (exports.DefectAnalysisType = DefectAnalysisType = {}));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hecom/codearts",
3
- "version": "0.1.0",
4
- "description": "CodeArts获取日报信息",
3
+ "version": "0.2.0",
4
+ "description": "华为云 CodeArts 统计分析工具",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -17,7 +17,8 @@
17
17
  "build": "rm -rf dist && tsc && chmod +x bin/codearts",
18
18
  "test": "jest --passWithNoTests",
19
19
  "test:coverage": "jest --coverage --passWithNoTests",
20
- "prepublishOnly": "npm run build"
20
+ "prepublishOnly": "npm run build",
21
+ "dev": "ts-node src/bin/cli.ts"
21
22
  },
22
23
  "keywords": [
23
24
  "huawei-cloud",
@@ -40,7 +41,7 @@
40
41
  "url": "https://github.com/hecom-rn/hecom-codearts/issues"
41
42
  },
42
43
  "engines": {
43
- "node": ">=16.0.0",
44
+ "node": ">=23.0.0",
44
45
  "npm": ">=7.0.0"
45
46
  },
46
47
  "dependencies": {