@hecom/codearts 0.2.3 → 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 +10 -10
- package/dist/commands/bug.command.d.ts +1 -1
- package/dist/commands/bug.command.js +78 -96
- package/dist/commands/config.command.js +10 -16
- package/dist/commands/daily.command.js +155 -159
- 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/{constant/index.js → utils/console.js} +18 -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 +0 -1
- package/package.json +4 -2
- package/dist/constant/index.d.ts +0 -1
package/README.md
CHANGED
package/dist/bin/cli.js
CHANGED
|
@@ -41,8 +41,8 @@ const bug_command_1 = require("../commands/bug.command");
|
|
|
41
41
|
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
|
-
const constant_1 = require("../constant");
|
|
45
44
|
const config_loader_1 = require("../utils/config-loader");
|
|
45
|
+
const console_1 = require("../utils/console");
|
|
46
46
|
const logger_1 = require("../utils/logger");
|
|
47
47
|
// 读取 package.json 中的版本号
|
|
48
48
|
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
@@ -52,14 +52,14 @@ const program = new commander_1.Command();
|
|
|
52
52
|
program.name('codearts').description('华为云 CodeArts 统计分析工具').version(version);
|
|
53
53
|
// 全局选项(环境变量覆盖)
|
|
54
54
|
program
|
|
55
|
-
.option('--role
|
|
55
|
+
.option('--role <ids>', '角色 ID(支持逗号分隔,如: 1,2)')
|
|
56
56
|
.option('--output <format>', '输出格式:console、csv、json', 'console');
|
|
57
57
|
// config 命令 - 交互式配置向导
|
|
58
58
|
const configCmd = program
|
|
59
59
|
.command('config')
|
|
60
60
|
.description('交互式配置向导,引导用户创建或更新全局配置文件')
|
|
61
61
|
.action(async () => {
|
|
62
|
-
(0,
|
|
62
|
+
(0, console_1.showLogo)();
|
|
63
63
|
await (0, config_command_1.configCommand)();
|
|
64
64
|
});
|
|
65
65
|
// config show 子命令 - 显示当前配置
|
|
@@ -67,7 +67,7 @@ configCmd
|
|
|
67
67
|
.command('show')
|
|
68
68
|
.description('显示当前配置信息')
|
|
69
69
|
.action(async () => {
|
|
70
|
-
(0,
|
|
70
|
+
(0, console_1.showLogo)();
|
|
71
71
|
await (0, config_command_1.showConfigCommand)();
|
|
72
72
|
});
|
|
73
73
|
// 为每个项目配置项添加子命令
|
|
@@ -78,7 +78,7 @@ availableConfigs.forEach((configItem) => {
|
|
|
78
78
|
.command(subCommandName)
|
|
79
79
|
.description(`更新${configItem.label}`)
|
|
80
80
|
.action(async () => {
|
|
81
|
-
(0,
|
|
81
|
+
(0, console_1.showLogo)();
|
|
82
82
|
await (0, config_command_1.updateProjectConfigCommand)(configItem.key);
|
|
83
83
|
});
|
|
84
84
|
});
|
|
@@ -103,19 +103,19 @@ program
|
|
|
103
103
|
});
|
|
104
104
|
// bug-rate 命令
|
|
105
105
|
program
|
|
106
|
-
.command('bug-rate
|
|
107
|
-
.description('
|
|
108
|
-
.action(async (
|
|
106
|
+
.command('bug-rate')
|
|
107
|
+
.description('产品缺陷率统计')
|
|
108
|
+
.action(async (options, command) => {
|
|
109
109
|
const cliOptions = command.parent.opts();
|
|
110
110
|
logger_1.logger.setOutputFormat(cliOptions.output);
|
|
111
|
-
await (0, bug_command_1.bugCommand)(
|
|
111
|
+
await (0, bug_command_1.bugCommand)(cliOptions);
|
|
112
112
|
});
|
|
113
113
|
// 检查配置并自动执行 config 命令
|
|
114
114
|
async function checkConfigAndRun() {
|
|
115
115
|
const args = process.argv.slice(2);
|
|
116
116
|
// 如果没有参数(直接执行 codearts),检测配置
|
|
117
117
|
if (args.length === 0) {
|
|
118
|
-
(0,
|
|
118
|
+
(0, console_1.showLogo)();
|
|
119
119
|
// 检查是否有全局配置
|
|
120
120
|
const hasConfig = (0, config_loader_1.configExists)();
|
|
121
121
|
if (!hasConfig) {
|
|
@@ -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
|
}
|
|
@@ -41,11 +41,13 @@ exports.updateProjectConfigCommand = updateProjectConfigCommand;
|
|
|
41
41
|
exports.getAvailableProjectConfigs = getAvailableProjectConfigs;
|
|
42
42
|
exports.showConfigCommand = showConfigCommand;
|
|
43
43
|
const prompts_1 = require("@inquirer/prompts");
|
|
44
|
+
const ora_1 = __importDefault(require("ora"));
|
|
44
45
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
45
46
|
const readline = __importStar(require("readline"));
|
|
46
47
|
const business_service_1 = require("../services/business.service");
|
|
47
48
|
const types_1 = require("../types");
|
|
48
49
|
const config_loader_1 = require("../utils/config-loader");
|
|
50
|
+
const inquirer_theme_1 = require("../utils/inquirer-theme");
|
|
49
51
|
const logger_1 = require("../utils/logger");
|
|
50
52
|
/**
|
|
51
53
|
* 清除终端上指定行数的内容
|
|
@@ -77,7 +79,9 @@ async function inputRoleIds(existingValue) {
|
|
|
77
79
|
async function configureRoleIds(businessService, projectId, existingValue) {
|
|
78
80
|
let roles = [];
|
|
79
81
|
try {
|
|
82
|
+
const spinner = (0, ora_1.default)('正在获取角色列表...').start();
|
|
80
83
|
roles = await businessService.getProjectRoles(projectId);
|
|
84
|
+
spinner.stop();
|
|
81
85
|
}
|
|
82
86
|
catch (error) {
|
|
83
87
|
logger_1.logger.error('❌ 获取角色列表失败:', error);
|
|
@@ -104,22 +108,7 @@ async function configureRoleIds(businessService, projectId, existingValue) {
|
|
|
104
108
|
}
|
|
105
109
|
return true;
|
|
106
110
|
},
|
|
107
|
-
theme:
|
|
108
|
-
style: {
|
|
109
|
-
help: (text) => `\x1b[90m${123}\x1b[0m`, // 灰色
|
|
110
|
-
keysHelpTip: (keys) => {
|
|
111
|
-
const actionMap = {
|
|
112
|
-
navigate: '上下移动',
|
|
113
|
-
select: '选择/取消',
|
|
114
|
-
all: '全选',
|
|
115
|
-
invert: '反选',
|
|
116
|
-
submit: '提交',
|
|
117
|
-
};
|
|
118
|
-
const tips = keys.map(([key, action]) => `${key} \x1b[90m${actionMap[action] || action}\x1b[0m`);
|
|
119
|
-
return tips.join(' • ');
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
},
|
|
111
|
+
theme: inquirer_theme_1.globalTheme,
|
|
123
112
|
});
|
|
124
113
|
return selectedRoleIds.join(',');
|
|
125
114
|
}
|
|
@@ -237,7 +226,9 @@ async function configCommand() {
|
|
|
237
226
|
enableLogging: false,
|
|
238
227
|
});
|
|
239
228
|
// 验证凭证
|
|
229
|
+
const spinner = (0, ora_1.default)('正在验证 IAM 凭证...').start();
|
|
240
230
|
const validationResult = await businessService.validateCredentials();
|
|
231
|
+
spinner.stop();
|
|
241
232
|
if (validationResult.success) {
|
|
242
233
|
credentialsValid = true;
|
|
243
234
|
}
|
|
@@ -270,7 +261,9 @@ async function configCommand() {
|
|
|
270
261
|
});
|
|
271
262
|
}
|
|
272
263
|
try {
|
|
264
|
+
const spinner = (0, ora_1.default)('正在获取项目列表...').start();
|
|
273
265
|
projects = await businessService.getProjects(100);
|
|
266
|
+
spinner.stop();
|
|
274
267
|
}
|
|
275
268
|
catch (error) {
|
|
276
269
|
logger_1.logger.error(`❌ 获取项目列表失败: `, error);
|
|
@@ -293,6 +286,7 @@ async function configCommand() {
|
|
|
293
286
|
message: '请选择项目:',
|
|
294
287
|
choices: projectChoices,
|
|
295
288
|
default: defaultProjectId,
|
|
289
|
+
theme: inquirer_theme_1.globalTheme,
|
|
296
290
|
});
|
|
297
291
|
}
|
|
298
292
|
// 第三阶段:配置项目相关配置
|