@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 +17 -41
- package/dist/commands/bug.command.d.ts +7 -0
- package/dist/commands/bug.command.js +158 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +3 -1
- package/dist/services/api.service.d.ts +6 -5
- package/dist/services/api.service.js +20 -9
- package/dist/services/business.service.d.ts +22 -2
- package/dist/services/business.service.js +80 -11
- package/dist/types/index.d.ts +118 -117
- package/dist/types/index.js +23 -0
- package/package.json +5 -4
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
|
|
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
|
|
52
|
+
const version = packageJson.version;
|
|
52
53
|
const program = new commander_1.Command();
|
|
53
|
-
program.name('codearts').description('华为云 CodeArts
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|
package/dist/commands/index.d.ts
CHANGED
package/dist/commands/index.js
CHANGED
|
@@ -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 {
|
|
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,
|
|
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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
362
|
-
|
|
363
|
-
name
|
|
343
|
+
export interface IssueProjectV2 {
|
|
344
|
+
identifier: string;
|
|
345
|
+
name: string;
|
|
346
|
+
id: number;
|
|
347
|
+
type: string;
|
|
364
348
|
}
|
|
365
|
-
export interface
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
id?: number;
|
|
369
|
-
project_type?: string;
|
|
349
|
+
export interface IssueTrackerV2 {
|
|
350
|
+
name: string;
|
|
351
|
+
id: number;
|
|
370
352
|
}
|
|
371
|
-
export interface
|
|
372
|
-
|
|
373
|
-
|
|
353
|
+
export interface IssueSeverityV2 {
|
|
354
|
+
name: string;
|
|
355
|
+
id: number;
|
|
374
356
|
}
|
|
375
|
-
export interface
|
|
376
|
-
|
|
377
|
-
|
|
357
|
+
export interface IssuePriorityV2 {
|
|
358
|
+
name: string;
|
|
359
|
+
id: number;
|
|
378
360
|
}
|
|
379
|
-
export interface
|
|
380
|
-
|
|
381
|
-
|
|
361
|
+
export interface IssueStatusV2 {
|
|
362
|
+
name: string;
|
|
363
|
+
id: number;
|
|
382
364
|
}
|
|
383
|
-
export interface
|
|
384
|
-
|
|
385
|
-
|
|
365
|
+
export interface IssueStatusAttributeV2 {
|
|
366
|
+
name: string;
|
|
367
|
+
id: number;
|
|
386
368
|
}
|
|
387
|
-
export interface
|
|
388
|
-
|
|
389
|
-
|
|
369
|
+
export interface IssueDomainV2 {
|
|
370
|
+
name: string;
|
|
371
|
+
id: number;
|
|
390
372
|
}
|
|
391
|
-
export interface
|
|
373
|
+
export interface IssueModuleV2 {
|
|
392
374
|
id?: number;
|
|
393
375
|
name?: string;
|
|
394
376
|
}
|
|
395
|
-
export interface
|
|
377
|
+
export interface IssueDeveloperV2 {
|
|
396
378
|
id?: number;
|
|
397
379
|
name?: string;
|
|
398
380
|
}
|
|
399
|
-
export interface
|
|
400
|
-
|
|
401
|
-
|
|
381
|
+
export interface IssueFixedVersionV2 {
|
|
382
|
+
name: string;
|
|
383
|
+
id: number;
|
|
402
384
|
}
|
|
403
|
-
export interface
|
|
404
|
-
|
|
405
|
-
|
|
385
|
+
export interface IssueOrderV2 {
|
|
386
|
+
name: number;
|
|
387
|
+
id: number;
|
|
406
388
|
}
|
|
407
|
-
export interface
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
expected_work_hours
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
* 用户工作项统计信息
|
package/dist/types/index.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
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": ">=
|
|
44
|
+
"node": ">=23.0.0",
|
|
44
45
|
"npm": ">=7.0.0"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|