zephyr-enterprise-tools 1.2.1 → 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/mcp-server.js +29 -0
- package/package.json +1 -1
- package/zephyr-enterprise-tools.js +93 -0
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-enterprise-tools",
|
|
3
|
-
"version": "1.2.
|
|
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
6
|
"types": "zephyr-enterprise-tools.d.ts",
|
|
@@ -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
|