feat: add environment filtering across all APIs and UI
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled

Backend: Added optional `environment` query parameter to catalog,
search, stats, timeseries, punchcard, top-errors, logs, and agents
endpoints. ClickHouse queries filter by environment when specified
(literal SQL for AggregatingMergeTree, ? binds for raw tables).
StatsStore interface methods all accept environment parameter.

UI: Added EnvironmentSelector component (compact native select).
LayoutShell extracts distinct environments from agent data and
passes selected environment to catalog and agent queries via URL
search param (?env=). TopBar shows current environment label.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-04 15:42:26 +02:00
parent babdc1d7a4
commit 694d0eef59
25 changed files with 439 additions and 160 deletions

View File

@@ -12,6 +12,7 @@ import java.util.List;
* @param instanceId agent instance ID filter
* @param exchangeId Camel exchange ID filter
* @param logger logger name substring filter
* @param environment optional environment filter (e.g. "dev", "staging", "prod")
* @param from inclusive start of time range (required)
* @param to inclusive end of time range (required)
* @param cursor ISO timestamp cursor for keyset pagination
@@ -25,6 +26,7 @@ public record LogSearchRequest(
String instanceId,
String exchangeId,
String logger,
String environment,
Instant from,
Instant to,
String cursor,

View File

@@ -28,6 +28,7 @@ import java.util.List;
* @param limit page size (default 50, max 500)
* @param sortField column to sort by (default: startTime)
* @param sortDir sort direction: asc or desc (default: desc)
* @param environment optional environment filter (e.g. "dev", "staging", "prod")
*/
public record SearchRequest(
String status,
@@ -48,7 +49,8 @@ public record SearchRequest(
int offset,
int limit,
String sortField,
String sortDir
String sortDir,
String environment
) {
private static final int DEFAULT_LIMIT = 50;
@@ -90,7 +92,17 @@ public record SearchRequest(
status, timeFrom, timeTo, durationMin, durationMax, correlationId,
text, textInBody, textInHeaders, textInErrors,
routeId, instanceId, processorType, applicationId, resolvedInstanceIds,
offset, limit, sortField, sortDir
offset, limit, sortField, sortDir, environment
);
}
/** Create a copy with the given environment filter. */
public SearchRequest withEnvironment(String env) {
return new SearchRequest(
status, timeFrom, timeTo, durationMin, durationMax, correlationId,
text, textInBody, textInHeaders, textInErrors,
routeId, instanceId, processorType, applicationId, instanceIds,
offset, limit, sortField, sortDir, env
);
}
}

View File

@@ -30,65 +30,126 @@ public class SearchService {
}
public ExecutionStats stats(Instant from, Instant to) {
return statsStore.stats(from, to);
return statsStore.stats(from, to, null);
}
public ExecutionStats stats(Instant from, Instant to, String environment) {
return statsStore.stats(from, to, environment);
}
public ExecutionStats statsForApp(Instant from, Instant to, String applicationId) {
return statsStore.statsForApp(from, to, applicationId);
return statsStore.statsForApp(from, to, applicationId, null);
}
public ExecutionStats statsForApp(Instant from, Instant to, String applicationId, String environment) {
return statsStore.statsForApp(from, to, applicationId, environment);
}
public ExecutionStats stats(Instant from, Instant to, String routeId, List<String> agentIds) {
return statsStore.statsForRoute(from, to, routeId, agentIds);
return statsStore.statsForRoute(from, to, routeId, agentIds, null);
}
public ExecutionStats stats(Instant from, Instant to, String routeId, List<String> agentIds, String environment) {
return statsStore.statsForRoute(from, to, routeId, agentIds, environment);
}
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount) {
return statsStore.timeseries(from, to, bucketCount);
return statsStore.timeseries(from, to, bucketCount, null);
}
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount, String environment) {
return statsStore.timeseries(from, to, bucketCount, environment);
}
public StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId) {
return statsStore.timeseriesForApp(from, to, bucketCount, applicationId);
return statsStore.timeseriesForApp(from, to, bucketCount, applicationId, null);
}
public StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId, String environment) {
return statsStore.timeseriesForApp(from, to, bucketCount, applicationId, environment);
}
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount,
String routeId, List<String> agentIds) {
return statsStore.timeseriesForRoute(from, to, bucketCount, routeId, agentIds);
return statsStore.timeseriesForRoute(from, to, bucketCount, routeId, agentIds, null);
}
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount,
String routeId, List<String> agentIds, String environment) {
return statsStore.timeseriesForRoute(from, to, bucketCount, routeId, agentIds, environment);
}
// ── Dashboard-specific queries ────────────────────────────────────────
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount) {
return statsStore.timeseriesGroupedByApp(from, to, bucketCount);
return statsStore.timeseriesGroupedByApp(from, to, bucketCount, null);
}
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount, String environment) {
return statsStore.timeseriesGroupedByApp(from, to, bucketCount, environment);
}
public Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to,
int bucketCount, String applicationId) {
return statsStore.timeseriesGroupedByRoute(from, to, bucketCount, applicationId);
return statsStore.timeseriesGroupedByRoute(from, to, bucketCount, applicationId, null);
}
public Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to,
int bucketCount, String applicationId, String environment) {
return statsStore.timeseriesGroupedByRoute(from, to, bucketCount, applicationId, environment);
}
public double slaCompliance(Instant from, Instant to, int thresholdMs,
String applicationId, String routeId) {
return statsStore.slaCompliance(from, to, thresholdMs, applicationId, routeId);
return statsStore.slaCompliance(from, to, thresholdMs, applicationId, routeId, null);
}
public double slaCompliance(Instant from, Instant to, int thresholdMs,
String applicationId, String routeId, String environment) {
return statsStore.slaCompliance(from, to, thresholdMs, applicationId, routeId, environment);
}
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs) {
return statsStore.slaCountsByApp(from, to, defaultThresholdMs);
return statsStore.slaCountsByApp(from, to, defaultThresholdMs, null);
}
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs, String environment) {
return statsStore.slaCountsByApp(from, to, defaultThresholdMs, environment);
}
public Map<String, long[]> slaCountsByRoute(Instant from, Instant to,
String applicationId, int thresholdMs) {
return statsStore.slaCountsByRoute(from, to, applicationId, thresholdMs);
return statsStore.slaCountsByRoute(from, to, applicationId, thresholdMs, null);
}
public Map<String, long[]> slaCountsByRoute(Instant from, Instant to,
String applicationId, int thresholdMs, String environment) {
return statsStore.slaCountsByRoute(from, to, applicationId, thresholdMs, environment);
}
public List<TopError> topErrors(Instant from, Instant to, String applicationId,
String routeId, int limit) {
return statsStore.topErrors(from, to, applicationId, routeId, limit);
return statsStore.topErrors(from, to, applicationId, routeId, limit, null);
}
public List<TopError> topErrors(Instant from, Instant to, String applicationId,
String routeId, int limit, String environment) {
return statsStore.topErrors(from, to, applicationId, routeId, limit, environment);
}
public int activeErrorTypes(Instant from, Instant to, String applicationId) {
return statsStore.activeErrorTypes(from, to, applicationId);
return statsStore.activeErrorTypes(from, to, applicationId, null);
}
public int activeErrorTypes(Instant from, Instant to, String applicationId, String environment) {
return statsStore.activeErrorTypes(from, to, applicationId, environment);
}
public List<StatsStore.PunchcardCell> punchcard(Instant from, Instant to, String applicationId) {
return statsStore.punchcard(from, to, applicationId);
return statsStore.punchcard(from, to, applicationId, null);
}
public List<StatsStore.PunchcardCell> punchcard(Instant from, Instant to, String applicationId, String environment) {
return statsStore.punchcard(from, to, applicationId, environment);
}
}

