@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 CHANGED
@@ -35,7 +35,7 @@ npx @hecom/codearts bug-rate "迭代1,迭代2"
35
35
  npx @hecom/codearts config
36
36
 
37
37
  # 单独更新角色配置
38
- npx @hecom/codearts config role-id
38
+ npx @hecom/codearts config role
39
39
 
40
40
  # 查看当前配置
41
41
  npx @hecom/codearts config show
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-id <ids>', '角色 ID(支持逗号分隔,如: 1,2)')
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, constant_1.showLogo)();
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, constant_1.showLogo)();
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, constant_1.showLogo)();
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 <iterations>')
107
- .description('产品缺陷率统计,支持多个迭代')
108
- .action(async (iterations, options, command) => {
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)(iterations, cliOptions);
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, constant_1.showLogo)();
118
+ (0, console_1.showLogo)();
119
119
  // 检查是否有全局配置
120
120
  const hasConfig = (0, config_loader_1.configExists)();
121
121
  if (!hasConfig) {
@@ -2,4 +2,4 @@ import { CliOptions } from '../utils/config-loader';
2
2
  /**
3
3
  * bug 命令入口
4
4
  */
5
- export declare function bugCommand(iterationTitlesStr: string, cliOptions?: CliOptions): Promise<void>;
5
+ export declare function bugCommand(cliOptions?: CliOptions): Promise<void>;
@@ -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, iterationTitles) {
30
+ async function queryBugReportData(businessService, projectId, roleIds, iterations) {
24
31
  const roleNames = new Set();
25
32
  const targetMemberIds = new Set();
26
- if (roleIds.length > 0) {
27
- for (const roleId of roleIds) {
28
- const members = await businessService.getMembersByRoleId(projectId, roleId);
29
- members.forEach((member) => {
30
- targetMemberIds.add(member.user_num_id);
31
- roleNames.add(member.role_name);
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 filteredStories) {
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
- assignedUser: assignedUserName,
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
- iterations: iterationTitles,
119
- roleIds,
88
+ title: '产品缺陷率统计',
120
89
  roleNames: Array.from(roleNames),
121
- userStats: sortedStats,
122
- summary: {
123
- totalBugs,
124
- totalProductBugs,
125
- overallProductDefectRate,
126
- userCount: sortedStats.length,
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
- logger_1.logger.info(`产品缺陷率统计 [${data.roleNames.join(', ')}]`);
135
- logger_1.logger.info('='.repeat(80));
136
- logger_1.logger.info(`产品缺陷率: ${data.summary.overallProductDefectRate.toFixed(2)}% `);
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(data, iterationTitles) {
116
+ function outputCsv(list, projectId) {
154
117
  const csvLines = [];
155
- csvLines.push('迭代,处理人,Story名称,StoryID,总Bug数,产品问题Bug数,产品缺陷率');
156
- data.userStats.forEach((userStat) => {
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
- csvLines.push(`"${(0, csv_writer_1.escapeCsv)(data.iterations.join(', '))}",${userStat.assignedUser ?? ''},"${(0, csv_writer_1.escapeCsv)(story.storyName ?? '')}",${story.storyId ?? ''},${story.totalBugCount ?? ''},${story.productBugCount ?? ''},${defectRate}%`);
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-${iterationTitles.join('-')}.csv`;
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(iterationTitlesStr, cliOptions = {}) {
177
- try {
178
- if (!iterationTitlesStr || iterationTitlesStr.trim() === '') {
179
- throw new Error('请指定至少一个迭代标题');
180
- }
181
- const iterationTitles = iterationTitlesStr
182
- .split(/[,,;;|\s、]+/)
183
- .map((title) => title.trim())
184
- .filter((title) => title.length > 0);
185
- const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
186
- const businessService = new business_service_1.BusinessService(config);
187
- const reportData = await queryBugReportData(businessService, projectId, roleIds, iterationTitles);
188
- if (outputFormat === 'console') {
189
- outputConsole(reportData);
190
- }
191
- else if (outputFormat === 'csv') {
192
- outputCsv(reportData, iterationTitles);
193
- }
194
- else if (outputFormat === 'json') {
195
- outputJson(reportData);
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
- catch (error) {
199
- logger_1.logger.error(`执行过程中发生错误: `, error);
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
  // 第三阶段:配置项目相关配置