feat: add environment filtering across all APIs and UI
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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user