@hecom/codearts 0.3.2 → 0.4.1

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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/cli.js +25 -4
  3. package/dist/charts/chart.interface.d.ts +5 -0
  4. package/dist/charts/chart.interface.js +2 -0
  5. package/dist/charts/index.d.ts +2 -0
  6. package/dist/charts/index.js +17 -0
  7. package/dist/charts/modules/__tests__/bug-by-developer-hours.test.d.ts +1 -0
  8. package/dist/charts/modules/__tests__/bug-by-developer-hours.test.js +54 -0
  9. package/dist/charts/modules/__tests__/bug-by-fix-duration.test.d.ts +1 -0
  10. package/dist/charts/modules/__tests__/bug-by-fix-duration.test.js +89 -0
  11. package/dist/charts/modules/__tests__/bug-open-priority-heatmap.test.d.ts +1 -0
  12. package/dist/charts/modules/__tests__/bug-open-priority-heatmap.test.js +61 -0
  13. package/dist/charts/modules/bug-by-assignee.d.ts +2 -0
  14. package/dist/charts/modules/bug-by-assignee.js +30 -0
  15. package/dist/charts/modules/bug-by-defect-analysis.d.ts +2 -0
  16. package/dist/charts/modules/bug-by-defect-analysis.js +30 -0
  17. package/dist/charts/modules/bug-by-developer-hours.d.ts +2 -0
  18. package/dist/charts/modules/bug-by-developer-hours.js +32 -0
  19. package/dist/charts/modules/bug-by-fix-duration.d.ts +2 -0
  20. package/dist/charts/modules/bug-by-fix-duration.js +46 -0
  21. package/dist/charts/modules/bug-by-module.d.ts +2 -0
  22. package/dist/charts/modules/bug-by-module.js +34 -0
  23. package/dist/charts/modules/bug-open-priority-heatmap.d.ts +2 -0
  24. package/dist/charts/modules/bug-open-priority-heatmap.js +74 -0
  25. package/dist/charts/renderer.d.ts +21 -0
  26. package/dist/charts/renderer.js +163 -0
  27. package/dist/commands/config.command.d.ts +1 -1
  28. package/dist/commands/config.command.js +6 -6
  29. package/dist/commands/fix.command.d.ts +6 -0
  30. package/dist/commands/fix.command.js +220 -0
  31. package/dist/commands/index.d.ts +2 -0
  32. package/dist/commands/index.js +5 -1
  33. package/dist/commands/rebug.command.d.ts +5 -0
  34. package/dist/commands/rebug.command.js +113 -0
  35. package/dist/services/api.service.d.ts +10 -2
  36. package/dist/services/api.service.js +19 -0
  37. package/dist/services/business.service.d.ts +40 -1
  38. package/dist/services/business.service.js +226 -5
  39. package/dist/types/index.d.ts +107 -1
  40. package/dist/types/index.js +60 -1
  41. package/dist/utils/config-loader.d.ts +7 -6
  42. package/dist/utils/config-loader.js +13 -13
  43. package/package.json +5 -2
package/README.md CHANGED
@@ -31,7 +31,7 @@ npx @hecom/codearts bug-rate "迭代1,迭代2"
31
31
  ### 3. 更新配置
32
32
 
