zephyr-enterprise-tools 1.1.0 → 1.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/cli.js +7 -8
- package/mcp-server.js +29 -0
- package/package.json +7 -2
- package/zephyr-enterprise-tools.d.ts +202 -0
- package/zephyr-enterprise-tools.js +93 -0
package/cli.js
CHANGED
|
@@ -15,17 +15,19 @@
|
|
|
15
15
|
* zephyr-tools mcp # Start MCP server
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import QualityGates from './zephyr-enterprise-tools.js';
|
|
19
|
+
|
|
18
20
|
// Check if MCP server mode
|
|
19
21
|
if (process.argv[2] === 'mcp') {
|
|
20
22
|
import('./mcp-server.js');
|
|
21
23
|
} else {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
main().catch(err => {
|
|
25
|
+
console.error(`Error: ${err.message}`);
|
|
26
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
async function runCLI() {
|
|
27
|
-
const { default: QualityGates } = await import('./zephyr-enterprise-tools.js');
|
|
28
|
-
|
|
29
31
|
// ─── Parse Arguments ──────────────────────────────────────────────────────────
|
|
30
32
|
|
|
31
33
|
function parseArgs() {
|
|
@@ -545,6 +547,3 @@ async function main() {
|
|
|
545
547
|
process.exit(1);
|
|
546
548
|
}
|
|
547
549
|
}
|
|
548
|
-
|
|
549
|
-
main();
|
|
550
|
-
} // end runCLI
|
package/mcp-server.js
CHANGED
|
@@ -167,6 +167,23 @@ const TOOLS = [
|
|
|
167
167
|
required: ['projectId', 'releaseId'],
|
|
168
168
|
},
|
|
169
169
|
},
|
|
170
|
+
{
|
|
171
|
+
name: 'user_trend',
|
|
172
|
+
description: 'Get full audit log activity for a user — every action they performed across the system. Filter by date range and optionally by entity type (project or release).',
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: 'object',
|
|
175
|
+
properties: {
|
|
176
|
+
userName: { type: 'string', description: 'Username to look up (e.g. "dylan.garcia")' },
|
|
177
|
+
fromDate: { type: 'string', description: 'Start date YYYY-MM-DD (e.g. "2026-08-01")' },
|
|
178
|
+
toDate: { type: 'string', description: 'End date YYYY-MM-DD (e.g. "2026-08-18")' },
|
|
179
|
+
entity: { type: 'string', description: 'Filter by entity type: "project", "release", or "" for all', enum: ['project', 'release', ''] },
|
|
180
|
+
operation: { type: 'string', description: 'Filter by operation (e.g. "CREATE", "UPDATE"). Omit for all.' },
|
|
181
|
+
offset: { type: 'number', description: 'Pagination offset (default 0)' },
|
|
182
|
+
pageSize: { type: 'number', description: 'Records per page (default 25)' },
|
|
183
|
+
},
|
|
184
|
+
required: ['userName'],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
170
187
|
{
|
|
171
188
|
name: 'list_projects',
|
|
172
189
|
description: 'List all available Zephyr projects.',
|
|
@@ -282,6 +299,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
282
299
|
case 'user_activity':
|
|
283
300
|
result = await tools.getUserActivity(projectId, releaseId, { days: args.days || 30 });
|
|
284
301
|
break;
|
|
302
|
+
|
|
303
|
+
case 'user_trend':
|
|
304
|
+
result = await tools.getUserTrend({
|
|
305
|
+
userName: args.userName,
|
|
306
|
+
fromDate: args.fromDate || null,
|
|
307
|
+
toDate: args.toDate || null,
|
|
308
|
+
entity: args.entity || '',
|
|
309
|
+
operation: args.operation || null,
|
|
310
|
+
offset: args.offset || 0,
|
|
311
|
+
pageSize: args.pageSize || 25,
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
285
314
|
|
|
286
315
|
case 'list_projects':
|
|
287
316
|
const projects = await tools.GET('/project');
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-enterprise-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Comprehensive Zephyr Enterprise Tools - Release Readiness, Project Health, Test Analytics & More",
|
|
5
5
|
"main": "zephyr-enterprise-tools.js",
|
|
6
|
+
"types": "zephyr-enterprise-tools.d.ts",
|
|
6
7
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./zephyr-enterprise-tools.js",
|
|
10
|
+
"types": "./zephyr-enterprise-tools.d.ts"
|
|
11
|
+
},
|
|
8
12
|
"./cli": "./cli.js",
|
|
9
13
|
"./mcp": "./mcp-server.js"
|
|
10
14
|
},
|
|
@@ -52,6 +56,7 @@
|
|
|
52
56
|
},
|
|
53
57
|
"files": [
|
|
54
58
|
"zephyr-enterprise-tools.js",
|
|
59
|
+
"zephyr-enterprise-tools.d.ts",
|
|
55
60
|
"cli.js",
|
|
56
61
|
"mcp-server.js",
|
|
57
62
|
"README.md",
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zephyr Enterprise Tools - TypeScript Declarations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ZephyrConfig {
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ThresholdConfig {
|
|
11
|
+
requirementCoverage: { go: number; description: string };
|
|
12
|
+
testPlanAnalysis: { noGo: number; conditionalGo: number; description: string };
|
|
13
|
+
testExecution: { noGo: number; conditionalGo: number; description: string };
|
|
14
|
+
defectQuality: { blockerLimit: number; highRiskLimit: number; description: string };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type GateStatus = "GO" | "CONDITIONAL GO" | "NO GO";
|
|
18
|
+
export type HealthStatus = "HEALTHY" | "MODERATE" | "AT RISK" | "CRITICAL";
|
|
19
|
+
|
|
20
|
+
export interface GateResult {
|
|
21
|
+
gate: string;
|
|
22
|
+
status: GateStatus;
|
|
23
|
+
statusMessage: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RequirementCoverageResult extends GateResult {
|
|
28
|
+
coveragePercentage: number;
|
|
29
|
+
coveredRequirements: number;
|
|
30
|
+
notCoveredRequirements: number;
|
|
31
|
+
totalRequirements: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TestPlanResult extends GateResult {
|
|
35
|
+
overallPlanningPercentage: number;
|
|
36
|
+
totalTests: number;
|
|
37
|
+
plannedTests: number;
|
|
38
|
+
assignedTests: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TestExecutionResult extends GateResult {
|
|
42
|
+
executionPercentage: number;
|
|
43
|
+
completedTests: number;
|
|
44
|
+
totalPlannedTests: number;
|
|
45
|
+
breakdown: {
|
|
46
|
+
passed: number;
|
|
47
|
+
failed: number;
|
|
48
|
+
blocked: number;
|
|
49
|
+
wip: number;
|
|
50
|
+
notExecuted: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DefectQualityResult extends GateResult {
|
|
55
|
+
totalDefects: number;
|
|
56
|
+
breakdown: {
|
|
57
|
+
blocker: { count: number; defects: unknown[] };
|
|
58
|
+
highRisk: { count: number; defects: unknown[] };
|
|
59
|
+
lowRisk: { count: number; defects: unknown[] };
|
|
60
|
+
resolved: { count: number };
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ReleaseReadinessResult {
|
|
65
|
+
projectId: number;
|
|
66
|
+
releaseId: number;
|
|
67
|
+
timestamp: string;
|
|
68
|
+
overallStatus: GateStatus;
|
|
69
|
+
summary: { passed: number; failed: number; conditional: number };
|
|
70
|
+
recommendation: string;
|
|
71
|
+
gates: {
|
|
72
|
+
requirementCoverage: RequirementCoverageResult;
|
|
73
|
+
testPlanAnalysis: TestPlanResult;
|
|
74
|
+
testExecution: TestExecutionResult;
|
|
75
|
+
defectQuality: DefectQualityResult;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ProjectHealthResult {
|
|
80
|
+
tool: string;
|
|
81
|
+
projectId: number;
|
|
82
|
+
releaseId: number;
|
|
83
|
+
timestamp: string;
|
|
84
|
+
healthScore: number;
|
|
85
|
+
healthStatus: HealthStatus;
|
|
86
|
+
summary: Record<string, unknown>;
|
|
87
|
+
metrics: Record<string, Record<string, unknown>>;
|
|
88
|
+
recommendations: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface TestCoverageResult {
|
|
92
|
+
tool: string;
|
|
93
|
+
projectId: number;
|
|
94
|
+
releaseId: number;
|
|
95
|
+
timestamp: string;
|
|
96
|
+
summary: Record<string, unknown>;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface FailedTest {
|
|
101
|
+
testcaseId: number;
|
|
102
|
+
testcaseKey: string;
|
|
103
|
+
testcaseName: string;
|
|
104
|
+
executedBy?: string;
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface FailedTestsResult {
|
|
109
|
+
tool: string;
|
|
110
|
+
projectId: number;
|
|
111
|
+
releaseId: number;
|
|
112
|
+
timestamp: string;
|
|
113
|
+
failedTests: FailedTest[];
|
|
114
|
+
total: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface RequirementCoverageDetailResult {
|
|
118
|
+
tool: string;
|
|
119
|
+
projectId: number;
|
|
120
|
+
releaseId: number;
|
|
121
|
+
timestamp: string;
|
|
122
|
+
results: unknown[];
|
|
123
|
+
[key: string]: unknown;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface TestTrendsResult {
|
|
127
|
+
tool: string;
|
|
128
|
+
projectId: number;
|
|
129
|
+
releaseId: number;
|
|
130
|
+
timestamp: string;
|
|
131
|
+
dailyTrends: Array<{ date: string; total: number; passed: number; failed: number; blocked: number }>;
|
|
132
|
+
[key: string]: unknown;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface SearchTestCasesOptions {
|
|
136
|
+
query?: string;
|
|
137
|
+
limit?: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface SearchTestCasesResult {
|
|
141
|
+
tool: string;
|
|
142
|
+
projectId: number;
|
|
143
|
+
releaseId: number;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
results: Array<{ id: number; key: string; name: string; status?: string }>;
|
|
146
|
+
totalMatches: number;
|
|
147
|
+
returned: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface UserActivityOptions {
|
|
151
|
+
days?: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface UserActivityResult {
|
|
155
|
+
tool: string;
|
|
156
|
+
projectId: number;
|
|
157
|
+
releaseId: number;
|
|
158
|
+
timestamp: string;
|
|
159
|
+
period: { days: number };
|
|
160
|
+
teamSummary: Record<string, unknown>;
|
|
161
|
+
assignedTo: unknown[];
|
|
162
|
+
executedBy: unknown[];
|
|
163
|
+
topExecutors: unknown[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface FailedTestsOptions {
|
|
167
|
+
limit?: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface TrendsOptions {
|
|
171
|
+
days?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export declare class QualityGates {
|
|
175
|
+
constructor(config: ZephyrConfig);
|
|
176
|
+
|
|
177
|
+
// Quality Gates (Release Readiness)
|
|
178
|
+
requirementCoverageGate(projectId: number, releaseId: number): Promise<RequirementCoverageResult>;
|
|
179
|
+
testPlanAnalysisGate(projectId: number, releaseId: number): Promise<TestPlanResult>;
|
|
180
|
+
testExecutionGate(projectId: number, releaseId: number): Promise<TestExecutionResult>;
|
|
181
|
+
defectQualityGate(projectId: number, releaseId: number): Promise<DefectQualityResult>;
|
|
182
|
+
runAllGates(projectId: number, releaseId: number): Promise<ReleaseReadinessResult>;
|
|
183
|
+
|
|
184
|
+
// Analytics & Insights
|
|
185
|
+
getProjectHealth(projectId: number, releaseId: number): Promise<ProjectHealthResult>;
|
|
186
|
+
getTestCoverage(projectId: number, releaseId: number): Promise<TestCoverageResult>;
|
|
187
|
+
getFailedTests(projectId: number, releaseId: number, options?: FailedTestsOptions): Promise<FailedTestsResult>;
|
|
188
|
+
getRequirementCoverage(projectId: number, releaseId: number): Promise<RequirementCoverageDetailResult>;
|
|
189
|
+
getTestCaseTrends(projectId: number, releaseId: number, options?: TrendsOptions): Promise<TestTrendsResult>;
|
|
190
|
+
searchTestCases(projectId: number, releaseId: number, options?: SearchTestCasesOptions): Promise<SearchTestCasesResult>;
|
|
191
|
+
getUserActivity(projectId: number, releaseId: number, options?: UserActivityOptions): Promise<UserActivityResult>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export declare const THRESHOLDS: ThresholdConfig;
|
|
195
|
+
export declare const RESOLVED_STATUSES: string[];
|
|
196
|
+
export declare const BLOCKER_PRIORITIES: string[];
|
|
197
|
+
export declare const HIGH_RISK_PRIORITIES: string[];
|
|
198
|
+
export declare const LOW_RISK_PRIORITIES: string[];
|
|
199
|
+
|
|
200
|
+
/** Alias for QualityGates — kept for backward compatibility */
|
|
201
|
+
export { QualityGates as ZephyrTools };
|
|
202
|
+
export default QualityGates;
|
|
@@ -1285,6 +1285,99 @@ export class QualityGates {
|
|
|
1285
1285
|
topExecutors: executors.slice(0, 5),
|
|
1286
1286
|
};
|
|
1287
1287
|
}
|
|
1288
|
+
|
|
1289
|
+
// ─── User Trend (Audit Logs) ─────────────────────────────────────────────────
|
|
1290
|
+
|
|
1291
|
+
async getUserTrend(options = {}) {
|
|
1292
|
+
const {
|
|
1293
|
+
userName = "",
|
|
1294
|
+
fromDate = null, // "YYYY-MM-DD"
|
|
1295
|
+
toDate = null, // "YYYY-MM-DD"
|
|
1296
|
+
entity = "", // "project", "release", or "" for all
|
|
1297
|
+
operation = null, // specific operation or null for all
|
|
1298
|
+
offset = 0,
|
|
1299
|
+
pageSize = 25,
|
|
1300
|
+
} = options;
|
|
1301
|
+
|
|
1302
|
+
const v3BaseUrl = this.baseUrl.replace('/latest', '/v3');
|
|
1303
|
+
const url = new URL(`${v3BaseUrl}/ui/auditLogs`);
|
|
1304
|
+
url.searchParams.set("offset", String(offset));
|
|
1305
|
+
url.searchParams.set("pagesize", String(pageSize));
|
|
1306
|
+
url.searchParams.set("isascorder", "false");
|
|
1307
|
+
url.searchParams.set("order", "id");
|
|
1308
|
+
url.searchParams.set("isExport", "false");
|
|
1309
|
+
|
|
1310
|
+
const body = { entity, operation, userName };
|
|
1311
|
+
|
|
1312
|
+
if (fromDate) {
|
|
1313
|
+
body.fromDate = fromDate;
|
|
1314
|
+
body.fromDateVal = `${fromDate}T00:00:00.000Z`;
|
|
1315
|
+
}
|
|
1316
|
+
if (toDate) {
|
|
1317
|
+
body.toDate = toDate;
|
|
1318
|
+
body.toDateVal = `${toDate}T00:00:00.000Z`;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const res = await fetch(url.toString(), {
|
|
1322
|
+
method: "POST",
|
|
1323
|
+
headers: {
|
|
1324
|
+
Accept: "application/json",
|
|
1325
|
+
"Content-Type": "application/json",
|
|
1326
|
+
...this.authHeader(),
|
|
1327
|
+
},
|
|
1328
|
+
body: JSON.stringify(body),
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
if (!res.ok) {
|
|
1332
|
+
const text = await res.text().catch(() => "");
|
|
1333
|
+
throw new Error(`Zephyr audit logs API ${res.status}: ${text}`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const data = await res.json();
|
|
1337
|
+
const logs = data.results || data || [];
|
|
1338
|
+
|
|
1339
|
+
// Summarise by operation and entity type
|
|
1340
|
+
const byOperation = {};
|
|
1341
|
+
const byEntity = {};
|
|
1342
|
+
for (const log of logs) {
|
|
1343
|
+
const op = log.operation || "Unknown";
|
|
1344
|
+
const ent = log.entity || "Unknown";
|
|
1345
|
+
byOperation[op] = (byOperation[op] || 0) + 1;
|
|
1346
|
+
byEntity[ent] = (byEntity[ent] || 0) + 1;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return {
|
|
1350
|
+
tool: "User Trend",
|
|
1351
|
+
userName,
|
|
1352
|
+
dateRange: { from: fromDate || "all", to: toDate || "all" },
|
|
1353
|
+
entity: entity || "all",
|
|
1354
|
+
timestamp: new Date().toISOString(),
|
|
1355
|
+
totalRecords: data.totalCount ?? logs.length,
|
|
1356
|
+
offset,
|
|
1357
|
+
pageSize,
|
|
1358
|
+
operationSummary: Object.entries(byOperation)
|
|
1359
|
+
.map(([operation, count]) => ({ operation, count }))
|
|
1360
|
+
.sort((a, b) => b.count - a.count),
|
|
1361
|
+
entitySummary: Object.entries(byEntity)
|
|
1362
|
+
.map(([entity, count]) => ({ entity, count }))
|
|
1363
|
+
.sort((a, b) => b.count - a.count),
|
|
1364
|
+
auditLogs: logs.map(log => ({
|
|
1365
|
+
id: log.id,
|
|
1366
|
+
userName: log.userName,
|
|
1367
|
+
entity: log.entity,
|
|
1368
|
+
entityName: log.entityName,
|
|
1369
|
+
operation: log.operation,
|
|
1370
|
+
description: log.description,
|
|
1371
|
+
detail: log.detail,
|
|
1372
|
+
projectId: log.projectId,
|
|
1373
|
+
projectName: log.projectName,
|
|
1374
|
+
releaseId: log.releaseId,
|
|
1375
|
+
releaseName: log.releaseName,
|
|
1376
|
+
createdOn: log.createdOn,
|
|
1377
|
+
ipAddress: log.ipAddress,
|
|
1378
|
+
})),
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1288
1381
|
}
|
|
1289
1382
|
|
|
1290
1383
|
// Export with both names for backward compatibility
|