Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions mcpgateway/admin_ui/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ export const extractKPIData = function (data) {

const avgResponseTime =
totalExecutions > 0 && weightedResponseSum > 0
? weightedResponseSum / totalExecutions
? (weightedResponseSum / totalExecutions) * 1000
: null;

const successRate =
Expand Down Expand Up @@ -1142,12 +1142,12 @@ item.executionCount || item.execution_count || item.executions || 0,
);
row.appendChild(execCell);

// Avg Response Time
// Avg Response Time (API returns seconds; convert to ms for display)
const avgTimeCell = document.createElement("td");
avgTimeCell.className =
"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300 sm:px-6 sm:py-4";
const avgTime = item.avg_response_time || item.avgResponseTime;
avgTimeCell.textContent = avgTime ? `${Math.round(avgTime)}ms` : "N/A";
const avgTime = item.avg_response_time ?? item.avgResponseTime;
avgTimeCell.textContent = avgTime != null ? `${Math.round(Number(avgTime) * 1000)}ms` : "N/A";
row.appendChild(avgTimeCell);

// Success Rate
Expand Down Expand Up @@ -1489,12 +1489,12 @@ export const updateTableRows = function (
);
row.appendChild(execCell);

// Avg Response Time
// Avg Response Time (API returns seconds; convert to ms for display)
const avgTimeCell = document.createElement("td");
avgTimeCell.className =
"px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300 sm:px-6 sm:py-4";
const avgTime = item.avg_response_time || item.avgResponseTime;
avgTimeCell.textContent = avgTime ? `${Math.round(avgTime)}ms` : "N/A";
const avgTime = item.avg_response_time ?? item.avgResponseTime;
avgTimeCell.textContent = avgTime != null ? `${Math.round(Number(avgTime) * 1000)}ms` : "N/A";
row.appendChild(avgTimeCell);

