zephyr-enterprise-tools 1.2.4 → 1.2.6
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 +2 -0
- package/mcp-server.js +5 -4
- package/package.json +1 -1
- package/zephyr-enterprise-tools.d.ts +7 -1
- package/zephyr-enterprise-tools.js +142 -67
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ Comprehensive tools for Zephyr Enterprise - Release Readiness, Project Health, T
|
|
|
25
25
|
| `test-trends` | Test execution trends over time |
|
|
26
26
|
| `search-tests` | Search test cases by query |
|
|
27
27
|
| `user-activity` | User activity and productivity metrics |
|
|
28
|
+
| `user-trend` | Full audit log history for a user — every action across the system |
|
|
28
29
|
| `execution-burndown` | Day-by-day execution burndown (remaining vs ideal) |
|
|
29
30
|
|
|
30
31
|
---
|
|
@@ -73,6 +74,7 @@ Use zephyr-enterprise-tools as an MCP (Model Context Protocol) server with your
|
|
|
73
74
|
| `test_trends` | Get execution trends over time |
|
|
74
75
|
| `search_test_cases` | Search test cases by query |
|
|
75
76
|
| `user_activity` | Get user activity metrics |
|
|
77
|
+
| `user_trend` | Full audit log history for a user — filter by date range, entity type, and operation |
|
|
76
78
|
| `execution_burndown` | Day-by-day execution burndown chart data (remaining vs ideal) |
|
|
77
79
|
|
|
78
80
|
### Claude Desktop
|
package/mcp-server.js
CHANGED
|
@@ -44,12 +44,13 @@ const TOOLS = [
|
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
name: 'test_plan_analysis',
|
|
47
|
-
description: 'Analyze test planning status. Threshold: <80% = NO GO, 80-90% = CONDITIONAL, ≥90% = GO.',
|
|
47
|
+
description: 'Analyze test planning status, optionally scoped to testcase IDs returned by a Zephyr ZQL expression. Threshold: <80% = NO GO, 80-90% = CONDITIONAL, ≥90% = GO.',
|
|
48
48
|
inputSchema: {
|
|
49
49
|
type: 'object',
|
|
50
50
|
properties: {
|
|
51
51
|
projectId: { type: 'number', description: 'Zephyr project ID' },
|
|
52
52
|
releaseId: { type: 'number', description: 'Zephyr release ID' },
|
|
53
|
+
query: { type: 'string', description: 'Optional Zephyr ZQL expression, e.g. priority = "P1"' },
|
|
53
54
|
},
|
|
54
55
|
required: ['projectId', 'releaseId'],
|
|
55
56
|
},
|
|
@@ -142,13 +143,13 @@ const TOOLS = [
|
|
|
142
143
|
},
|
|
143
144
|
{
|
|
144
145
|
name: 'search_test_cases',
|
|
145
|
-
description: 'Search test cases
|
|
146
|
+
description: 'Search test cases using a Zephyr ZQL expression, such as priority = "P1".',
|
|
146
147
|
inputSchema: {
|
|
147
148
|
type: 'object',
|
|
148
149
|
properties: {
|
|
149
150
|
projectId: { type: 'number', description: 'Zephyr project ID' },
|
|
150
151
|
releaseId: { type: 'number', description: 'Zephyr release ID' },
|
|
151
|
-
query: { type: 'string', description: '
|
|
152
|
+
query: { type: 'string', description: 'Zephyr ZQL expression, e.g. priority = "P1"' },
|
|
152
153
|
limit: { type: 'number', description: 'Maximum results (default: 50)' },
|
|
153
154
|
},
|
|
154
155
|
required: ['projectId', 'releaseId'],
|
|
@@ -272,7 +273,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
272
273
|
break;
|
|
273
274
|
|
|
274
275
|
case 'test_plan_analysis':
|
|
275
|
-
result = await tools.testPlanAnalysisGate(projectId, releaseId);
|
|
276
|
+
result = await tools.testPlanAnalysisGate(projectId, releaseId, { query: args.query });
|
|
276
277
|
break;
|
|
277
278
|
|
|
278
279
|
case 'test_execution':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-enterprise-tools",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Comprehensive Zephyr Enterprise Tools - Release Readiness, Project Health, Test Analytics & More",
|
|
5
5
|
"main": "zephyr-enterprise-tools.js",
|
|
6
6
|
"types": "zephyr-enterprise-tools.d.ts",
|
|
@@ -38,6 +38,11 @@ export interface TestPlanResult extends GateResult {
|
|
|
38
38
|
assignedTests: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export interface TestPlanOptions {
|
|
42
|
+
/** Optional Zephyr ZQL expression, e.g. `priority = "P1"`. */
|
|
43
|
+
query?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
export interface TestExecutionResult extends GateResult {
|
|
42
47
|
executionPercentage: number;
|
|
43
48
|
completedTests: number;
|
|
@@ -133,6 +138,7 @@ export interface TestTrendsResult {
|
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
export interface SearchTestCasesOptions {
|
|
141
|
+
/** Zephyr ZQL expression, e.g. `priority = "P1"`. */
|
|
136
142
|
query?: string;
|
|
137
143
|
limit?: number;
|
|
138
144
|
}
|
|
@@ -176,7 +182,7 @@ export declare class QualityGates {
|
|
|
176
182
|
|
|
177
183
|
// Quality Gates (Release Readiness)
|
|
178
184
|
requirementCoverageGate(projectId: number, releaseId: number): Promise<RequirementCoverageResult>;
|
|
179
|
-
testPlanAnalysisGate(projectId: number, releaseId: number): Promise<TestPlanResult>;
|
|
185
|
+
testPlanAnalysisGate(projectId: number, releaseId: number, options?: TestPlanOptions): Promise<TestPlanResult>;
|
|
180
186
|
testExecutionGate(projectId: number, releaseId: number): Promise<TestExecutionResult>;
|
|
181
187
|
defectQualityGate(projectId: number, releaseId: number): Promise<DefectQualityResult>;
|
|
182
188
|
runAllGates(projectId: number, releaseId: number): Promise<ReleaseReadinessResult>;
|
|
@@ -113,6 +113,50 @@ export class QualityGates {
|
|
|
113
113
|
return res.json();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
async POSTv3(path, body) {
|
|
117
|
+
const v3BaseUrl = this.baseUrl.replace('/latest', '/v3');
|
|
118
|
+
const res = await fetch(`${v3BaseUrl}${path}`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { Accept: "application/json", "Content-Type": "application/json", ...this.authHeader() },
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
const text = await res.text().catch(() => "");
|
|
125
|
+
throw new Error(`Zephyr v3 API ${res.status}: ${text}`);
|
|
126
|
+
}
|
|
127
|
+
return res.json();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async searchExecutionsByZql(releaseId, query) {
|
|
131
|
+
const pageSize = 50;
|
|
132
|
+
let firstResult = 0;
|
|
133
|
+
const executions = [];
|
|
134
|
+
|
|
135
|
+
while (true) {
|
|
136
|
+
const response = await this.POSTv3("/advancesearch/zql", {
|
|
137
|
+
firstresult: firstResult,
|
|
138
|
+
maxresults: pageSize,
|
|
139
|
+
entitytype: "execution",
|
|
140
|
+
order: "testcaseId",
|
|
141
|
+
isascorder: true,
|
|
142
|
+
is_cfield: false,
|
|
143
|
+
releaseid: String(releaseId),
|
|
144
|
+
projectid: "",
|
|
145
|
+
word: query,
|
|
146
|
+
zql: true,
|
|
147
|
+
isOld: false,
|
|
148
|
+
});
|
|
149
|
+
const page = response[0]?.results || response.results || [];
|
|
150
|
+
|
|
151
|
+
if (!Array.isArray(page) || page.length === 0) break;
|
|
152
|
+
|
|
153
|
+
executions.push(...page);
|
|
154
|
+
firstResult += page.length;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return [...new Map(executions.map(execution => [execution.id, execution])).values()];
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
async PUT(path, params, body) {
|
|
117
161
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
118
162
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -163,22 +207,36 @@ export class QualityGates {
|
|
|
163
207
|
|
|
164
208
|
// ─── Gate 2: Test Plan Analysis ─────────────────────────────────────────────
|
|
165
209
|
|
|
166
|
-
async testPlanAnalysisGate(projectId, releaseId) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
async testPlanAnalysisGate(projectId, releaseId, options = {}) {
|
|
211
|
+
const { query } = options;
|
|
212
|
+
let totalTestcases;
|
|
213
|
+
let mappedRequirements;
|
|
214
|
+
let totalRequirements;
|
|
215
|
+
let executions;
|
|
216
|
+
|
|
217
|
+
if (query) {
|
|
218
|
+
const [matchingTestcases, matchingExecutions] = await Promise.all([
|
|
219
|
+
this.searchTestCases(projectId, releaseId, {
|
|
220
|
+
query,
|
|
221
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
222
|
+
}),
|
|
223
|
+
this.searchExecutionsByZql(releaseId, query),
|
|
224
|
+
]);
|
|
225
|
+
totalTestcases = matchingTestcases.results.length;
|
|
226
|
+
executions = matchingExecutions;
|
|
227
|
+
} else {
|
|
228
|
+
const summary = await this.GET(`/summary/release/${releaseId}`, { isHideCycleEnabled: false });
|
|
229
|
+
totalTestcases = summary.testcase?.totalTestcaseCount || 0;
|
|
230
|
+
mappedRequirements = summary.requirement?.mappedRequirementCount || 0;
|
|
231
|
+
totalRequirements = summary.requirement?.totalRequirementCount || 0;
|
|
232
|
+
const executionData = await this.GET("/execution", {
|
|
233
|
+
releaseid: releaseId,
|
|
234
|
+
offset: 0,
|
|
235
|
+
pagesize: 10000,
|
|
236
|
+
includeanyoneuser: true,
|
|
237
|
+
});
|
|
238
|
+
executions = executionData.results || executionData || [];
|
|
239
|
+
}
|
|
182
240
|
const totalExecutions = Array.isArray(executions) ? executions.length : 0;
|
|
183
241
|
|
|
184
242
|
// Count assigned executions
|
|
@@ -223,6 +281,7 @@ export class QualityGates {
|
|
|
223
281
|
gate: "Test Plan Analysis",
|
|
224
282
|
projectId,
|
|
225
283
|
releaseId,
|
|
284
|
+
query: query || undefined,
|
|
226
285
|
analysis: {
|
|
227
286
|
testcasePlanning: {
|
|
228
287
|
totalTestcases,
|
|
@@ -234,7 +293,7 @@ export class QualityGates {
|
|
|
234
293
|
assignedExecutions,
|
|
235
294
|
percentage: executionAssignmentPct,
|
|
236
295
|
},
|
|
237
|
-
requirementCoverage: {
|
|
296
|
+
requirementCoverage: query ? undefined : {
|
|
238
297
|
totalRequirements,
|
|
239
298
|
mappedRequirements,
|
|
240
299
|
percentage: totalRequirements > 0 ? Math.round((mappedRequirements / totalRequirements) * 100 * 100) / 100 : 0,
|
|
@@ -1031,53 +1090,65 @@ export class QualityGates {
|
|
|
1031
1090
|
async searchTestCases(projectId, releaseId, options = {}) {
|
|
1032
1091
|
const { query = '', status, priority, limit = 50 } = options;
|
|
1033
1092
|
|
|
1034
|
-
// Get test cases
|
|
1035
|
-
const params = {
|
|
1036
|
-
projectId: projectId,
|
|
1037
|
-
releaseId: releaseId,
|
|
1038
|
-
offset: 0,
|
|
1039
|
-
maxRecords: 500,
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
if (query) params.word = query;
|
|
1043
|
-
|
|
1044
1093
|
let testcases = [];
|
|
1045
|
-
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1094
|
+
if (query) {
|
|
1095
|
+
const pageSize = 5000;
|
|
1096
|
+
let firstResult = 0;
|
|
1097
|
+
|
|
1098
|
+
while (true) {
|
|
1099
|
+
const tcData = await this.POSTv3("/advancesearch/zql", {
|
|
1100
|
+
firstresult: firstResult,
|
|
1101
|
+
maxresults: pageSize,
|
|
1102
|
+
entitytype: "testcase",
|
|
1103
|
+
order: "orderId",
|
|
1104
|
+
isascorder: true,
|
|
1105
|
+
is_cfield: false,
|
|
1106
|
+
releaseid: String(releaseId),
|
|
1107
|
+
projectid: String(projectId),
|
|
1108
|
+
word: query,
|
|
1109
|
+
zql: true,
|
|
1110
|
+
isOld: false,
|
|
1111
|
+
});
|
|
1112
|
+
const page = tcData[0]?.results || tcData.results || [];
|
|
1113
|
+
|
|
1114
|
+
if (!Array.isArray(page) || page.length === 0) break;
|
|
1115
|
+
|
|
1116
|
+
testcases.push(...page);
|
|
1117
|
+
firstResult += page.length;
|
|
1052
1118
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1119
|
+
testcases = [...new Map(testcases.map(testcase => [testcase.testcase?.id || testcase.id, testcase])).values()];
|
|
1120
|
+
} else {
|
|
1121
|
+
const params = {
|
|
1122
|
+
projectId,
|
|
1123
|
+
releaseId,
|
|
1124
|
+
offset: 0,
|
|
1125
|
+
maxRecords: 500,
|
|
1126
|
+
};
|
|
1055
1127
|
try {
|
|
1056
|
-
const tcData = await this.GET("/testcase", params);
|
|
1128
|
+
const tcData = await this.GET("/testcase/tree", params);
|
|
1057
1129
|
testcases = tcData.results || tcData || [];
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
results
|
|
1065
|
-
}
|
|
1130
|
+
if (!Array.isArray(testcases)) {
|
|
1131
|
+
testcases = this.flattenTestcaseTree(tcData);
|
|
1132
|
+
}
|
|
1133
|
+
} catch (e) {
|
|
1134
|
+
try {
|
|
1135
|
+
const tcData = await this.GET("/testcase", params);
|
|
1136
|
+
testcases = tcData.results || tcData || [];
|
|
1137
|
+
} catch (e2) {
|
|
1138
|
+
return {
|
|
1139
|
+
tool: "Search Test Cases",
|
|
1140
|
+
projectId,
|
|
1141
|
+
releaseId,
|
|
1142
|
+
error: "Unable to fetch test cases",
|
|
1143
|
+
results: [],
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1066
1146
|
}
|
|
1067
1147
|
}
|
|
1068
1148
|
|
|
1069
1149
|
// Filter results
|
|
1070
1150
|
let filtered = testcases;
|
|
1071
1151
|
|
|
1072
|
-
if (query) {
|
|
1073
|
-
const q = query.toLowerCase();
|
|
1074
|
-
filtered = filtered.filter(tc =>
|
|
1075
|
-
(tc.name || '').toLowerCase().includes(q) ||
|
|
1076
|
-
(tc.testcaseKey || tc.alternateId || '').toLowerCase().includes(q) ||
|
|
1077
|
-
(tc.description || '').toLowerCase().includes(q)
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1152
|
if (status) {
|
|
1082
1153
|
filtered = filtered.filter(tc =>
|
|
1083
1154
|
(tc.status || '').toLowerCase() === status.toLowerCase()
|
|
@@ -1091,17 +1162,20 @@ export class QualityGates {
|
|
|
1091
1162
|
}
|
|
1092
1163
|
|
|
1093
1164
|
// Map to clean output
|
|
1094
|
-
const results = filtered.slice(0, limit).map(tc =>
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1165
|
+
const results = filtered.slice(0, limit).map(tc => {
|
|
1166
|
+
const testcase = tc.testcase || tc;
|
|
1167
|
+
return {
|
|
1168
|
+
id: testcase.id,
|
|
1169
|
+
key: testcase.testcaseKey || testcase.alternateId || testcase.externalId,
|
|
1170
|
+
name: testcase.name,
|
|
1171
|
+
status: testcase.status,
|
|
1172
|
+
priority: testcase.priority,
|
|
1173
|
+
automated: testcase.automated || testcase.isAutomated || false,
|
|
1101
1174
|
folder: tc.folderPath || tc.tcrCatalogTreeId?.name,
|
|
1102
|
-
estimatedTime:
|
|
1103
|
-
tags:
|
|
1104
|
-
|
|
1175
|
+
estimatedTime: testcase.estimatedTime,
|
|
1176
|
+
tags: testcase.tags || [],
|
|
1177
|
+
};
|
|
1178
|
+
});
|
|
1105
1179
|
|
|
1106
1180
|
return {
|
|
1107
1181
|
tool: "Search Test Cases",
|
|
@@ -1293,9 +1367,10 @@ export class QualityGates {
|
|
|
1293
1367
|
|
|
1294
1368
|
// Fetch all executions for the release
|
|
1295
1369
|
const executionData = await this.GET('/execution', {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1370
|
+
releaseid: releaseId,
|
|
1371
|
+
offset: 0,
|
|
1372
|
+
pagesize: 10000,
|
|
1373
|
+
includeanyoneuser: true,
|
|
1299
1374
|
});
|
|
1300
1375
|
const executions = executionData.results || executionData || [];
|
|
1301
1376
|
const total = executions.length;
|