@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
|
@@ -1,9 +1,14 @@
|
|
|
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.workHourCommand = workHourCommand;
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
4
8
|
const holidays_1 = require("../config/holidays");
|
|
5
9
|
const business_service_1 = require("../services/business.service");
|
|
6
10
|
const config_loader_1 = require("../utils/config-loader");
|
|
11
|
+
const console_1 = require("../utils/console");
|
|
7
12
|
const csv_writer_1 = require("../utils/csv-writer");
|
|
8
13
|
const logger_1 = require("../utils/logger");
|
|
9
14
|
/**
|
|
@@ -16,30 +21,29 @@ function roundToTwo(num) {
|
|
|
16
21
|
* 查询年度工时数据
|
|
17
22
|
*/
|
|
18
23
|
async function queryWorkHourReportData(businessService, projectId, roleIds, targetYear) {
|
|
19
|
-
const
|
|
24
|
+
const members = await businessService.getMembersByRoleIds(projectId, roleIds);
|
|
25
|
+
const userIds = members.map((member) => member.user_id);
|
|
26
|
+
const stats = await businessService.getAllWorkHourStats(projectId, userIds, `${targetYear}-01-01`, `${targetYear}-12-31`);
|
|
27
|
+
// 创建用户ID到成员信息的映射
|
|
28
|
+
const userIdToMember = new Map();
|
|
29
|
+
members.forEach((member) => {
|
|
30
|
+
userIdToMember.set(member.user_id, member);
|
|
31
|
+
});
|
|
20
32
|
const allUserStats = [];
|
|
21
33
|
const allTypes = new Set();
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
logger_1.logger.warn(`角色ID ${roleId} 未找到用户,跳过`);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const roleName = roleMembers[0].role_name;
|
|
29
|
-
allMembers.push(...roleMembers);
|
|
30
|
-
const roleUserIds = roleMembers.map((member) => member.user_id);
|
|
31
|
-
const stats = await businessService.getAllWorkHourStats(projectId, roleUserIds, `${targetYear}-01-01`, `${targetYear}-12-31`);
|
|
32
|
-
stats.userStats.forEach((userStat) => {
|
|
34
|
+
stats.userStats.forEach((userStat) => {
|
|
35
|
+
const member = userIdToMember.get(userStat.userId);
|
|
36
|
+
if (member) {
|
|
33
37
|
allUserStats.push({
|
|
34
38
|
...userStat,
|
|
35
|
-
roleName,
|
|
36
|
-
roleId,
|
|
39
|
+
roleName: member.role_name,
|
|
40
|
+
roleId: member.role_id,
|
|
37
41
|
});
|
|
38
42
|
userStat.domainStats.forEach((domainStat) => {
|
|
39
43
|
allTypes.add(domainStat.type);
|
|
40
44
|
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
43
47
|
allUserStats.sort((a, b) => {
|
|
44
48
|
if (a.roleId !== b.roleId) {
|
|
45
49
|
return a.roleId - b.roleId;
|
|
@@ -48,37 +52,10 @@ async function queryWorkHourReportData(businessService, projectId, roleIds, targ
|
|
|
48
52
|
});
|
|
49
53
|
const expectedWorkdays = (0, holidays_1.calculateExpectedWorkdays)(targetYear);
|
|
50
54
|
const expectedHoursPerPerson = expectedWorkdays * 8;
|
|
51
|
-
const totalExpectedHours = expectedHoursPerPerson *
|
|
55
|
+
const totalExpectedHours = expectedHoursPerPerson * members.length;
|
|
52
56
|
const totalHours = roundToTwo(allUserStats.reduce((sum, userStat) => sum + userStat.totalHours, 0));
|
|
53
|
-
const totalEntries = allUserStats.reduce((sum, userStat) => sum + userStat.domainStats.reduce((s, d) => s + d.workHours.length, 0), 0);
|
|
54
57
|
const userStatsData = [];
|
|
55
|
-
|
|
56
|
-
const typeTotals = {};
|
|
57
|
-
allTypes.forEach((type) => {
|
|
58
|
-
typeTotals[type] = 0;
|
|
59
|
-
});
|
|
60
|
-
let currentRoleId = null;
|
|
61
|
-
let currentRoleName = '';
|
|
62
|
-
const roleSubtotalStats = {};
|
|
63
|
-
allUserStats.forEach((userStat, index) => {
|
|
64
|
-
if (currentRoleId !== null && currentRoleId !== userStat.roleId) {
|
|
65
|
-
const subtotalDomainStats = {};
|
|
66
|
-
let subtotal = 0;
|
|
67
|
-
allTypes.forEach((type) => {
|
|
68
|
-
subtotalDomainStats[type] = roleSubtotalStats[type] || 0;
|
|
69
|
-
subtotal = roundToTwo(subtotal + (roleSubtotalStats[type] || 0));
|
|
70
|
-
});
|
|
71
|
-
roleSubtotals.push({
|
|
72
|
-
roleName: currentRoleName,
|
|
73
|
-
domainStats: subtotalDomainStats,
|
|
74
|
-
total: subtotal,
|
|
75
|
-
});
|
|
76
|
-
Object.keys(roleSubtotalStats).forEach((key) => {
|
|
77
|
-
roleSubtotalStats[key] = 0;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
currentRoleId = userStat.roleId;
|
|
81
|
-
currentRoleName = userStat.roleName;
|
|
58
|
+
allUserStats.forEach((userStat) => {
|
|
82
59
|
const domainStats = {};
|
|
83
60
|
allTypes.forEach((type) => {
|
|
84
61
|
domainStats[type] = 0;
|
|
@@ -87,94 +64,54 @@ async function queryWorkHourReportData(businessService, projectId, roleIds, targ
|
|
|
87
64
|
userStat.domainStats.forEach((domainStat) => {
|
|
88
65
|
domainStats[domainStat.type] = domainStat.totalHours;
|
|
89
66
|
userTotal = roundToTwo(userTotal + domainStat.totalHours);
|
|
90
|
-
typeTotals[domainStat.type] = roundToTwo(typeTotals[domainStat.type] + domainStat.totalHours);
|
|
91
|
-
if (!roleSubtotalStats[domainStat.type]) {
|
|
92
|
-
roleSubtotalStats[domainStat.type] = 0;
|
|
93
|
-
}
|
|
94
|
-
roleSubtotalStats[domainStat.type] = roundToTwo(roleSubtotalStats[domainStat.type] + domainStat.totalHours);
|
|
95
67
|
});
|
|
96
68
|
userStatsData.push({
|
|
97
69
|
userName: userStat.userName,
|
|
98
70
|
roleName: userStat.roleName,
|
|
99
|
-
roleId: userStat.roleId,
|
|
100
71
|
domainStats,
|
|
101
72
|
total: userTotal,
|
|
102
73
|
});
|
|
103
|
-
if (index === allUserStats.length - 1) {
|
|
104
|
-
const subtotalDomainStats = {};
|
|
105
|
-
let subtotal = 0;
|
|
106
|
-
allTypes.forEach((type) => {
|
|
107
|
-
subtotalDomainStats[type] = roleSubtotalStats[type] || 0;
|
|
108
|
-
subtotal = roundToTwo(subtotal + (roleSubtotalStats[type] || 0));
|
|
109
|
-
});
|
|
110
|
-
roleSubtotals.push({
|
|
111
|
-
roleName: currentRoleName,
|
|
112
|
-
domainStats: subtotalDomainStats,
|
|
113
|
-
total: subtotal,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
const grandTotalDomainStats = {};
|
|
118
|
-
let grandTotal = 0;
|
|
119
|
-
allTypes.forEach((type) => {
|
|
120
|
-
grandTotalDomainStats[type] = typeTotals[type];
|
|
121
|
-
grandTotal = roundToTwo(grandTotal + typeTotals[type]);
|
|
122
74
|
});
|
|
123
75
|
return {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
grandTotal: {
|
|
135
|
-
domainStats: grandTotalDomainStats,
|
|
136
|
-
total: grandTotal,
|
|
137
|
-
},
|
|
76
|
+
title: `${targetYear}年工时统计`,
|
|
77
|
+
roleNames: Array.from(new Set(members.map((m) => m.role_name))),
|
|
78
|
+
totalMap: [
|
|
79
|
+
['统计人数', `${members.length} 人`],
|
|
80
|
+
['应计工日', `${expectedWorkdays} 天`],
|
|
81
|
+
['应计工时', `${totalExpectedHours} 小时`],
|
|
82
|
+
['实际工时', `${totalHours} 小时`],
|
|
83
|
+
['工时完成率', `${((totalHours / totalExpectedHours) * 100).toFixed(2)}%`],
|
|
84
|
+
],
|
|
85
|
+
list: userStatsData,
|
|
138
86
|
};
|
|
139
87
|
}
|
|
140
88
|
/**
|
|
141
89
|
* 控制台输出工时统计
|
|
142
90
|
*/
|
|
143
91
|
function outputConsole(data) {
|
|
144
|
-
|
|
145
|
-
logger_1.logger.info('='.repeat(80));
|
|
146
|
-
logger_1.logger.info(`统计人数: ${data.memberCount} 人`);
|
|
147
|
-
logger_1.logger.info(`应计工作日: ${data.expectedWorkdays} 天`);
|
|
148
|
-
logger_1.logger.info(`应计工时: ${data.expectedHours} 小时`);
|
|
149
|
-
logger_1.logger.info(`实际工时: ${data.actualHours} 小时`);
|
|
150
|
-
logger_1.logger.info(`工时完成率: ${data.completionRate.toFixed(2)}%`);
|
|
151
|
-
logger_1.logger.info('='.repeat(80));
|
|
92
|
+
(0, console_1.consoleTotal)(data);
|
|
152
93
|
// 构建表格数据
|
|
153
94
|
const tableData = {};
|
|
154
|
-
data.
|
|
95
|
+
data.list.forEach((userStat) => {
|
|
155
96
|
const row = {
|
|
156
97
|
...userStat.domainStats,
|
|
157
98
|
合计: userStat.total,
|
|
158
99
|
};
|
|
159
100
|
tableData[userStat.userName] = row;
|
|
160
101
|
});
|
|
161
|
-
// 添加总计行
|
|
162
|
-
tableData['总计'] = {
|
|
163
|
-
...data.grandTotal.domainStats,
|
|
164
|
-
合计: data.grandTotal.total,
|
|
165
|
-
};
|
|
166
102
|
logger_1.logger.table(tableData);
|
|
167
103
|
}
|
|
168
104
|
/**
|
|
169
105
|
* CSV 文件输出
|
|
170
106
|
*/
|
|
171
|
-
function outputCsv(
|
|
172
|
-
const domains = Object.keys(
|
|
107
|
+
function outputCsv(list, targetYear) {
|
|
108
|
+
const domains = Object.keys(list[0].domainStats);
|
|
173
109
|
const csvLines = [];
|
|
174
|
-
|
|
175
|
-
|
|
110
|
+
const headerRow = ['用户', '角色', ...domains, '合计'];
|
|
111
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)(headerRow));
|
|
112
|
+
list.forEach((userStat) => {
|
|
176
113
|
const domainValues = domains.map((domain) => userStat.domainStats[domain] || 0);
|
|
177
|
-
csvLines.push(
|
|
114
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)([userStat.userName, userStat.roleName, ...domainValues, userStat.total]));
|
|
178
115
|
});
|
|
179
116
|
const filename = `work-hour-${targetYear}.csv`;
|
|
180
117
|
(0, csv_writer_1.writeCsvFile)(filename, csvLines, logger_1.logger);
|
|
@@ -189,23 +126,19 @@ function outputJson(data) {
|
|
|
189
126
|
* work-hour 命令入口
|
|
190
127
|
*/
|
|
191
128
|
async function workHourCommand(year, cliOptions = {}) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
else if (outputFormat === 'json') {
|
|
204
|
-
outputJson(reportData);
|
|
205
|
-
}
|
|
129
|
+
const spinner = (0, ora_1.default)('正在查询数据...').start();
|
|
130
|
+
const targetYear = year || new Date().getFullYear().toString();
|
|
131
|
+
const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
132
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
133
|
+
const reportData = await queryWorkHourReportData(businessService, projectId, roleIds, targetYear);
|
|
134
|
+
spinner.stop();
|
|
135
|
+
if (outputFormat === 'console') {
|
|
136
|
+
outputConsole(reportData);
|
|
137
|
+
}
|
|
138
|
+
else if (outputFormat === 'csv') {
|
|
139
|
+
outputCsv(reportData.list, targetYear);
|
|
206
140
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
process.exit(1);
|
|
141
|
+
else if (outputFormat === 'json') {
|
|
142
|
+
outputJson(reportData.list);
|
|
210
143
|
}
|
|
211
144
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AllWorkHourStats, HuaweiCloudConfig, IssueItem, IssueItemV2, IterationInfo, ProjectMember, ProjectRole, WorkHourStats, WorkProgressStats } from '../types';
|
|
2
|
-
import { ApiService } from './api.service';
|
|
3
2
|
/**
|
|
4
3
|
* 业务服务类
|
|
5
4
|
* 提供面向业务场景的高级操作,封装ApiService的底层调用
|
|
@@ -7,24 +6,28 @@ import { ApiService } from './api.service';
|
|
|
7
6
|
export declare class BusinessService {
|
|
8
7
|
private apiService;
|
|
9
8
|
constructor(config: HuaweiCloudConfig);
|
|
10
|
-
/**
|
|
11
|
-
* 获取底层ApiService实例
|
|
12
|
-
* 用于需要直接访问API服务的场景
|
|
13
|
-
*/
|
|
14
|
-
getApiService(): ApiService;
|
|
15
9
|
/**
|
|
16
10
|
* 通过角色ID获取项目成员
|
|
17
11
|
* @param projectId 项目ID
|
|
18
|
-
* @param
|
|
12
|
+
* @param roleIds 角色ID列表
|
|
19
13
|
* @returns 指定角色的成员列表
|
|
20
14
|
*/
|
|
21
|
-
|
|
15
|
+
getMembersByRoleIds(projectId: string, roleIds: number[]): Promise<ProjectMember[]>;
|
|
22
16
|
/**
|
|
23
17
|
* 获取项目中的所有角色列表(去重)
|
|
24
18
|
* @param projectId 项目ID
|
|
25
19
|
* @returns 项目中的所有角色列表
|
|
26
20
|
*/
|
|
27
21
|
getProjectRoles(projectId: string): Promise<ProjectRole[]>;
|
|
22
|
+
/**
|
|
23
|
+
* 获取项目的迭代列表
|
|
24
|
+
* @param projectId 项目ID
|
|
25
|
+
* @param options 可选参数
|
|
26
|
+
* @returns 迭代列表
|
|
27
|
+
*/
|
|
28
|
+
getIterations(projectId: string, options?: {
|
|
29
|
+
limit: number;
|
|
30
|
+
}): Promise<IterationInfo[]>;
|
|
28
31
|
/**
|
|
29
32
|
* 获取指定日期之后的迭代列表
|
|
30
33
|
* @param projectId 项目ID
|
|
@@ -40,14 +43,6 @@ export declare class BusinessService {
|
|
|
40
43
|
* @returns Task类型的工作项列表
|
|
41
44
|
*/
|
|
42
45
|
getWorkloadByIterationsAndUsers(projectId: string, iterationIds: number[], userIds: string[]): Promise<IssueItem[]>;
|
|
43
|
-
/**
|
|
44
|
-
* 根据迭代ID和用户ID列表查询工作量列表(仅Task和Story)
|
|
45
|
-
* @param projectId 项目ID
|
|
46
|
-
* @param iterationId 迭代ID
|
|
47
|
-
* @param userIds 用户ID列表
|
|
48
|
-
* @returns Task和Story类型的工作项列表
|
|
49
|
-
*/
|
|
50
|
-
getWorkloadByIterationAndUsers(projectId: string, iterationId: number, userIds: string[]): Promise<IssueItem[]>;
|
|
51
46
|
/**
|
|
52
47
|
* 统计工作项进度信息
|
|
53
48
|
* @param issues 工作项列表
|
|
@@ -70,12 +65,12 @@ export declare class BusinessService {
|
|
|
70
65
|
*/
|
|
71
66
|
getIterationsByTitles(projectId: string, iterationTitles: string[]): Promise<IterationInfo[]>;
|
|
72
67
|
/**
|
|
73
|
-
*
|
|
68
|
+
* 根据迭代信息获取所有 Story
|
|
74
69
|
* @param projectId 项目ID
|
|
75
|
-
* @param
|
|
70
|
+
* @param iterations 迭代信息列表
|
|
76
71
|
* @returns Story 类型的工作项列表
|
|
77
72
|
*/
|
|
78
|
-
|
|
73
|
+
getStoriesByIterations(projectId: string, iterations: IterationInfo[], userIds?: string[]): Promise<IssueItem[]>;
|
|
79
74
|
/**
|
|
80
75
|
* 查询指定用户在指定时间段内的所有工时统计(按人和领域分组)
|
|
81
76
|
* @param projectId 项目ID
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BusinessService = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
4
5
|
const api_service_1 = require("./api.service");
|
|
5
6
|
/**
|
|
6
7
|
* 业务服务类
|
|
@@ -10,27 +11,21 @@ class BusinessService {
|
|
|
10
11
|
constructor(config) {
|
|
11
12
|
this.apiService = new api_service_1.ApiService(config);
|
|
12
13
|
}
|
|
13
|
-
/**
|
|
14
|
-
* 获取底层ApiService实例
|
|
15
|
-
* 用于需要直接访问API服务的场景
|
|
16
|
-
*/
|
|
17
|
-
getApiService() {
|
|
18
|
-
return this.apiService;
|
|
19
|
-
}
|
|
20
14
|
// 业务操作方法将在后续添加
|
|
21
15
|
/**
|
|
22
16
|
* 通过角色ID获取项目成员
|
|
23
17
|
* @param projectId 项目ID
|
|
24
|
-
* @param
|
|
18
|
+
* @param roleIds 角色ID列表
|
|
25
19
|
* @returns 指定角色的成员列表
|
|
26
20
|
*/
|
|
27
|
-
async
|
|
21
|
+
async getMembersByRoleIds(projectId, roleIds) {
|
|
28
22
|
const membersResponse = await this.apiService.getMembers(projectId);
|
|
29
23
|
if (!membersResponse.success) {
|
|
30
24
|
throw new Error(`获取成员列表失败: ${membersResponse.error || '未知错误'}`);
|
|
31
25
|
}
|
|
32
26
|
const allMembers = membersResponse.data?.members || [];
|
|
33
|
-
|
|
27
|
+
const roleIdsSet = new Set(roleIds);
|
|
28
|
+
return allMembers.filter((member) => roleIdsSet.has(member.role_id));
|
|
34
29
|
}
|
|
35
30
|
/**
|
|
36
31
|
* 获取项目中的所有角色列表(去重)
|
|
@@ -71,6 +66,24 @@ class BusinessService {
|
|
|
71
66
|
// 转换为数组并按 role_id 排序
|
|
72
67
|
return Array.from(rolesMap.values()).sort((a, b) => a.role_id - b.role_id);
|
|
73
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取项目的迭代列表
|
|
71
|
+
* @param projectId 项目ID
|
|
72
|
+
* @param options 可选参数
|
|
73
|
+
* @returns 迭代列表
|
|
74
|
+
*/
|
|
75
|
+
async getIterations(projectId, options = { limit: 12 }) {
|
|
76
|
+
const iterationsResponse = await this.apiService.getIterations(projectId, {
|
|
77
|
+
include_deleted: false,
|
|
78
|
+
});
|
|
79
|
+
if (!iterationsResponse.success) {
|
|
80
|
+
throw new Error(`获取迭代列表失败: ${iterationsResponse.error || '未知错误'}`);
|
|
81
|
+
}
|
|
82
|
+
const list = (iterationsResponse.data?.iterations?.length || 0) > options.limit
|
|
83
|
+
? iterationsResponse.data?.iterations?.slice(0, options.limit) || []
|
|
84
|
+
: iterationsResponse.data?.iterations || [];
|
|
85
|
+
return list;
|
|
86
|
+
}
|
|
74
87
|
/**
|
|
75
88
|
* 获取指定日期之后的迭代列表
|
|
76
89
|
* @param projectId 项目ID
|
|
@@ -89,7 +102,7 @@ class BusinessService {
|
|
|
89
102
|
// 过滤出在目标日期正在进行中的迭代
|
|
90
103
|
return iterations.filter((iteration) => {
|
|
91
104
|
// 检查迭代状态是否为进行中 (1)
|
|
92
|
-
if (iteration.status !==
|
|
105
|
+
if (iteration.status !== types_1.IterationStatus.IN_PROGRESS) {
|
|
93
106
|
return false;
|
|
94
107
|
}
|
|
95
108
|
// 检查目标日期是否在迭代时间范围内
|
|
@@ -121,27 +134,6 @@ class BusinessService {
|
|
|
121
134
|
}
|
|
122
135
|
return issuesResponse.data?.issues || [];
|
|
123
136
|
}
|
|
124
|
-
/**
|
|
125
|
-
* 根据迭代ID和用户ID列表查询工作量列表(仅Task和Story)
|
|
126
|
-
* @param projectId 项目ID
|
|
127
|
-
* @param iterationId 迭代ID
|
|
128
|
-
* @param userIds 用户ID列表
|
|
129
|
-
* @returns Task和Story类型的工作项列表
|
|
130
|
-
*/
|
|
131
|
-
async getWorkloadByIterationAndUsers(projectId, iterationId, userIds) {
|
|
132
|
-
const issuesResponse = await this.apiService.getIssues(projectId, {
|
|
133
|
-
iteration_ids: [iterationId],
|
|
134
|
-
tracker_ids: [2, 7], // 2=Task(任务), 7=Story
|
|
135
|
-
assigned_ids: userIds,
|
|
136
|
-
include_deleted: false,
|
|
137
|
-
limit: 100,
|
|
138
|
-
offset: 0,
|
|
139
|
-
});
|
|
140
|
-
if (!issuesResponse.success) {
|
|
141
|
-
throw new Error(`获取工作项列表失败: ${issuesResponse.error || '未知错误'}`);
|
|
142
|
-
}
|
|
143
|
-
return issuesResponse.data?.issues || [];
|
|
144
|
-
}
|
|
145
137
|
/**
|
|
146
138
|
* 统计工作项进度信息
|
|
147
139
|
* @param issues 工作项列表
|
|
@@ -256,14 +248,12 @@ class BusinessService {
|
|
|
256
248
|
return iterations.filter((iteration) => iterationTitles.includes(iteration.name));
|
|
257
249
|
}
|
|
258
250
|
/**
|
|
259
|
-
*
|
|
251
|
+
* 根据迭代信息获取所有 Story
|
|
260
252
|
* @param projectId 项目ID
|
|
261
|
-
* @param
|
|
253
|
+
* @param iterations 迭代信息列表
|
|
262
254
|
* @returns Story 类型的工作项列表
|
|
263
255
|
*/
|
|
264
|
-
async
|
|
265
|
-
// Step 1: 根据标题获取迭代信息
|
|
266
|
-
const iterations = await this.getIterationsByTitles(projectId, iterationTitles);
|
|
256
|
+
async getStoriesByIterations(projectId, iterations, userIds) {
|
|
267
257
|
if (iterations.length === 0) {
|
|
268
258
|
return [];
|
|
269
259
|
}
|
|
@@ -277,6 +267,7 @@ class BusinessService {
|
|
|
277
267
|
while (hasMore) {
|
|
278
268
|
const issuesResponse = await this.apiService.getIssues(projectId, {
|
|
279
269
|
iteration_ids: iterationIds,
|
|
270
|
+
assigned_ids: userIds,
|
|
280
271
|
tracker_ids: [7], // 7=Story
|
|
281
272
|
include_deleted: false,
|
|
282
273
|
limit: pageSize,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -305,6 +305,12 @@ export interface ListProjectIterationsV4Request {
|
|
|
305
305
|
updated_time_interval?: string;
|
|
306
306
|
include_deleted?: boolean;
|
|
307
307
|
}
|
|
308
|
+
export declare enum IterationStatus {
|
|
309
|
+
OPEN = "open",// 老数据默认状态
|
|
310
|
+
NOT_STARTED = "0",// 未启动
|
|
311
|
+
IN_PROGRESS = "1",// 进行中
|
|
312
|
+
COMPLETED = "2"
|
|
313
|
+
}
|
|
308
314
|
export interface IterationInfo {
|
|
309
315
|
begin_time: string;
|
|
310
316
|
deleted: boolean;
|
|
@@ -312,7 +318,7 @@ export interface IterationInfo {
|
|
|
312
318
|
end_time: string;
|
|
313
319
|
id: number;
|
|
314
320
|
name: string;
|
|
315
|
-
status:
|
|
321
|
+
status: IterationStatus;
|
|
316
322
|
updated_time: number;
|
|
317
323
|
}
|
|
318
324
|
export interface ListProjectIterationsV4Response {
|
|
@@ -539,14 +545,6 @@ export declare enum ConfigKey {
|
|
|
539
545
|
PROJECT_ID = "PROJECT_ID",
|
|
540
546
|
ROLE_ID = "ROLE_ID"
|
|
541
547
|
}
|
|
542
|
-
/**
|
|
543
|
-
* 不可变配置键(只能通过 config 命令设置)
|
|
544
|
-
*/
|
|
545
|
-
export declare const IMMUTABLE_CONFIG_KEYS: ConfigKey[];
|
|
546
|
-
/**
|
|
547
|
-
* 可变配置键(可以通过命令行参数覆盖)
|
|
548
|
-
*/
|
|
549
|
-
export declare const MUTABLE_CONFIG_KEYS: ConfigKey[];
|
|
550
548
|
/**
|
|
551
549
|
* 类型安全的配置映射
|
|
552
550
|
*/
|
|
@@ -560,11 +558,13 @@ export type ConfigMap = {
|
|
|
560
558
|
[ConfigKey.PROJECT_ID]: string;
|
|
561
559
|
[ConfigKey.ROLE_ID]: string;
|
|
562
560
|
};
|
|
563
|
-
/**
|
|
564
|
-
* 部分配置映射(用于读取配置文件)
|
|
565
|
-
*/
|
|
566
|
-
export type PartialConfigMap = Partial<ConfigMap>;
|
|
567
561
|
/**
|
|
568
562
|
* 输出格式类型
|
|
569
563
|
*/
|
|
570
564
|
export type OutputFormat = 'console' | 'csv' | 'json';
|
|
565
|
+
export interface ConsoleTotal<T> {
|
|
566
|
+
title: string;
|
|
567
|
+
roleNames: string[];
|
|
568
|
+
totalMap: [string, string | (() => string)][];
|
|
569
|
+
list: T[];
|
|
570
|
+
}
|
package/dist/types/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ConfigKey = exports.DefectAnalysisType = exports.IterationStatus = void 0;
|
|
4
|
+
var IterationStatus;
|
|
5
|
+
(function (IterationStatus) {
|
|
6
|
+
IterationStatus["OPEN"] = "open";
|
|
7
|
+
IterationStatus["NOT_STARTED"] = "0";
|
|
8
|
+
IterationStatus["IN_PROGRESS"] = "1";
|
|
9
|
+
IterationStatus["COMPLETED"] = "2";
|
|
10
|
+
})(IterationStatus || (exports.IterationStatus = IterationStatus = {}));
|
|
4
11
|
/**
|
|
5
12
|
* 缺陷技术分析选项枚举
|
|
6
13
|
* custom_field32(缺陷技术分析)字段的选项值枚举定义
|
|
@@ -40,19 +47,3 @@ var ConfigKey;
|
|
|
40
47
|
// 可变配置(可以通过命令行参数覆盖)
|
|
41
48
|
ConfigKey["ROLE_ID"] = "ROLE_ID";
|
|
42
49
|
})(ConfigKey || (exports.ConfigKey = ConfigKey = {}));
|
|
43
|
-
/**
|
|
44
|
-
* 不可变配置键(只能通过 config 命令设置)
|
|
45
|
-
*/
|
|
46
|
-
exports.IMMUTABLE_CONFIG_KEYS = [
|
|
47
|
-
ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT,
|
|
48
|
-
ConfigKey.HUAWEI_CLOUD_REGION,
|
|
49
|
-
ConfigKey.HUAWEI_CLOUD_USERNAME,
|
|
50
|
-
ConfigKey.HUAWEI_CLOUD_PASSWORD,
|
|
51
|
-
ConfigKey.HUAWEI_CLOUD_DOMAIN,
|
|
52
|
-
ConfigKey.CODEARTS_BASE_URL,
|
|
53
|
-
ConfigKey.PROJECT_ID,
|
|
54
|
-
];
|
|
55
|
-
/**
|
|
56
|
-
* 可变配置键(可以通过命令行参数覆盖)
|
|
57
|
-
*/
|
|
58
|
-
exports.MUTABLE_CONFIG_KEYS = [ConfigKey.ROLE_ID];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HuaweiCloudConfig, OutputFormat
|
|
1
|
+
import { ConfigMap, HuaweiCloudConfig, OutputFormat } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* 获取全局配置文件路径
|
|
4
4
|
*/
|
|
@@ -10,18 +10,18 @@ export declare function configExists(): boolean;
|
|
|
10
10
|
/**
|
|
11
11
|
* 读取全局配置
|
|
12
12
|
*/
|
|
13
|
-
export declare function readConfig():
|
|
13
|
+
export declare function readConfig(): Partial<ConfigMap>;
|
|
14
14
|
/**
|
|
15
15
|
* 写入全局配置
|
|
16
16
|
* 支持动态配置项,自动按分组组织配置文件
|
|
17
17
|
*/
|
|
18
|
-
export declare function writeConfig(config:
|
|
18
|
+
export declare function writeConfig(config: Partial<ConfigMap>): void;
|
|
19
19
|
/**
|
|
20
20
|
* 删除全局配置
|
|
21
21
|
*/
|
|
22
22
|
export declare function deleteConfig(): void;
|
|
23
23
|
export interface CliOptions {
|
|
24
|
-
|
|
24
|
+
role?: string;
|
|
25
25
|
output?: string;
|
|
26
26
|
report?: boolean;
|
|
27
27
|
}
|
|
@@ -41,4 +41,4 @@ export declare function loadConfig(cliOptions?: CliOptions): LoadedConfig;
|
|
|
41
41
|
* 获取最终合并后的配置(用于显示)
|
|
42
42
|
* @returns 合并后的配置映射
|
|
43
43
|
*/
|
|
44
|
-
export declare function getConfig():
|
|
44
|
+
export declare function getConfig(): Partial<ConfigMap>;
|
|
@@ -181,7 +181,7 @@ const globalConfig = configExists() ? readConfig() : {};
|
|
|
181
181
|
function loadConfig(cliOptions = {}) {
|
|
182
182
|
// 命令行参数 > 全局配置
|
|
183
183
|
const projectId = globalConfig[types_1.ConfigKey.PROJECT_ID];
|
|
184
|
-
const roleIdStr = cliOptions.
|
|
184
|
+
const roleIdStr = cliOptions.role || globalConfig[types_1.ConfigKey.ROLE_ID];
|
|
185
185
|
if (!projectId) {
|
|
186
186
|
throw new Error('缺少项目 ID');
|
|
187
187
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.showLogo = showLogo;
|
|
4
|
+
exports.consoleTotal = consoleTotal;
|
|
5
|
+
exports.issueLink = issueLink;
|
|
4
6
|
const logger_1 = require("../utils/logger");
|
|
5
7
|
const LOGO_LINES = [
|
|
6
8
|
'██╗ ██╗███████╗ ██████╗ ██████╗ ███╗ ███╗',
|
|
@@ -27,3 +29,19 @@ function showLogo() {
|
|
|
27
29
|
});
|
|
28
30
|
logger_1.logger.info();
|
|
29
31
|
}
|
|
32
|
+
function consoleTotal(total) {
|
|
33
|
+
logger_1.logger.info(`${total.title} [${total.roleNames.join(', ')}]`);
|
|
34
|
+
logger_1.logger.info('='.repeat(80));
|
|
35
|
+
total.totalMap.forEach(([key, value]) => {
|
|
36
|
+
if (typeof value === 'function') {
|
|
37
|
+
logger_1.logger.info(`${key}: ${value()}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
logger_1.logger.info(`${key}: ${value}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
logger_1.logger.info('='.repeat(80));
|
|
44
|
+
}
|
|
45
|
+
function issueLink(projectId, issueId) {
|
|
46
|
+
return `https://devcloud.cn-north-4.huaweicloud.com/projectman/scrum/${projectId}/task/detail/${issueId}`;
|
|
47
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { Logger } from './logger';
|
|
2
2
|
/**
|
|
3
|
-
* CSV
|
|
3
|
+
* 构建 CSV 行
|
|
4
|
+
* @param fields CSV 字段数组
|
|
5
|
+
* @returns CSV 行字符串
|
|
4
6
|
*/
|
|
5
|
-
export declare function
|
|
7
|
+
export declare function buildCsvRow(fields: (string | number | null | undefined)[]): string;
|
|
8
|
+
/**
|
|
9
|
+
* 创建 Excel HYPERLINK 公式
|
|
10
|
+
* @param url 链接地址
|
|
11
|
+
* @param displayText 显示文本
|
|
12
|
+
* @returns Excel HYPERLINK 公式字符串
|
|
13
|
+
*/
|
|
14
|
+
export declare function createHyperlinkFormula(url: string, displayText: string): string;
|
|
6
15
|
/**
|
|
7
16
|
* 写入 CSV 文件到当前工作目录
|
|
8
17
|
* @param filename 文件名
|