33
33
  ```bash
34
- # 更新全局配置
34
+ # 更新配置文件
35
35
  npx @hecom/codearts config
36
36
 
37
37
  # 单独更新角色配置
package/dist/bin/cli.js CHANGED
@@ -40,6 +40,8 @@ const path = __importStar(require("path"));
40
40
  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
+ const fix_command_1 = require("../commands/fix.command");
44
+ const rebug_command_1 = require("../commands/rebug.command");
43
45
  const work_hour_command_1 = require("../commands/work-hour.command");
44
46
  const config_loader_1 = require("../utils/config-loader");
45
47
  const console_1 = require("../utils/console");
@@ -57,7 +59,7 @@ program
57
59
  // config 命令 - 交互式配置向导
58
60
  const configCmd = program
59
61
  .command('config')
60
- .description('交互式配置向导,引导用户创建或更新全局配置文件')
62
+ .description('交互式配置向导,引导用户创建或更新配置文件')
61
63
  .action(async () => {
62
64
  (0, console_1.showLogo)();
63
65
  await (0, config_command_1.configCommand)();
@@ -85,7 +87,7 @@ availableConfigs.forEach((configItem) => {
85
87
  // daily 命令
86
88
  program
87
89
  .command('daily [date]')
88
- .description('每日工时统计(默认日期为当天)')
90
+ .description('每日工时统计(默认日期为当天),日期格式:YYYY-MM-DD')
89
91
  .option('-r, --report', '显示总结报告', false)
90
92
  .action(async (date, options, command) => {
91
93
  const cliOptions = { ...command.parent.opts(), report: options.report };
@@ -95,7 +97,7 @@ program
95
97
  // work-hour 命令
96
98
  program
97
99
  .command('work-hour [year]')
98
- .description('年度工时统计(默认当前年份)')
100
+ .description('年度工时统计(默认当前年份),年份格式:YYYY')
99
101
  .action(async (year, options, command) => {
100
102
  const cliOptions = command.parent.opts();
101
103
  logger_1.logger.setOutputFormat(cliOptions.output);
@@ -110,13 +112,32 @@ program
110
112
  logger_1.logger.setOutputFormat(cliOptions.output);
111
113
  await (0, bug_command_1.bugCommand)(cliOptions);
112
114
  });
115
+ // fix 命令
116
+ program
117
+ .command('fix')
118
+ .description('交互式修复 bug,填写相关信息')
119
+ .action(async (options, command) => {
120
+ const cliOptions = command.parent.opts();
121
+ logger_1.logger.setOutputFormat(cliOptions.output);
122
+ await (0, fix_command_1.fixCommand)(cliOptions);
123
+ });
124
+ // rebug 命令
125
+ program
126
+ .command('rebug')
127
+ .description('Bug 列表交互式查询与多维度可视化分析')
128
+ .option('--output-dir <path>', '输出 HTML 报告的目录(默认输出到系统 cache 目录,指定此参数则输出到当前目录)')
129
+ .action(async (options, command) => {
130
+ const cliOptions = { ...command.parent.opts(), outputDir: options.outputDir };
131
+ logger_1.logger.setOutputFormat(cliOptions.output);
132
+ await (0, rebug_command_1.rebugCommand)(cliOptions);
133
+ });
113
134
  // 检查配置并自动执行 config 命令
114
135
  async function checkConfigAndRun() {
115
136
  const args = process.argv.slice(2);
116
137
  // 如果没有参数(直接执行 codearts),检测配置
117
138
  if (args.length === 0) {
118
139
  (0, console_1.showLogo)();
119
- // 检查是否有全局配置
140
+ // 检查是否有配置文件
120
141
  const hasConfig = (0, config_loader_1.configExists)();
121
142
  if (!hasConfig) {
122
143
  // 没有配置,自动执行 config 命令
@@ -0,0 +1,5 @@
1
+ import { IssueItem } from '../types';
2
+ export interface ChartModule {
3
+ title: string;
4
+ buildOption(bugs: IssueItem[]): object;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from './chart.interface';
2
+ export declare const allCharts: ChartModule[];
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.allCharts = void 0;
4
+ const bug_by_assignee_1 = require("./modules/bug-by-assignee");
5
+ const bug_by_defect_analysis_1 = require("./modules/bug-by-defect-analysis");
6
+ const bug_by_module_1 = require("./modules/bug-by-module");
7
+ const bug_by_fix_duration_1 = require("./modules/bug-by-fix-duration");
8
+ const bug_by_developer_hours_1 = require("./modules/bug-by-developer-hours");
9
+ const bug_open_priority_heatmap_1 = require("./modules/bug-open-priority-heatmap");
10
+ exports.allCharts = [
11
+ bug_by_defect_analysis_1.bugByDefectAnalysisChart,
12
+ bug_by_assignee_1.bugByAssigneeChart,
13
+ bug_by_module_1.bugByModuleChart,
14
+ bug_by_fix_duration_1.bugByFixDurationChart,
15
+ bug_by_developer_hours_1.bugByDeveloperHoursChart,
16
+ bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart,
17
+ ];
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const bug_by_developer_hours_1 = require("../bug-by-developer-hours");
4
+ function makeIssue(nickName, hours) {
5
+ return {
6
+ developer: {
7
+ id: 1,
8
+ name: '',
9
+ nick_name: nickName,
10
+ user_id: '',
11
+ user_num_id: 1,
12
+ first_name: '',
13
+ },
14
+ actual_work_hours: hours,
15
+ new_custom_fields: [],
16
+ deleted: false,
17
+ };
18
+ }
19
+ describe('bugByDeveloperHoursChart', () => {
20
+ it('标题应为"开发人员工时消耗"', () => {
21
+ expect(bug_by_developer_hours_1.bugByDeveloperHoursChart.title).toBe('开发人员工时消耗');
22
+ });
23
+ it('按开发人员汇总工时并降序排列', () => {
24
+ const bugs = [makeIssue('张三', 3), makeIssue('李四', 5), makeIssue('张三', 1)];
25
+ const option = bug_by_developer_hours_1.bugByDeveloperHoursChart.buildOption(bugs);
26
+ const names = option.yAxis.data;
27
+ const values = option.series[0].data;
28
+ expect(names[0]).toBe('李四');
29
+ expect(values[0]).toBe(5);
30
+ expect(names[1]).toBe('张三');
31
+ expect(values[1]).toBe(4);
32
+ });
33
+ it('过滤掉 nick_name 为空的条目', () => {
34
+ const bugs = [makeIssue('', 4), makeIssue('王五', 2)];
35
+ const option = bug_by_developer_hours_1.bugByDeveloperHoursChart.buildOption(bugs);
36
+ const names = option.yAxis.data;
37
+ expect(names).not.toContain('');
38
+ expect(names).toContain('王五');
39
+ });
40
+ it('过滤掉 developer 未设置的条目', () => {
41
+ const bugs = [
42
+ { ...makeIssue('赵六', 3), developer: null },
43
+ makeIssue('赵六', 1),
44
+ ];
45
+ const option = bug_by_developer_hours_1.bugByDeveloperHoursChart.buildOption(bugs);
46
+ const values = option.series[0].data;
47
+ expect(values[0]).toBe(1);
48
+ });
49
+ it('空数组时返回空图表', () => {
50
+ const option = bug_by_developer_hours_1.bugByDeveloperHoursChart.buildOption([]);
51
+ expect(option.yAxis.data).toHaveLength(0);
52
+ expect(option.series[0].data).toHaveLength(0);
53
+ });
54
+ });
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const bug_by_fix_duration_1 = require("../bug-by-fix-duration");
4
+ function makeIssue(overrides) {
5
+ return {
6
+ actual_work_hours: 0,
7
+ assigned_cc_user: [],
8
+ assigned_user: {
9
+ id: 1,
10
+ name: '',
11
+ nick_name: '测试人',
12
+ user_id: '',
13
+ user_num_id: 1,
14
+ first_name: '',
15
+ },
16
+ begin_time: '',
17
+ closed_time: '',
18
+ created_time: '2026-03-01T08:00:00Z',
19
+ creator: { id: 1, name: '', nick_name: '', user_id: '', user_num_id: 1, first_name: '' },
20
+ custom_fields: [],
21
+ developer: {
22
+ id: 1,
23
+ name: '',
24
+ nick_name: '开发人',
25
+ user_id: '',
26
+ user_num_id: 1,
27
+ first_name: '',
28
+ },
29
+ domain: { id: 1, name: '' },
30
+ done_ratio: 0,
31
+ end_time: '',
32
+ expected_work_hours: 0,
33
+ id: 1,
34
+ iteration: { id: 1, name: '' },
35
+ module: { id: 1, name: '' },
36
+ name: 'test bug',
37
+ new_custom_fields: [],
38
+ parent_issue: { id: 0, name: '' },
39
+ priority: { id: 1, name: '中' },
40
+ project: { project_id: '', project_name: '', project_num_id: 1 },
41
+ severity: { id: 1, name: '重要' },
42
+ status: { id: 5, name: '已关闭' },
43
+ tracker: { id: 3, name: 'Bug' },
44
+ updated_time: '',
45
+ deleted: false,
46
+ ...overrides,
47
+ };
48
+ }
49
+ describe('bugByFixDurationChart', () => {
50
+ it('标题应为"修复周期分布"', () => {
51
+ expect(bug_by_fix_duration_1.bugByFixDurationChart.title).toBe('修复周期分布');
52
+ });
53
+ it('closed_time 为空时归入"未关闭"桶', () => {
54
+ const bugs = [makeIssue({ closed_time: '' })];
55
+ const option = bug_by_fix_duration_1.bugByFixDurationChart.buildOption(bugs);
56
+ const data = option.series[0].data;
57
+ const categories = option.xAxis.data;
58
+ const idx = categories.indexOf('未关闭');
59
+ expect(idx).toBeGreaterThanOrEqual(0);
60
+ expect(data[idx]).toBe(1);
61
+ });
62
+ it('1天内关闭归入"≤1天"桶(ceil,间隔0.5天)', () => {
63
+ const created = '2026-03-01T08:00:00Z';
64
+ const closed = '2026-03-01T20:00:00Z'; // 12h → Math.ceil(0.5) = 1
65
+ const bugs = [makeIssue({ created_time: created, closed_time: closed })];
66
+ const option = bug_by_fix_duration_1.bugByFixDurationChart.buildOption(bugs);
67
+ const data = option.series[0].data;
68
+ const categories = option.xAxis.data;
69
+ const idx = categories.indexOf('≤1天');
70
+ expect(idx).toBeGreaterThanOrEqual(0);
71
+ expect(data[idx]).toBe(1);
72
+ });
73
+ it('3天关闭归入"2-3天"桶', () => {
74
+ const created = '2026-03-01T00:00:00Z';
75
+ const closed = '2026-03-04T00:00:00Z'; // 3天整
76
+ const bugs = [makeIssue({ created_time: created, closed_time: closed })];
77
+ const option = bug_by_fix_duration_1.bugByFixDurationChart.buildOption(bugs);
78
+ const data = option.series[0].data;
79
+ const categories = option.xAxis.data;
80
+ const idx = categories.indexOf('2-3天');
81
+ expect(idx).toBeGreaterThanOrEqual(0);
82
+ expect(data[idx]).toBe(1);
83
+ });
84
+ it('空数组时所有桶数量均为0', () => {
85
+ const option = bug_by_fix_duration_1.bugByFixDurationChart.buildOption([]);
86
+ const data = option.series[0].data;
87
+ expect(data.every((v) => v === 0)).toBe(true);
88
+ });
89
+ });
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const bug_open_priority_heatmap_1 = require("../bug-open-priority-heatmap");
4
+ function makeIssue(assignee, severityName, statusName, deleted = false) {
5
+ return {
6
+ assigned_user: {
7
+ id: 1,
8
+ name: '',
9
+ nick_name: assignee,
10
+ user_id: '',
11
+ user_num_id: 1,
12
+ first_name: '',
13
+ },
14
+ severity: { id: 1, name: severityName },
15
+ status: { id: 5, name: statusName },
16
+ deleted,
17
+ new_custom_fields: [],
18
+ };
19
+ }
20
+ describe('bugOpenPriorityHeatmapChart', () => {
21
+ it('标题应为"未关闭 Bug 优先级分布"', () => {
22
+ expect(bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.title).toBe('未关闭 Bug 优先级分布');
23
+ });
24
+ it('过滤掉已关闭的 bug', () => {
25
+ const bugs = [
26
+ makeIssue('张三', '重要', '已关闭'),
27
+ makeIssue('张三', '严重', '进行中'),
28
+ ];
29
+ const option = bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.buildOption(bugs);
30
+ const seriesData = option.series[0].data;
31
+ const total = seriesData.reduce((sum, d) => sum + d[2], 0);
32
+ expect(total).toBe(1);
33
+ });
34
+ it('过滤掉 deleted=true 的 bug', () => {
35
+ const bugs = [
36
+ makeIssue('张三', '重要', '进行中', true),
37
+ makeIssue('李四', '一般', '新问题', false),
38
+ ];
39
+ const option = bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.buildOption(bugs);
40
+ const seriesData = option.series[0].data;
41
+ const total = seriesData.reduce((sum, d) => sum + d[2], 0);
42
+ expect(total).toBe(1);
43
+ });
44
+ it('X 轴严重程度按 一般→重要→严重 顺序排列', () => {
45
+ const bugs = [];
46
+ const option = bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.buildOption(bugs);
47
+ const xData = option.xAxis.data;
48
+ expect(xData.indexOf('一般')).toBeLessThan(xData.indexOf('重要'));
49
+ expect(xData.indexOf('重要')).toBeLessThan(xData.indexOf('严重'));
50
+ });
51
+ it('visualMap max 至少为 1(防止空数据时 min===max===0)', () => {
52
+ const option = bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.buildOption([]);
53
+ expect(option.visualMap.max).toBeGreaterThanOrEqual(1);
54
+ });
55
+ it('未知 severity 值放 X 轴末尾', () => {
56
+ const bugs = [makeIssue('张三', '未知等级', '进行中')];
57
+ const option = bug_open_priority_heatmap_1.bugOpenPriorityHeatmapChart.buildOption(bugs);
58
+ const xData = option.xAxis.data;
59
+ expect(xData[xData.length - 1]).toBe('未知等级');
60
+ });
61
+ });
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugByAssigneeChart: ChartModule;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugByAssigneeChart = void 0;
4
+ exports.bugByAssigneeChart = {
5
+ title: '修复人 Bug 数量',
6
+ buildOption(bugs) {
7
+ const countMap = new Map();
8
+ bugs.forEach((bug) => {
9
+ const name = bug.developer?.nick_name || '未设置';
10
+ countMap.set(name, (countMap.get(name) || 0) + 1);
11
+ });
12
+ const sorted = Array.from(countMap.entries()).sort((a, b) => b[1] - a[1]);
13
+ const names = sorted.map(([name]) => name);
14
+ const values = sorted.map(([, value]) => value);
15
+ return {
16
+ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
17
+ grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
18
+ xAxis: { type: 'value' },
19
+ yAxis: { type: 'category', data: names, inverse: true },
20
+ series: [
21
+ {
22
+ type: 'bar',
23
+ data: values,
24
+ label: { show: true, position: 'right' },
25
+ itemStyle: { color: '#5470c6' },
26
+ },
27
+ ],
28
+ };
29
+ },
30
+ };
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugByDefectAnalysisChart: ChartModule;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugByDefectAnalysisChart = void 0;
4
+ exports.bugByDefectAnalysisChart = {
5
+ title: '缺陷技术分析分布',
6
+ buildOption(bugs) {
7
+ const countMap = new Map();
8
+ bugs.forEach((bug) => {
9
+ const field = bug.new_custom_fields?.find((f) => f.custom_field === 'custom_field32');
10
+ const value = field?.value || '未填写';
11
+ countMap.set(value, (countMap.get(value) || 0) + 1);
12
+ });
13
+ const data = Array.from(countMap.entries())
14
+ .sort((a, b) => b[1] - a[1])
15
+ .map(([name, value]) => ({ name, value }));
16
+ return {
17
+ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
18
+ legend: { orient: 'vertical', left: 'left', type: 'scroll' },
19
+ series: [
20
+ {
21
+ type: 'pie',
22
+ radius: ['40%', '70%'],
23
+ avoidLabelOverlap: true,
24
+ label: { show: true, formatter: '{b}: {d}%' },
25
+ data,
26
+ },
27
+ ],
28
+ };
29
+ },
30
+ };
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugByDeveloperHoursChart: ChartModule;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugByDeveloperHoursChart = void 0;
4
+ exports.bugByDeveloperHoursChart = {
5
+ title: '开发人员工时消耗',
6
+ buildOption(bugs) {
7
+ const hoursMap = new Map();
8
+ bugs.forEach((bug) => {
9
+ const name = bug.developer?.nick_name;
10
+ if (!name || name === '未设置')
11
+ return;
12
+ hoursMap.set(name, (hoursMap.get(name) ?? 0) + (bug.actual_work_hours ?? 0));
13
+ });
14
+ const sorted = Array.from(hoursMap.entries()).sort((a, b) => b[1] - a[1]);
15
+ const names = sorted.map(([name]) => name);
16
+ const values = sorted.map(([, hours]) => hours);
17
+ return {
18
+ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
19
+ grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
20
+ xAxis: { type: 'value', name: '小时' },
21
+ yAxis: { type: 'category', data: names, inverse: true },
22
+ series: [
23
+ {
24
+ type: 'bar',
25
+ data: values,
26
+ label: { show: true, position: 'right', formatter: '{c}h' },
27
+ itemStyle: { color: '#ee6666' },
28
+ },
29
+ ],
30
+ };
31
+ },
32
+ };
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugByFixDurationChart: ChartModule;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugByFixDurationChart = void 0;
4
+ const BUCKETS = ['未关闭', '≤1天', '2-3天', '4-7天', '8-14天', '>14天'];
5
+ function getDurationBucket(created, closed) {
6
+ if (!closed)
7
+ return '未关闭';
8
+ const days = Math.ceil((new Date(closed).getTime() - new Date(created).getTime()) / (1000 * 60 * 60 * 24));
9
+ if (days <= 1)
10
+ return '≤1天';
11
+ if (days <= 3)
12
+ return '2-3天';
13
+ if (days <= 7)
14
+ return '4-7天';
15
+ if (days <= 14)
16
+ return '8-14天';
17
+ return '>14天';
18
+ }
19
+ exports.bugByFixDurationChart = {
20
+ title: '修复周期分布',
21
+ buildOption(bugs) {
22
+ const countMap = new Map(BUCKETS.map((b) => [b, 0]));
23
+ bugs.forEach((bug) => {
24
+ const bucket = getDurationBucket(bug.created_time, bug.closed_time);
25
+ countMap.set(bucket, (countMap.get(bucket) ?? 0) + 1);
26
+ });
27
+ const values = BUCKETS.map((b) => countMap.get(b) ?? 0);
28
+ return {
29
+ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
30
+ grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
31
+ xAxis: {
32
+ type: 'category',
33
+ data: [...BUCKETS],
34
+ },
35
+ yAxis: { type: 'value', minInterval: 1 },
36
+ series: [
37
+ {
38
+ type: 'bar',
39
+ data: values,
40
+ label: { show: true, position: 'top' },
41
+ itemStyle: { color: '#fac858' },
42
+ },
43
+ ],
44
+ };
45
+ },
46
+ };
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugByModuleChart: ChartModule;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugByModuleChart = void 0;
4
+ exports.bugByModuleChart = {
5
+ title: '模块 Bug 分布',
6
+ buildOption(bugs) {
7
+ const countMap = new Map();
8
+ bugs.forEach((bug) => {
9
+ const name = bug.module?.name || '未分配模块';
10
+ countMap.set(name, (countMap.get(name) || 0) + 1);
11
+ });
12
+ const sorted = Array.from(countMap.entries()).sort((a, b) => b[1] - a[1]);
13
+ const names = sorted.map(([name]) => name);
14
+ const values = sorted.map(([, value]) => value);
15
+ return {
16
+ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
17
+ grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
18
+ xAxis: {
19
+ type: 'category',
20
+ data: names,
21
+ axisLabel: { rotate: 30, overflow: 'truncate', width: 80 },
22
+ },
23
+ yAxis: { type: 'value' },
24
+ series: [
25
+ {
26
+ type: 'bar',
27
+ data: values,
28
+ label: { show: true, position: 'top' },
29
+ itemStyle: { color: '#91cc75' },
30
+ },
31
+ ],
32
+ };
33
+ },
34
+ };
@@ -0,0 +1,2 @@
1
+ import { ChartModule } from '../chart.interface';
2
+ export declare const bugOpenPriorityHeatmapChart: ChartModule;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bugOpenPriorityHeatmapChart = void 0;
4
+ const SEVERITY_ORDER = ['一般', '重要', '严重'];
5
+ exports.bugOpenPriorityHeatmapChart = {
6
+ title: '未关闭 Bug 优先级分布',
7
+ buildOption(bugs) {
8
+ const openBugs = bugs.filter((b) => !b.deleted && b.status?.name !== '已关闭');
9
+ const unknownSeverities = [
10
+ ...new Set(openBugs.map((b) => b.severity?.name ?? '').filter((s) => s && !SEVERITY_ORDER.includes(s))),
11
+ ];
12
+ const xCategories = [...SEVERITY_ORDER, ...unknownSeverities];
13
+ const assigneeCounts = new Map();
14
+ openBugs.forEach((b) => {
15
+ const name = b.assigned_user?.nick_name ?? '未分配';
16
+ assigneeCounts.set(name, (assigneeCounts.get(name) ?? 0) + 1);
17
+ });
18
+ const yCategories = Array.from(assigneeCounts.entries())
19
+ .sort((a, b) => b[1] - a[1])
20
+ .map(([name]) => name);
21
+ const heatMap = new Map();
22
+ openBugs.forEach((b) => {
23
+ const xKey = b.severity?.name ?? '';
24
+ const yKey = b.assigned_user?.nick_name ?? '未分配';
25
+ const key = `${xKey}|${yKey}`;
26
+ heatMap.set(key, (heatMap.get(key) ?? 0) + 1);
27
+ });
28
+ const data = [];
29
+ xCategories.forEach((x, xi) => {
30
+ yCategories.forEach((y, yi) => {
31
+ const v = heatMap.get(`${x}|${y}`) ?? 0;
32
+ data.push([xi, yi, v]);
33
+ });
34
+ });
35
+ const maxValue = data.reduce((max, d) => Math.max(max, d[2]), 0);
36
+ return {
37
+ tooltip: {
38
+ position: 'top',
39
+ formatter: (params) => `${yCategories[params.data[1]]} / ${xCategories[params.data[0]]}: ${params.data[2]} 个`,
40
+ },
41
+ grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true },
42
+ xAxis: {
43
+ type: 'category',
44
+ data: xCategories,
45
+ splitArea: { show: true },
46
+ },
47
+ yAxis: {
48
+ type: 'category',
49
+ data: yCategories,
50
+ splitArea: { show: true },
51
+ },
52
+ visualMap: {
53
+ min: 0,
54
+ max: Math.max(1, maxValue),
55
+ calculable: true,
56
+ orient: 'horizontal',
57
+ left: 'center',
58
+ bottom: '0%',
59
+ inRange: { color: ['#ffffff', '#ee6666'] },
60
+ },
61
+ series: [
62
+ {
63
+ type: 'heatmap',
64
+ data,
65
+ label: {
66
+ show: true,
67
+ formatter: (params) => params.data[2] > 0 ? String(params.data[2]) : '',
68
+ },
69
+ emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' } },
70
+ },
71
+ ],
72
+ };
73
+ },
74
+ };
@@ -0,0 +1,21 @@
1
+ import { IssueItem } from '../types';
2
+ import { ChartModule } from './chart.interface';
3
+ export interface ReportMeta {
4
+ iterationNames: string[];
5
+ terminalTypes: string[];
6
+ totalCount: number;
7
+ generatedAt: string;
8
+ }
9
+ /**
10
+ * 生成 HTML 报告并写入文件,返回文件路径。
11
+ * @param bugs Bug 列表
12
+ * @param charts 图表模块列表
13
+ * @param meta 报告元数据
14
+ * @param outputDir 输出目录,若不指定则使用系统 cache 目录
15
+ * 若文件写入失败,将 HTML 内容通过 logger 输出作为兜底,然后抛出错误。
16
+ */
17
+ export declare function renderReport(bugs: IssueItem[], charts: ChartModule[], meta: ReportMeta, outputDir?: string): string;
18
+ /**
19
+ * 自动在系统默认浏览器中打开文件
20
+ */
21
+ export declare function openInBrowser(filePath: string): void;