@hecom/codearts 0.2.0 → 0.2.2
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 +45 -168
- package/dist/bin/cli.js +46 -28
- package/dist/commands/bug.command.d.ts +0 -2
- package/dist/commands/bug.command.js +105 -61
- package/dist/commands/config.command.d.ts +25 -0
- package/dist/commands/config.command.js +425 -78
- package/dist/commands/daily.command.d.ts +0 -5
- package/dist/commands/daily.command.js +265 -99
- package/dist/commands/work-hour.command.js +189 -131
- package/dist/services/api.service.d.ts +9 -2
- package/dist/services/api.service.js +12 -4
- package/dist/services/business.service.d.ts +21 -1
- package/dist/services/business.service.js +66 -1
- package/dist/types/index.d.ts +47 -0
- package/dist/types/index.js +34 -1
- package/dist/utils/config-loader.d.ts +31 -9
- package/dist/utils/config-loader.js +201 -32
- package/dist/utils/csv-writer.d.ts +12 -0
- package/dist/utils/csv-writer.js +60 -0
- package/dist/utils/logger.d.ts +62 -0
- package/dist/utils/logger.js +98 -0
- package/package.json +1 -3
- package/.env.example +0 -20
- package/dist/utils/global-config.d.ts +0 -24
- package/dist/utils/global-config.js +0 -153
|
@@ -4,150 +4,208 @@ exports.workHourCommand = workHourCommand;
|
|
|
4
4
|
const holidays_1 = require("../config/holidays");
|
|
5
5
|
const business_service_1 = require("../services/business.service");
|
|
6
6
|
const config_loader_1 = require("../utils/config-loader");
|
|
7
|
+
const csv_writer_1 = require("../utils/csv-writer");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* 处理浮点数精度,保留2位小数
|
|
9
11
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
userStat.domainStats.forEach((domainStat) => {
|
|
39
|
-
allTypes.add(domainStat.type);
|
|
40
|
-
});
|
|
12
|
+
function roundToTwo(num) {
|
|
13
|
+
return Math.round(num * 100) / 100;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 查询年度工时数据
|
|
17
|
+
*/
|
|
18
|
+
async function queryWorkHourReportData(businessService, projectId, roleIds, targetYear) {
|
|
19
|
+
const allMembers = [];
|
|
20
|
+
const allUserStats = [];
|
|
21
|
+
const allTypes = new Set();
|
|
22
|
+
for (const roleId of roleIds) {
|
|
23
|
+
const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
|
|
24
|
+
if (roleMembers.length === 0) {
|
|
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) => {
|
|
33
|
+
allUserStats.push({
|
|
34
|
+
...userStat,
|
|
35
|
+
roleName,
|
|
36
|
+
roleId,
|
|
37
|
+
});
|
|
38
|
+
userStat.domainStats.forEach((domainStat) => {
|
|
39
|
+
allTypes.add(domainStat.type);
|
|
41
40
|
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
allUserStats.sort((a, b) => {
|
|
44
|
+
if (a.roleId !== b.roleId) {
|
|
45
|
+
return a.roleId - b.roleId;
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
return a.userName.localeCompare(b.userName);
|
|
48
|
+
});
|
|
49
|
+
const expectedWorkdays = (0, holidays_1.calculateExpectedWorkdays)(targetYear);
|
|
50
|
+
const expectedHoursPerPerson = expectedWorkdays * 8;
|
|
51
|
+
const totalExpectedHours = expectedHoursPerPerson * allMembers.length;
|
|
52
|
+
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
|
+
const userStatsData = [];
|
|
55
|
+
const roleSubtotals = [];
|
|
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
|
+
});
|
|
46
79
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return a.roleId - b.roleId;
|
|
51
|
-
}
|
|
52
|
-
return a.userName.localeCompare(b.userName);
|
|
53
|
-
});
|
|
54
|
-
const expectedWorkdays = (0, holidays_1.calculateExpectedWorkdays)(targetYear);
|
|
55
|
-
const expectedHoursPerPerson = expectedWorkdays * 8;
|
|
56
|
-
const totalExpectedHours = expectedHoursPerPerson * allMembers.length;
|
|
57
|
-
// 计算总工时
|
|
58
|
-
const totalHours = allUserStats.reduce((sum, userStat) => sum + userStat.totalHours, 0);
|
|
59
|
-
const totalEntries = allUserStats.reduce((sum, userStat) => sum + userStat.domainStats.reduce((s, d) => s + d.workHours.length, 0), 0);
|
|
60
|
-
console.log(`\n${targetYear}年工时统计报告`);
|
|
61
|
-
console.log('='.repeat(80));
|
|
62
|
-
console.log(`统计期间: ${targetYear}-01-01 至 ${targetYear}-12-31`);
|
|
63
|
-
console.log(`统计角色: ${roleIds.length} 个角色`);
|
|
64
|
-
console.log(`统计人数: ${allMembers.length} 人`);
|
|
65
|
-
console.log(`应计工作日: ${expectedWorkdays} 天`);
|
|
66
|
-
console.log(`应计工时: ${totalExpectedHours} 小时 (${expectedHoursPerPerson} 小时/人)`);
|
|
67
|
-
console.log(`实际工时: ${totalHours} 小时`);
|
|
68
|
-
console.log(`工时完成率: ${((totalHours / totalExpectedHours) * 100).toFixed(2)}%`);
|
|
69
|
-
console.log(`工时条目: ${totalEntries} 条`);
|
|
70
|
-
console.log('='.repeat(80));
|
|
71
|
-
// 构建表格数据:人作为行,type作为列
|
|
72
|
-
const tableData = {};
|
|
73
|
-
const typeTotals = {};
|
|
74
|
-
// 初始化总计
|
|
80
|
+
currentRoleId = userStat.roleId;
|
|
81
|
+
currentRoleName = userStat.roleName;
|
|
82
|
+
const domainStats = {};
|
|
75
83
|
allTypes.forEach((type) => {
|
|
76
|
-
|
|
84
|
+
domainStats[type] = 0;
|
|
77
85
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const subtotalRow = {};
|
|
86
|
-
subtotalRow['角色'] = `${currentRoleName} 小计`;
|
|
87
|
-
let subtotal = 0;
|
|
88
|
-
allTypes.forEach((type) => {
|
|
89
|
-
subtotalRow[type] = roleSubtotals[type] || 0;
|
|
90
|
-
subtotal += roleSubtotals[type] || 0;
|
|
91
|
-
});
|
|
92
|
-
subtotalRow['合计'] = subtotal;
|
|
93
|
-
tableData[`─ ${currentRoleName} 小计`] = subtotalRow;
|
|
94
|
-
// 重置小计
|
|
95
|
-
Object.keys(roleSubtotals).forEach((key) => {
|
|
96
|
-
roleSubtotals[key] = 0;
|
|
97
|
-
});
|
|
86
|
+
let userTotal = 0;
|
|
87
|
+
userStat.domainStats.forEach((domainStat) => {
|
|
88
|
+
domainStats[domainStat.type] = domainStat.totalHours;
|
|
89
|
+
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;
|
|
98
93
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
roleSubtotalStats[domainStat.type] = roundToTwo(roleSubtotalStats[domainStat.type] + domainStat.totalHours);
|
|
95
|
+
});
|
|
96
|
+
userStatsData.push({
|
|
97
|
+
userName: userStat.userName,
|
|
98
|
+
roleName: userStat.roleName,
|
|
99
|
+
roleId: userStat.roleId,
|
|
100
|
+
domainStats,
|
|
101
|
+
total: userTotal,
|
|
102
|
+
});
|
|
103
|
+
if (index === allUserStats.length - 1) {
|
|
104
|
+
const subtotalDomainStats = {};
|
|
105
|
+
let subtotal = 0;
|
|
106
106
|
allTypes.forEach((type) => {
|
|
107
|
-
|
|
107
|
+
subtotalDomainStats[type] = roleSubtotalStats[type] || 0;
|
|
108
|
+
subtotal = roundToTwo(subtotal + (roleSubtotalStats[type] || 0));
|
|
108
109
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
userTotal += domainStat.totalHours;
|
|
114
|
-
typeTotals[domainStat.type] += domainStat.totalHours;
|
|
115
|
-
// 累加到角色小计
|
|
116
|
-
if (!roleSubtotals[domainStat.type]) {
|
|
117
|
-
roleSubtotals[domainStat.type] = 0;
|
|
118
|
-
}
|
|
119
|
-
roleSubtotals[domainStat.type] += domainStat.totalHours;
|
|
110
|
+
roleSubtotals.push({
|
|
111
|
+
roleName: currentRoleName,
|
|
112
|
+
domainStats: subtotalDomainStats,
|
|
113
|
+
total: subtotal,
|
|
120
114
|
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
grandTotal
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
year: targetYear,
|
|
125
|
+
roleCount: roleIds.length,
|
|
126
|
+
memberCount: allMembers.length,
|
|
127
|
+
expectedWorkdays,
|
|
128
|
+
expectedHours: totalExpectedHours,
|
|
129
|
+
actualHours: totalHours,
|
|
130
|
+
completionRate: (totalHours / totalExpectedHours) * 100,
|
|
131
|
+
entryCount: totalEntries,
|
|
132
|
+
userStats: userStatsData,
|
|
133
|
+
roleSubtotals,
|
|
134
|
+
grandTotal: {
|
|
135
|
+
domainStats: grandTotalDomainStats,
|
|
136
|
+
total: grandTotal,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 控制台输出工时统计
|
|
142
|
+
*/
|
|
143
|
+
function outputConsole(data) {
|
|
144
|
+
logger_1.logger.info(`\n${data.year}年工时统计 [${data.roleSubtotals.map((r) => r.roleName).join(', ')}]`);
|
|
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));
|
|
152
|
+
// 构建表格数据
|
|
153
|
+
const tableData = {};
|
|
154
|
+
data.userStats.forEach((userStat) => {
|
|
155
|
+
const row = {
|
|
156
|
+
...userStat.domainStats,
|
|
157
|
+
合计: userStat.total,
|
|
158
|
+
};
|
|
159
|
+
tableData[userStat.userName] = row;
|
|
160
|
+
});
|
|
161
|
+
// 添加总计行
|
|
162
|
+
tableData['总计'] = {
|
|
163
|
+
...data.grandTotal.domainStats,
|
|
164
|
+
合计: data.grandTotal.total,
|
|
165
|
+
};
|
|
166
|
+
logger_1.logger.table(tableData);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* CSV 文件输出
|
|
170
|
+
*/
|
|
171
|
+
function outputCsv(data, targetYear) {
|
|
172
|
+
const domains = Object.keys(data.grandTotal.domainStats);
|
|
173
|
+
const csvLines = [];
|
|
174
|
+
csvLines.push(`用户,角色,${domains.join(',')},合计`);
|
|
175
|
+
data.userStats.forEach((userStat) => {
|
|
176
|
+
const domainValues = domains.map((domain) => userStat.domainStats[domain] || 0);
|
|
177
|
+
csvLines.push(`${userStat.userName},${userStat.roleName},${domainValues.join(',')},${userStat.total}`);
|
|
178
|
+
});
|
|
179
|
+
const filename = `work-hour-${targetYear}.csv`;
|
|
180
|
+
(0, csv_writer_1.writeCsvFile)(filename, csvLines, logger_1.logger);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* JSON 输出(直接打印到控制台,供编程调用)
|
|
184
|
+
*/
|
|
185
|
+
function outputJson(data) {
|
|
186
|
+
logger_1.logger.json(data);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* work-hour 命令入口
|
|
190
|
+
*/
|
|
191
|
+
async function workHourCommand(year, cliOptions = {}) {
|
|
192
|
+
try {
|
|
193
|
+
const targetYear = year || new Date().getFullYear().toString();
|
|
194
|
+
const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
195
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
196
|
+
const reportData = await queryWorkHourReportData(businessService, projectId, roleIds, targetYear);
|
|
197
|
+
if (outputFormat === 'console') {
|
|
198
|
+
outputConsole(reportData);
|
|
199
|
+
}
|
|
200
|
+
else if (outputFormat === 'csv') {
|
|
201
|
+
outputCsv(reportData, targetYear);
|
|
202
|
+
}
|
|
203
|
+
else if (outputFormat === 'json') {
|
|
204
|
+
outputJson(reportData);
|
|
205
|
+
}
|
|
148
206
|
}
|
|
149
207
|
catch (error) {
|
|
150
|
-
|
|
208
|
+
logger_1.logger.error(`执行过程中发生错误:`, error);
|
|
151
209
|
process.exit(1);
|
|
152
210
|
}
|
|
153
211
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiResponse, CachedToken, HuaweiCloudConfig, ListChildIssuesV2Response, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse } from '../types';
|
|
1
|
+
import { ApiResponse, CachedToken, HuaweiCloudConfig, ListChildIssuesV2Response, ListChildIssuesV4Response, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* 华为云CodeArts API服务类
|
|
4
4
|
* 支持IAM Token认证和CodeArts API调用
|
|
@@ -58,8 +58,15 @@ export declare class ApiService {
|
|
|
58
58
|
/**
|
|
59
59
|
* 查询子工作项 (ListChildIssuesV4)
|
|
60
60
|
* 获取指定工作项的所有子工作项
|
|
61
|
+
*
|
|
62
|
+
* FIXME: 华为云接口有问题,只能查询 15 条数据
|
|
61
63
|
*/
|
|
62
|
-
getChildIssues(projectId: string, issueId: string
|
|
64
|
+
getChildIssues(projectId: string, issueId: string): Promise<ApiResponse<ListChildIssuesV4Response>>;
|
|
65
|
+
/**
|
|
66
|
+
* 查询子工作项 (ListChildIssuesV2)
|
|
67
|
+
* 获取指定工作项的所有子工作项
|
|
68
|
+
*/
|
|
69
|
+
getChildIssuesV2(projectId: string, issueId: string, pageSize?: number, pageNo?: number): Promise<ApiResponse<ListChildIssuesV2Response>>;
|
|
63
70
|
/**
|
|
64
71
|
* 创建工作项
|
|
65
72
|
*/
|
|
@@ -313,11 +313,19 @@ class ApiService {
|
|
|
313
313
|
/**
|
|
314
314
|
* 查询子工作项 (ListChildIssuesV4)
|
|
315
315
|
* 获取指定工作项的所有子工作项
|
|
316
|
+
*
|
|
317
|
+
* FIXME: 华为云接口有问题,只能查询 15 条数据
|
|
316
318
|
*/
|
|
317
|
-
async getChildIssues(projectId, issueId
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
async getChildIssues(projectId, issueId) {
|
|
320
|
+
return this.request(`/v4/projects/${projectId}/issues/${issueId}/child`, {
|
|
321
|
+
method: 'GET',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 查询子工作项 (ListChildIssuesV2)
|
|
326
|
+
* 获取指定工作项的所有子工作项
|
|
327
|
+
*/
|
|
328
|
+
async getChildIssuesV2(projectId, issueId, pageSize = 100, pageNo = 1) {
|
|
321
329
|
return this.request('/v2/issues/child-issue-list', {
|
|
322
330
|
method: 'POST',
|
|
323
331
|
data: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AllWorkHourStats, HuaweiCloudConfig, IssueItem, IssueItemV2, IterationInfo, ProjectMember, WorkHourStats, WorkProgressStats } from '../types';
|
|
1
|
+
import { AllWorkHourStats, HuaweiCloudConfig, IssueItem, IssueItemV2, IterationInfo, ProjectMember, ProjectRole, WorkHourStats, WorkProgressStats } from '../types';
|
|
2
2
|
import { ApiService } from './api.service';
|
|
3
3
|
/**
|
|
4
4
|
* 业务服务类
|
|
@@ -19,6 +19,12 @@ export declare class BusinessService {
|
|
|
19
19
|
* @returns 指定角色的成员列表
|
|
20
20
|
*/
|
|
21
21
|
getMembersByRoleId(projectId: string, roleId: number): Promise<ProjectMember[]>;
|
|
22
|
+
/**
|
|
23
|
+
* 获取项目中的所有角色列表(去重)
|
|
24
|
+
* @param projectId 项目ID
|
|
25
|
+
* @returns 项目中的所有角色列表
|
|
26
|
+
*/
|
|
27
|
+
getProjectRoles(projectId: string): Promise<ProjectRole[]>;
|
|
22
28
|
/**
|
|
23
29
|
* 获取指定日期之后的迭代列表
|
|
24
30
|
* @param projectId 项目ID
|
|
@@ -86,4 +92,18 @@ export declare class BusinessService {
|
|
|
86
92
|
* @returns 所有子工作项列表
|
|
87
93
|
*/
|
|
88
94
|
getChildIssues(projectId: string, issueId: string): Promise<IssueItemV2[]>;
|
|
95
|
+
/**
|
|
96
|
+
* 验证 IAM 凭证是否有效
|
|
97
|
+
* @returns 验证结果,包含成功状态和可能的错误信息
|
|
98
|
+
*/
|
|
99
|
+
validateCredentials(): Promise<{
|
|
100
|
+
success: boolean;
|
|
101
|
+
error?: string;
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* 获取项目列表
|
|
105
|
+
* @param limit 返回的项目数量限制,默认100
|
|
106
|
+
* @returns 项目列表
|
|
107
|
+
*/
|
|
108
|
+
getProjects(limit?: number): Promise<import('../types').Project[]>;
|
|
89
109
|
}
|
|
@@ -32,6 +32,45 @@ class BusinessService {
|
|
|
32
32
|
const allMembers = membersResponse.data?.members || [];
|
|
33
33
|
return allMembers.filter((member) => member.role_id === roleId);
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取项目中的所有角色列表(去重)
|
|
37
|
+
* @param projectId 项目ID
|
|
38
|
+
* @returns 项目中的所有角色列表
|
|
39
|
+
*/
|
|
40
|
+
async getProjectRoles(projectId) {
|
|
41
|
+
// 分页获取所有成员
|
|
42
|
+
const allMembers = [];
|
|
43
|
+
const pageSize = 100;
|
|
44
|
+
let offset = 0;
|
|
45
|
+
let hasMore = true;
|
|
46
|
+
while (hasMore) {
|
|
47
|
+
const membersResponse = await this.apiService.getMembers(projectId, {
|
|
48
|
+
limit: pageSize,
|
|
49
|
+
offset: offset,
|
|
50
|
+
});
|
|
51
|
+
if (!membersResponse.success) {
|
|
52
|
+
throw new Error(`获取成员列表失败: ${membersResponse.error || '未知错误'}`);
|
|
53
|
+
}
|
|
54
|
+
const members = membersResponse.data?.members.filter((member) => member.role_id !== -1 && member.nick_name != null) || [];
|
|
55
|
+
allMembers.push(...members);
|
|
56
|
+
// 判断是否还有更多数据
|
|
57
|
+
const total = membersResponse.data?.total || 0;
|
|
58
|
+
offset += pageSize;
|
|
59
|
+
hasMore = offset < total;
|
|
60
|
+
}
|
|
61
|
+
// 使用 Map 去重,key 为 role_id
|
|
62
|
+
const rolesMap = new Map();
|
|
63
|
+
allMembers.forEach((member) => {
|
|
64
|
+
if (!rolesMap.has(member.role_id)) {
|
|
65
|
+
rolesMap.set(member.role_id, {
|
|
66
|
+
role_id: member.role_id,
|
|
67
|
+
role_name: member.role_name,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// 转换为数组并按 role_id 排序
|
|
72
|
+
return Array.from(rolesMap.values()).sort((a, b) => a.role_id - b.role_id);
|
|
73
|
+
}
|
|
35
74
|
/**
|
|
36
75
|
* 获取指定日期之后的迭代列表
|
|
37
76
|
* @param projectId 项目ID
|
|
@@ -383,7 +422,7 @@ class BusinessService {
|
|
|
383
422
|
let pageNo = 1;
|
|
384
423
|
let hasMore = true;
|
|
385
424
|
while (hasMore) {
|
|
386
|
-
const response = await this.apiService.
|
|
425
|
+
const response = await this.apiService.getChildIssuesV2(projectId, issueId, pageSize, pageNo);
|
|
387
426
|
if (!response.success || !response.data) {
|
|
388
427
|
throw new Error(`获取子工作项失败: ${response.error || '未知错误'}`);
|
|
389
428
|
}
|
|
@@ -395,5 +434,31 @@ class BusinessService {
|
|
|
395
434
|
}
|
|
396
435
|
return allChildIssues;
|
|
397
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* 验证 IAM 凭证是否有效
|
|
439
|
+
* @returns 验证结果,包含成功状态和可能的错误信息
|
|
440
|
+
*/
|
|
441
|
+
async validateCredentials() {
|
|
442
|
+
try {
|
|
443
|
+
await this.apiService.refreshToken();
|
|
444
|
+
return { success: true };
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
448
|
+
return { success: false, error: errorMsg };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* 获取项目列表
|
|
453
|
+
* @param limit 返回的项目数量限制,默认100
|
|
454
|
+
* @returns 项目列表
|
|
455
|
+
*/
|
|
456
|
+
async getProjects(limit = 100) {
|
|
457
|
+
const response = await this.apiService.getProjects({ limit });
|
|
458
|
+
if (!response.success || !response.data) {
|
|
459
|
+
throw new Error(response.error || '获取项目列表失败');
|
|
460
|
+
}
|
|
461
|
+
return response.data.projects;
|
|
462
|
+
}
|
|
398
463
|
}
|
|
399
464
|
exports.BusinessService = BusinessService;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -152,6 +152,10 @@ export interface ProjectMemberQueryParams {
|
|
|
152
152
|
offset?: number;
|
|
153
153
|
limit?: number;
|
|
154
154
|
}
|
|
155
|
+
export interface ProjectRole {
|
|
156
|
+
role_id: number;
|
|
157
|
+
role_name: string;
|
|
158
|
+
}
|
|
155
159
|
export interface ShowProjectWorkHoursRequest {
|
|
156
160
|
begin_time?: string;
|
|
157
161
|
end_time?: string;
|
|
@@ -521,3 +525,46 @@ export interface AllWorkHourStats {
|
|
|
521
525
|
totalEntries: number;
|
|
522
526
|
userStats: UserAllWorkHourStats[];
|
|
523
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* 配置键枚举
|
|
530
|
+
* 用于类型安全的配置项访问
|
|
531
|
+
*/
|
|
532
|
+
export declare enum ConfigKey {
|
|
533
|
+
HUAWEI_CLOUD_IAM_ENDPOINT = "HUAWEI_CLOUD_IAM_ENDPOINT",
|
|
534
|
+
HUAWEI_CLOUD_REGION = "HUAWEI_CLOUD_REGION",
|
|
535
|
+
HUAWEI_CLOUD_USERNAME = "HUAWEI_CLOUD_USERNAME",
|
|
536
|
+
HUAWEI_CLOUD_PASSWORD = "HUAWEI_CLOUD_PASSWORD",
|
|
537
|
+
HUAWEI_CLOUD_DOMAIN = "HUAWEI_CLOUD_DOMAIN",
|
|
538
|
+
CODEARTS_BASE_URL = "CODEARTS_BASE_URL",
|
|
539
|
+
PROJECT_ID = "PROJECT_ID",
|
|
540
|
+
ROLE_ID = "ROLE_ID"
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* 不可变配置键(只能通过 config 命令设置)
|
|
544
|
+
*/
|
|
545
|
+
export declare const IMMUTABLE_CONFIG_KEYS: ConfigKey[];
|
|
546
|
+
/**
|
|
547
|
+
* 可变配置键(可以通过命令行参数覆盖)
|
|
548
|
+
*/
|
|
549
|
+
export declare const MUTABLE_CONFIG_KEYS: ConfigKey[];
|
|
550
|
+
/**
|
|
551
|
+
* 类型安全的配置映射
|
|
552
|
+
*/
|
|
553
|
+
export type ConfigMap = {
|
|
554
|
+
[ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT]: string;
|
|
555
|
+
[ConfigKey.HUAWEI_CLOUD_REGION]: string;
|
|
556
|
+
[ConfigKey.HUAWEI_CLOUD_USERNAME]: string;
|
|
557
|
+
[ConfigKey.HUAWEI_CLOUD_PASSWORD]: string;
|
|
558
|
+
[ConfigKey.HUAWEI_CLOUD_DOMAIN]: string;
|
|
559
|
+
[ConfigKey.CODEARTS_BASE_URL]: string;
|
|
560
|
+
[ConfigKey.PROJECT_ID]: string;
|
|
561
|
+
[ConfigKey.ROLE_ID]: string;
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* 部分配置映射(用于读取配置文件)
|
|
565
|
+
*/
|
|
566
|
+
export type PartialConfigMap = Partial<ConfigMap>;
|
|
567
|
+
/**
|
|
568
|
+
* 输出格式类型
|
|
569
|
+
*/
|
|
570
|
+
export type OutputFormat = 'console' | 'csv' | 'json';
|
package/dist/types/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DefectAnalysisType = void 0;
|
|
3
|
+
exports.MUTABLE_CONFIG_KEYS = exports.IMMUTABLE_CONFIG_KEYS = exports.ConfigKey = exports.DefectAnalysisType = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* 缺陷技术分析选项枚举
|
|
6
6
|
* custom_field32(缺陷技术分析)字段的选项值枚举定义
|
|
@@ -23,3 +23,36 @@ var DefectAnalysisType;
|
|
|
23
23
|
DefectAnalysisType["USAGE_AND_CONFIG"] = "\u4F7F\u7528\u53CA\u914D\u7F6E\u95EE\u9898";
|
|
24
24
|
DefectAnalysisType["OTHER"] = "\u5176\u4ED6\u95EE\u9898";
|
|
25
25
|
})(DefectAnalysisType || (exports.DefectAnalysisType = DefectAnalysisType = {}));
|
|
26
|
+
/**
|
|
27
|
+
* 配置键枚举
|
|
28
|
+
* 用于类型安全的配置项访问
|
|
29
|
+
*/
|
|
30
|
+
var ConfigKey;
|
|
31
|
+
(function (ConfigKey) {
|
|
32
|
+
// 不可变配置(只能通过 config 命令设置)
|
|
33
|
+
ConfigKey["HUAWEI_CLOUD_IAM_ENDPOINT"] = "HUAWEI_CLOUD_IAM_ENDPOINT";
|
|
34
|
+
ConfigKey["HUAWEI_CLOUD_REGION"] = "HUAWEI_CLOUD_REGION";
|
|
35
|
+
ConfigKey["HUAWEI_CLOUD_USERNAME"] = "HUAWEI_CLOUD_USERNAME";
|
|
36
|
+
ConfigKey["HUAWEI_CLOUD_PASSWORD"] = "HUAWEI_CLOUD_PASSWORD";
|
|
37
|
+
ConfigKey["HUAWEI_CLOUD_DOMAIN"] = "HUAWEI_CLOUD_DOMAIN";
|
|
38
|
+
ConfigKey["CODEARTS_BASE_URL"] = "CODEARTS_BASE_URL";
|
|
39
|
+
ConfigKey["PROJECT_ID"] = "PROJECT_ID";
|
|
40
|
+
// 可变配置(可以通过命令行参数覆盖)
|
|
41
|
+
ConfigKey["ROLE_ID"] = "ROLE_ID";
|
|
42
|
+
})(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,22 +1,44 @@
|
|
|
1
|
-
import { HuaweiCloudConfig } from '../types';
|
|
1
|
+
import { HuaweiCloudConfig, OutputFormat, PartialConfigMap } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* 获取全局配置文件路径
|
|
4
|
+
*/
|
|
5
|
+
export declare function getConfigPath(): string;
|
|
6
|
+
/**
|
|
7
|
+
* 检查全局配置文件是否存在
|
|
8
|
+
*/
|
|
9
|
+
export declare function configExists(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 读取全局配置
|
|
12
|
+
*/
|
|
13
|
+
export declare function readConfig(): PartialConfigMap;
|
|
14
|
+
/**
|
|
15
|
+
* 写入全局配置
|
|
16
|
+
* 支持动态配置项,自动按分组组织配置文件
|
|
17
|
+
*/
|
|
18
|
+
export declare function writeConfig(config: PartialConfigMap): void;
|
|
19
|
+
/**
|
|
20
|
+
* 删除全局配置
|
|
21
|
+
*/
|
|
22
|
+
export declare function deleteConfig(): void;
|
|
2
23
|
export interface CliOptions {
|
|
3
|
-
projectId?: string;
|
|
4
24
|
roleId?: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
domain?: string;
|
|
8
|
-
region?: string;
|
|
9
|
-
iamEndpoint?: string;
|
|
10
|
-
codeartsUrl?: string;
|
|
25
|
+
output?: string;
|
|
26
|
+
report?: boolean;
|
|
11
27
|
}
|
|
12
28
|
export interface LoadedConfig {
|
|
13
29
|
projectId: string;
|
|
14
30
|
roleIds: number[];
|
|
15
31
|
config: HuaweiCloudConfig;
|
|
32
|
+
outputFormat: OutputFormat;
|
|
16
33
|
}
|
|
17
34
|
/**
|
|
18
|
-
* 加载配置,优先级:命令行参数 >
|
|
35
|
+
* 加载配置,优先级:命令行参数 > 全局配置
|
|
19
36
|
* @param cliOptions 命令行选项
|
|
20
37
|
* @returns 加载的配置
|
|
21
38
|
*/
|
|
22
39
|
export declare function loadConfig(cliOptions?: CliOptions): LoadedConfig;
|
|
40
|
+
/**
|
|
41
|
+
* 获取最终合并后的配置(用于显示)
|
|
42
|
+
* @returns 合并后的配置映射
|
|
43
|
+
*/
|
|
44
|
+
export declare function getConfig(): PartialConfigMap;
|