View File

@@ -11,58 +11,58 @@ import java.util.Map;
public interface StatsStore {
// Global stats (stats_1m_all)
ExecutionStats stats(Instant from, Instant to);
ExecutionStats stats(Instant from, Instant to, String environment);
// Per-app stats (stats_1m_app)
ExecutionStats statsForApp(Instant from, Instant to, String applicationId);
ExecutionStats statsForApp(Instant from, Instant to, String applicationId, String environment);
// Per-route stats (stats_1m_route), optionally scoped to specific agents
ExecutionStats statsForRoute(Instant from, Instant to, String routeId, List<String> agentIds);
ExecutionStats statsForRoute(Instant from, Instant to, String routeId, List<String> agentIds, String environment);
// Per-processor stats (stats_1m_processor)
ExecutionStats statsForProcessor(Instant from, Instant to, String routeId, String processorType);
// Global timeseries
StatsTimeseries timeseries(Instant from, Instant to, int bucketCount);
StatsTimeseries timeseries(Instant from, Instant to, int bucketCount, String environment);
// Per-app timeseries
StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId);
StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId, String environment);
// Per-route timeseries, optionally scoped to specific agents
StatsTimeseries timeseriesForRoute(Instant from, Instant to, int bucketCount,
String routeId, List<String> agentIds);
String routeId, List<String> agentIds, String environment);
// Per-processor timeseries
StatsTimeseries timeseriesForProcessor(Instant from, Instant to, int bucketCount,
String routeId, String processorType);
// Grouped timeseries by application (for L1 dashboard charts)
Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount);
Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount, String environment);
// Grouped timeseries by route within an application (for L2 dashboard charts)
Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to, int bucketCount,
String applicationId);
String applicationId, String environment);
// SLA compliance: % of completed exchanges with duration <= thresholdMs
double slaCompliance(Instant from, Instant to, int thresholdMs,
String applicationId, String routeId);
String applicationId, String routeId, String environment);
// Batch SLA counts by app: {appId -> [compliant, total]}
Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs);
Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs, String environment);
// Batch SLA counts by route within an app: {routeId -> [compliant, total]}
Map<String, long[]> slaCountsByRoute(Instant from, Instant to, String applicationId,
int thresholdMs);
int thresholdMs, String environment);
// Top N errors with velocity trend
List<TopError> topErrors(Instant from, Instant to, String applicationId,
String routeId, int limit);
String routeId, int limit, String environment);
// Count of distinct error types in window
int activeErrorTypes(Instant from, Instant to, String applicationId);
int activeErrorTypes(Instant from, Instant to, String applicationId, String environment);
// Punchcard: aggregate by weekday (0=Sun..6=Sat) x hour (0-23) over last 7 days
List<PunchcardCell> punchcard(Instant from, Instant to, String applicationId);
List<PunchcardCell> punchcard(Instant from, Instant to, String applicationId, String environment);
record PunchcardCell(int weekday, int hour, long totalCount, long failedCount) {}
}