// Success Rate
Expand Down Expand Up @@ -1554,8 +1554,8 @@ item.execution_count ||
item.executions ||
0,
),
item.avg_response_time || item.avgResponseTime
? `${Math.round(item.avg_response_time || item.avgResponseTime)}ms`
(item.avg_response_time ?? item.avgResponseTime) != null
? `${Math.round((item.avg_response_time ?? item.avgResponseTime) * 1000)}ms`
: "N/A",
`${calculateSuccessRate(item)}%`,
formatLastUsed(item.last_execution || item.lastExecution),
Expand Down Expand Up @@ -1773,7 +1773,20 @@ export const createMetricsCard = function (title, metrics) {

const valueSpan = document.createElement("span");
valueSpan.className = "font-medium dark:text-gray-200";
valueSpan.textContent = value === "N/A" ? "N/A" : String(value);
let displayValue;
if (value === "N/A") {
displayValue = "N/A";
} else if (metric.key === "avgResponseTime") {
const ms = Number(value);
displayValue = isNaN(ms) ? "N/A" : `${(ms * 1000).toFixed(1)} ms`;
} else if (metric.key === "lastExecutionTime") {
displayValue = typeof value === "string" && value.includes("T")
? value.slice(0, 16).replace("T", " ")
: String(value);
} else {
displayValue = String(value);
}
valueSpan.textContent = displayValue;

metricRow.appendChild(label);
metricRow.appendChild(valueSpan);
Expand Down
14 changes: 9 additions & 5 deletions mcpgateway/templates/metrics_top_performers_partial.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,27 @@
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{% set avg_response = item.get('avgResponseTime') %}
{% if avg_response is not none and avg_response != 'N/A' %}
{{ "%.0f"|format(avg_response|float) }}ms
{% if avg_response is not none %}
{{ "%.0f"|format((avg_response|float) * 1000) }}ms
{% else %}
N/A
{% endif %}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm">
{% set success_rate = item.get('successRate', 0) or 0 %}
{% set success_rate = item.get('successRate') %}
{% if success_rate is not none %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold
{% if success_rate >= 95 %}bg-green-100 text-green-800{% elif success_rate >= 80 %}bg-yellow-100 text-yellow-800{% else %}bg-red-100 text-red-800{% endif %}">
{{ "%.1f"|format(success_rate) }}%
</span>
{% else %}
N/A
{% endif %}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{% set last_exec = item.get('lastExecution') %}
{% if last_exec and last_exec != 'Never' %}
{{ last_exec }}
{% if last_exec %}
{{ last_exec[:16] | replace('T', ' ') }}
{% else %}
Never
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions mcpgateway/utils/metrics_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def build_top_performers(results: List) -> List[TopPerformer]:
id=result.id,
name=result.name,
execution_count=result.execution_count or 0,
avg_response_time=float(result.avg_response_time) if result.avg_response_time else None,
success_rate=float(result.success_rate) if result.success_rate else None,
avg_response_time=float(result.avg_response_time) if result.avg_response_time is not None else None,
success_rate=float(result.success_rate) if result.success_rate is not None else None,
last_execution=result.last_execution,
)
for result in results
Expand Down
6 changes: 3 additions & 3 deletions tests/js/admin-parsing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ describe("extractKPIData", () => {
expect(result.totalExecutions).toBe(100);
expect(result.successRate).toBe(90);
expect(result.errorRate).toBe(10);
expect(result.avgResponseTime).toBeCloseTo(1.5, 1);
expect(result.avgResponseTime).toBeCloseTo(1500, 1);
});

test("aggregates across multiple categories", () => {
Expand Down Expand Up @@ -362,8 +362,8 @@ describe("extractKPIData", () => {
},
};
const result = extractKPIData(data);
// Weighted avg = (100*2.0 + 100*4.0) / 200 = 3.0
expect(result.avgResponseTime).toBeCloseTo(3.0, 1);
// Weighted avg = (100*2.0 + 100*4.0) / 200 = 3.0s = 3000ms
expect(result.avgResponseTime).toBeCloseTo(3000, 1);
});

test("ignores N/A response time values", () => {
Expand Down
28 changes: 27 additions & 1 deletion tests/unit/js/metrics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ describe("extractKPIData", () => {
};

const kpi = extractKPIData(data);
expect(kpi.avgResponseTime).toBe(150); // Weighted average
expect(kpi.avgResponseTime).toBe(150000); // Weighted average (seconds ร— 1000 โ†’ ms)
});

test("handles null avgResponseTime", () => {
Expand Down Expand Up @@ -825,6 +825,7 @@ describe("createPerformanceCard", () => {
expect(card).toBeInstanceOf(HTMLElement);
consoleError.mockRestore();
});

});

describe("createRecentActivitySection", () => {
Expand Down Expand Up @@ -904,6 +905,31 @@ describe("createMetricsCard", () => {
const card = createMetricsCard("Prompts", {});
expect(card.textContent).toContain("N/A");
});

test("converts avgResponseTime from seconds to milliseconds", () => {
const metrics = { avgResponseTime: 1.5 };
const card = createMetricsCard("Tools", metrics);
expect(card.textContent).toContain("1500.0 ms");
});

test("formats lastExecutionTime as human-readable date", () => {
const metrics = { lastExecutionTime: "2026-03-13T06:00:00.000000" };
const card = createMetricsCard("Tools", metrics);
expect(card.textContent).toContain("2026-03-13 06:00");
});

test("displays N/A for null avgResponseTime", () => {
const metrics = { avgResponseTime: null };
const card = createMetricsCard("Tools", metrics);
// avgResponseTime null โ†’ value resolves to "N/A" via ?? chain
expect(card.textContent).toContain("N/A");
});

test("preserves zero avgResponseTime", () => {
const metrics = { avgResponseTime: 0 };
const card = createMetricsCard("Tools", metrics);
expect(card.textContent).toContain("0.0 ms");
});
});

// ===================================================================
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/mcpgateway/utils/test_metrics_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,20 @@ def test_build_top_performers_handles_missing_values():
assert performers[0].avg_response_time is None
assert performers[0].success_rate is None
assert performers[0].last_execution is None


def test_build_top_performers_preserves_zero_values():
"""Zero avg_response_time and success_rate must not be coerced to None."""
result = SimpleNamespace(
id=3,
name="zero-tool",
execution_count=10,
avg_response_time=0.0,
success_rate=0.0,
last_execution=None,
)

performers = build_top_performers([result])

assert performers[0].avg_response_time == 0.0
assert performers[0].success_rate == 0.0
Loading
โšก