Fix status filter OR logic and add P99/active stats endpoint
All checks were successful
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 47s
CI / deploy (push) Successful in 29s

Status filter now parses comma-separated values into SQL IN clause
instead of exact match, so filtering by multiple statuses works.

Added GET /api/v1/search/stats returning P99 latency (last hour) and
active execution count, wired into the UI stat cards with 10s polling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-13 17:34:11 +01:00
parent c1f2ddb3f5
commit 3f98467ba5
8 changed files with 88 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
package com.cameleer3.server.app.controller;
import com.cameleer3.server.core.search.ExecutionStats;
import com.cameleer3.server.core.search.ExecutionSummary;
import com.cameleer3.server.core.search.SearchRequest;
import com.cameleer3.server.core.search.SearchResult;
@@ -65,4 +66,10 @@ public class SearchController {
@RequestBody SearchRequest request) {
return ResponseEntity.ok(searchService.search(request));
}
@GetMapping("/stats")
@Operation(summary = "Aggregate execution stats (P99 latency, active count)")
public ResponseEntity<ExecutionStats> stats() {
return ResponseEntity.ok(searchService.stats());
}
}

View File

@@ -1,5 +1,6 @@
package com.cameleer3.server.app.search;
import com.cameleer3.server.core.search.ExecutionStats;
import com.cameleer3.server.core.search.ExecutionSummary;
import com.cameleer3.server.core.search.SearchEngine;
import com.cameleer3.server.core.search.SearchRequest;
@@ -84,10 +85,33 @@ public class ClickHouseSearchEngine implements SearchEngine {
return result != null ? result : 0L;
}
@Override
public ExecutionStats stats() {
Long p99 = jdbcTemplate.queryForObject(
"SELECT quantile(0.99)(duration_ms) FROM route_executions " +
"WHERE start_time >= now() - INTERVAL 1 HOUR",
Long.class);
Long active = jdbcTemplate.queryForObject(
"SELECT count() FROM route_executions WHERE status = 'RUNNING'",
Long.class);
return new ExecutionStats(
p99 != null ? p99 : 0L,
active != null ? active : 0L);
}
private void buildWhereClause(SearchRequest req, List<String> conditions, List<Object> params) {
if (req.status() != null && !req.status().isBlank()) {
conditions.add("status = ?");
params.add(req.status());
String[] statuses = req.status().split(",");
if (statuses.length == 1) {
conditions.add("status = ?");
params.add(statuses[0].trim());
} else {
String placeholders = String.join(", ", java.util.Collections.nCopies(statuses.length, "?"));
conditions.add("status IN (" + placeholders + ")");
for (String s : statuses) {
params.add(s.trim());
}
}
}
if (req.timeFrom() != null) {
conditions.add("start_time >= ?");