@hecom/codearts 0.2.2 → 0.3.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/README.md +1 -1
- package/dist/bin/cli.js +20 -5
- package/dist/commands/bug.command.d.ts +1 -1
- package/dist/commands/bug.command.js +78 -96
- package/dist/commands/config.command.js +133 -166
- package/dist/commands/daily.command.js +166 -158
- package/dist/commands/work-hour.command.js +53 -120
- package/dist/services/business.service.d.ts +14 -19
- package/dist/services/business.service.js +28 -37
- package/dist/types/index.d.ts +13 -13
- package/dist/types/index.js +8 -17
- package/dist/utils/config-loader.d.ts +5 -5
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/console.d.ts +4 -0
- package/dist/utils/console.js +47 -0
- package/dist/utils/csv-writer.d.ts +11 -2
- package/dist/utils/csv-writer.js +37 -5
- package/dist/utils/inquirer-theme.d.ts +13 -0
- package/dist/utils/inquirer-theme.js +29 -0
- package/dist/utils/logger.d.ts +1 -2
- package/dist/utils/logger.js +6 -1
- package/package.json +8 -5
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -23
package/README.md
CHANGED
package/dist/bin/cli.js
CHANGED
|
@@ -42,6 +42,7 @@ const config_command_1 = require("../commands/config.command");
|
|
|
42
42
|
const daily_command_1 = require("../commands/daily.command");
|
|
43
43
|
const work_hour_command_1 = require("../commands/work-hour.command");
|
|
44
44
|
const config_loader_1 = require("../utils/config-loader");
|
|
45
|
+
const console_1 = require("../utils/console");
|
|
45
46
|
const logger_1 = require("../utils/logger");
|
|
46
47
|
// 读取 package.json 中的版本号
|
|
47
48
|
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
@@ -51,13 +52,14 @@ const program = new commander_1.Command();
|
|
|
51
52
|
program.name('codearts').description('华为云 CodeArts 统计分析工具').version(version);
|
|
52
53
|
// 全局选项(环境变量覆盖)
|
|
53
54
|
program
|
|
54
|
-
.option('--role
|
|
55
|
+
.option('--role <ids>', '角色 ID(支持逗号分隔,如: 1,2)')
|
|
55
56
|
.option('--output <format>', '输出格式:console、csv、json', 'console');
|
|
56
57
|
// config 命令 - 交互式配置向导
|
|
57
58
|
const configCmd = program
|
|
58
59
|
.command('config')
|
|
59
60
|
.description('交互式配置向导,引导用户创建或更新全局配置文件')
|
|
60
61
|
.action(async () => {
|
|
62
|
+
(0, console_1.showLogo)();
|
|
61
63
|
await (0, config_command_1.configCommand)();
|
|
62
64
|
});
|
|
63
65
|
// config show 子命令 - 显示当前配置
|
|
@@ -65,6 +67,7 @@ configCmd
|
|
|
65
67
|
.command('show')
|
|
66
68
|
.description('显示当前配置信息')
|
|
67
69
|
.action(async () => {
|
|
70
|
+
(0, console_1.showLogo)();
|
|
68
71
|
await (0, config_command_1.showConfigCommand)();
|
|
69
72
|
});
|
|
70
73
|
// 为每个项目配置项添加子命令
|
|
@@ -75,6 +78,7 @@ availableConfigs.forEach((configItem) => {
|
|
|
75
78
|
.command(subCommandName)
|
|
76
79
|
.description(`更新${configItem.label}`)
|
|
77
80
|
.action(async () => {
|
|
81
|
+
(0, console_1.showLogo)();
|
|
78
82
|
await (0, config_command_1.updateProjectConfigCommand)(configItem.key);
|
|
79
83
|
});
|
|
80
84
|
});
|
|
@@ -99,18 +103,19 @@ program
|
|
|
99
103
|
});
|
|
100
104
|
// bug-rate 命令
|
|
101
105
|
program
|
|
102
|
-
.command('bug-rate
|
|
103
|
-
.description('
|
|
104
|
-
.action(async (
|
|
106
|
+
.command('bug-rate')
|
|
107
|
+
.description('产品缺陷率统计')
|
|
108
|
+
.action(async (options, command) => {
|
|
105
109
|
const cliOptions = command.parent.opts();
|
|
106
110
|
logger_1.logger.setOutputFormat(cliOptions.output);
|
|
107
|
-
await (0, bug_command_1.bugCommand)(
|
|
111
|
+
await (0, bug_command_1.bugCommand)(cliOptions);
|
|
108
112
|
});
|
|
109
113
|
// 检查配置并自动执行 config 命令
|
|
110
114
|
async function checkConfigAndRun() {
|
|
111
115
|
const args = process.argv.slice(2);
|
|
112
116
|
// 如果没有参数(直接执行 codearts),检测配置
|
|
113
117
|
if (args.length === 0) {
|
|
118
|
+
(0, console_1.showLogo)();
|
|
114
119
|
// 检查是否有全局配置
|
|
115
120
|
const hasConfig = (0, config_loader_1.configExists)();
|
|
116
121
|
if (!hasConfig) {
|
|
@@ -125,6 +130,16 @@ async function checkConfigAndRun() {
|
|
|
125
130
|
// 有参数,正常解析命令
|
|
126
131
|
program.parse();
|
|
127
132
|
}
|
|
133
|
+
process.on('uncaughtException', (error) => {
|
|
134
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
135
|
+
console.log('👋 操作取消!');
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// 重新抛出未知错误
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
128
143
|
checkConfigAndRun().catch((error) => {
|
|
129
144
|
logger_1.logger.error('执行失败: ', error);
|
|
130
145
|
process.exit(1);
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.bugCommand = bugCommand;
|
|
7
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
4
9
|
const business_service_1 = require("../services/business.service");
|
|
5
10
|
const types_1 = require("../types");
|
|
6
11
|
const config_loader_1 = require("../utils/config-loader");
|
|
12
|
+
const console_1 = require("../utils/console");
|
|
7
13
|
const csv_writer_1 = require("../utils/csv-writer");
|
|
14
|
+
const inquirer_theme_1 = require("../utils/inquirer-theme");
|
|
8
15
|
const logger_1 = require("../utils/logger");
|
|
9
16
|
/**
|
|
10
17
|
* 判断 Bug 是否属于需求变更或产品设计问题
|
|
@@ -20,55 +27,17 @@ function isRequirementOrDesignBug(bug) {
|
|
|
20
27
|
/**
|
|
21
28
|
* 查询 Bug 统计数据
|
|
22
29
|
*/
|
|
23
|
-
async function queryBugReportData(businessService, projectId, roleIds,
|
|
30
|
+
async function queryBugReportData(businessService, projectId, roleIds, iterations) {
|
|
24
31
|
const roleNames = new Set();
|
|
25
32
|
const targetMemberIds = new Set();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const stories = await businessService.getStoriesByIterationTitles(projectId, iterationTitles);
|
|
36
|
-
if (stories.length === 0) {
|
|
37
|
-
return {
|
|
38
|
-
iterations: iterationTitles,
|
|
39
|
-
roleIds,
|
|
40
|
-
roleNames: Array.from(roleNames),
|
|
41
|
-
userStats: [],
|
|
42
|
-
summary: {
|
|
43
|
-
totalBugs: 0,
|
|
44
|
-
totalProductBugs: 0,
|
|
45
|
-
overallProductDefectRate: 0,
|
|
46
|
-
userCount: 0,
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const filteredStories = roleIds.length > 0
|
|
51
|
-
? stories.filter((story) => {
|
|
52
|
-
const assignedUserId = story.assigned_user?.id;
|
|
53
|
-
return assignedUserId && targetMemberIds.has(assignedUserId);
|
|
54
|
-
})
|
|
55
|
-
: stories;
|
|
56
|
-
if (filteredStories.length === 0) {
|
|
57
|
-
return {
|
|
58
|
-
iterations: iterationTitles,
|
|
59
|
-
roleIds,
|
|
60
|
-
roleNames: Array.from(roleNames),
|
|
61
|
-
userStats: [],
|
|
62
|
-
summary: {
|
|
63
|
-
totalBugs: 0,
|
|
64
|
-
totalProductBugs: 0,
|
|
65
|
-
overallProductDefectRate: 0,
|
|
66
|
-
userCount: 0,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
33
|
+
const members = await businessService.getMembersByRoleIds(projectId, roleIds);
|
|
34
|
+
members.forEach((member) => {
|
|
35
|
+
targetMemberIds.add(member.user_num_id);
|
|
36
|
+
roleNames.add(member.role_name);
|
|
37
|
+
});
|
|
38
|
+
const stories = await businessService.getStoriesByIterations(projectId, iterations, members.map((m) => m.user_id));
|
|
70
39
|
const bugStatsMap = new Map();
|
|
71
|
-
for (const story of
|
|
40
|
+
for (const story of stories) {
|
|
72
41
|
const childIssues = await businessService.getChildIssues(projectId, String(story.id));
|
|
73
42
|
const allBugs = childIssues.filter((issue) => issue.tracker.id === 3);
|
|
74
43
|
if (allBugs.length === 0) {
|
|
@@ -82,7 +51,7 @@ async function queryBugReportData(businessService, projectId, roleIds, iteration
|
|
|
82
51
|
}
|
|
83
52
|
if (!bugStatsMap.has(assignedUserId)) {
|
|
84
53
|
bugStatsMap.set(assignedUserId, {
|
|
85
|
-
|
|
54
|
+
userName: assignedUserName,
|
|
86
55
|
userId: assignedUserId,
|
|
87
56
|
totalBugCount: 0,
|
|
88
57
|
productBugCount: 0,
|
|
@@ -96,6 +65,7 @@ async function queryBugReportData(businessService, projectId, roleIds, iteration
|
|
|
96
65
|
userStats.stories.push({
|
|
97
66
|
storyName: story.name,
|
|
98
67
|
storyId: story.id,
|
|
68
|
+
iterationName: story.iteration?.name,
|
|
99
69
|
totalBugCount: allBugs.length,
|
|
100
70
|
productBugCount: productBugs.length,
|
|
101
71
|
});
|
|
@@ -115,34 +85,27 @@ async function queryBugReportData(businessService, projectId, roleIds, iteration
|
|
|
115
85
|
});
|
|
116
86
|
const overallProductDefectRate = totalBugs > 0 ? Math.round((totalProductBugs / totalBugs) * 100 * 100) / 100 : 0;
|
|
117
87
|
return {
|
|
118
|
-
|
|
119
|
-
roleIds,
|
|
88
|
+
title: '产品缺陷率统计',
|
|
120
89
|
roleNames: Array.from(roleNames),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
90
|
+
list: sortedStats,
|
|
91
|
+
totalMap: [
|
|
92
|
+
['产品缺陷率', `${overallProductDefectRate.toFixed(2)}%`],
|
|
93
|
+
['总 Bug 数', `${totalBugs}个`],
|
|
94
|
+
['产品问题数', `${totalProductBugs}个`],
|
|
95
|
+
['统计人数', `${sortedStats.length}人`],
|
|
96
|
+
['统计迭代', `${iterations.map((i) => i.name).join(', ')}`],
|
|
97
|
+
],
|
|
128
98
|
};
|
|
129
99
|
}
|
|
130
100
|
/**
|
|
131
101
|
* 控制台输出产品缺陷率统计
|
|
132
102
|
*/
|
|
133
103
|
function outputConsole(data) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
logger_1.logger.info(`总 Bug 数: ${data.summary.totalBugs}个`);
|
|
138
|
-
logger_1.logger.info(`产品问题: ${data.summary.totalProductBugs}个`);
|
|
139
|
-
logger_1.logger.info(`统计人数: ${data.summary.userCount}人`);
|
|
140
|
-
logger_1.logger.info(`统计迭代: ${data.iterations.join(', ')}`);
|
|
141
|
-
logger_1.logger.info('='.repeat(80));
|
|
142
|
-
data.userStats.forEach((stats) => {
|
|
143
|
-
logger_1.logger.info(`\x1b[31m${stats.assignedUser}: ${stats.productDefectRate.toFixed(2)}% (${stats.productBugCount}/${stats.totalBugCount})\x1b[0m`);
|
|
104
|
+
(0, console_1.consoleTotal)(data);
|
|
105
|
+
data.list.forEach((stats) => {
|
|
106
|
+
logger_1.logger.info(`\x1b[31m${stats.userName}: ${stats.productDefectRate.toFixed(2)}% (${stats.productBugCount}/${stats.totalBugCount})\x1b[0m`);
|
|
144
107
|
stats.stories.forEach((story) => {
|
|
145
|
-
logger_1.logger.info(` ${story.storyName} (${story.productBugCount}/${story.totalBugCount})`);
|
|
108
|
+
logger_1.logger.info(` [${story.iterationName}] ${story.storyName} (${story.productBugCount}/${story.totalBugCount})`);
|
|
146
109
|
});
|
|
147
110
|
logger_1.logger.info('');
|
|
148
111
|
});
|
|
@@ -150,18 +113,27 @@ function outputConsole(data) {
|
|
|
150
113
|
/**
|
|
151
114
|
* CSV 文件输出
|
|
152
115
|
*/
|
|
153
|
-
function outputCsv(
|
|
116
|
+
function outputCsv(list, projectId) {
|
|
154
117
|
const csvLines = [];
|
|
155
|
-
csvLines.push('
|
|
156
|
-
|
|
118
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)(['迭代', '处理人', 'Story', '总Bug数', '产品问题Bug数', '产品缺陷率']));
|
|
119
|
+
list.forEach((userStat) => {
|
|
157
120
|
userStat.stories.forEach((story) => {
|
|
158
121
|
const defectRate = story.totalBugCount > 0
|
|
159
122
|
? ((story.productBugCount / story.totalBugCount) * 100).toFixed(2)
|
|
160
123
|
: '0.00';
|
|
161
|
-
|
|
124
|
+
const storyUrl = (0, console_1.issueLink)(projectId, story.storyId);
|
|
125
|
+
const storyLink = (0, csv_writer_1.createHyperlinkFormula)(storyUrl, story.storyName ?? '');
|
|
126
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)([
|
|
127
|
+
story.iterationName,
|
|
128
|
+
userStat.userName ?? '',
|
|
129
|
+
storyLink,
|
|
130
|
+
story.totalBugCount ?? '',
|
|
131
|
+
story.productBugCount ?? '',
|
|
132
|
+
`${defectRate}%`,
|
|
133
|
+
]));
|
|
162
134
|
});
|
|
163
135
|
});
|
|
164
|
-
const filename = `bug-rate
|
|
136
|
+
const filename = `bug-rate.csv`;
|
|
165
137
|
(0, csv_writer_1.writeCsvFile)(filename, csvLines, logger_1.logger);
|
|
166
138
|
}
|
|
167
139
|
/**
|
|
@@ -173,30 +145,40 @@ function outputJson(data) {
|
|
|
173
145
|
/**
|
|
174
146
|
* bug 命令入口
|
|
175
147
|
*/
|
|
176
|
-
async function bugCommand(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
148
|
+
async function bugCommand(cliOptions = {}) {
|
|
149
|
+
const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
150
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
151
|
+
const iterations = await businessService.getIterations(projectId);
|
|
152
|
+
if (iterations.length === 0) {
|
|
153
|
+
throw new Error('未获取到任何迭代信息');
|
|
154
|
+
}
|
|
155
|
+
const iterationChoices = iterations.map((iteration) => ({
|
|
156
|
+
name: `${iteration.name} (${iteration.begin_time} ~ ${iteration.end_time})`,
|
|
157
|
+
value: iteration,
|
|
158
|
+
checked: false,
|
|
159
|
+
}));
|
|
160
|
+
const selectedIterations = await (0, prompts_1.checkbox)({
|
|
161
|
+
message: '请选择要统计的迭代:',
|
|
162
|
+
choices: iterationChoices,
|
|
163
|
+
validate: (answer) => {
|
|
164
|
+
if (answer.length === 0) {
|
|
165
|
+
return '至少需要选择一个迭代';
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
theme: inquirer_theme_1.globalTheme,
|
|
170
|
+
});
|
|
171
|
+
const spinner = (0, ora_1.default)('正在查询数据...').start();
|
|
172
|
+
const reportData = await queryBugReportData(businessService, projectId, roleIds, selectedIterations);
|
|
173
|
+
spinner.stop();
|
|
174
|
+
logger_1.logger.info();
|
|
175
|
+
if (outputFormat === 'console') {
|
|
176
|
+
outputConsole(reportData);
|
|
177
|
+
}
|
|
178
|
+
else if (outputFormat === 'csv') {
|
|
179
|
+
outputCsv(reportData.list, projectId);
|
|
197
180
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
process.exit(1);
|
|
181
|
+
else if (outputFormat === 'json') {
|
|
182
|
+
outputJson(reportData.list);
|
|
201
183
|
}
|
|
202
184
|
}
|