feat: trace data indicators, inline tap config, and detail tab gating
All checks were successful
CI / build (push) Successful in 1m46s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m57s

Trace data visibility:
- ProcessorNode now includes hasTraceData flag computed from captured
  body/headers during tree conversion
- ConfigBadge shows teal for tracing configured, green when data captured
- Search results show green footprints icon for exchanges with trace data
- New has_trace_data column on executions table (V11 migration with backfill)
- OpenSearch documents and ExecutionSummary include the flag

Inline tap configuration:
- Extracted reusable TapConfigModal component from RouteDetail
- Diagram context menu opens tap modal inline instead of navigating away
- Toggle-trace action works immediately with toast feedback
- Modal closes only on ESC, Cancel, Save, or Delete (not backdrop click)

Detail panel tab gating:
- Headers, Input, Output tabs disabled when no data is available
- Works at both exchange and processor level
- Falls back to Info tab when active tab becomes empty

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-29 13:08:58 +02:00
parent 5103f40196
commit 3d71345181
22 changed files with 568 additions and 41 deletions

View File

@@ -80,6 +80,8 @@ public class DetailService {
if (executions == null) return List.of();
List<ProcessorNode> result = new ArrayList<>();
for (ProcessorExecution p : executions) {
boolean hasTrace = p.getInputBody() != null || p.getOutputBody() != null
|| p.getInputHeaders() != null || p.getOutputHeaders() != null;
ProcessorNode node = new ProcessorNode(
p.getProcessorId(), p.getProcessorType(),
p.getStatus() != null ? p.getStatus().name() : null,
@@ -94,7 +96,8 @@ public class DetailService {
p.getErrorType(), p.getErrorCategory(),
p.getRootCauseType(), p.getRootCauseMessage(),
p.getErrorHandlerType(), p.getCircuitBreakerState(),
p.getFallbackTriggered()
p.getFallbackTriggered(),
hasTrace
);
for (ProcessorNode child : convertProcessors(p.getChildren())) {
node.addChild(child);
@@ -113,6 +116,8 @@ public class DetailService {
Map<String, ProcessorNode> nodeMap = new LinkedHashMap<>();
for (ProcessorRecord p : processors) {
boolean hasTrace = p.inputBody() != null || p.outputBody() != null
|| p.inputHeaders() != null || p.outputHeaders() != null;
nodeMap.put(p.processorId(), new ProcessorNode(
p.processorId(), p.processorType(), p.status(),
p.startTime(), p.endTime(),
@@ -126,7 +131,8 @@ public class DetailService {
p.errorType(), p.errorCategory(),
p.rootCauseType(), p.rootCauseMessage(),
p.errorHandlerType(), p.circuitBreakerState(),
p.fallbackTriggered()
p.fallbackTriggered(),
hasTrace
));
}

View File

@@ -35,6 +35,7 @@ public final class ProcessorNode {
private final String errorHandlerType;
private final String circuitBreakerState;
private final Boolean fallbackTriggered;
private final boolean hasTraceData;
private final List<ProcessorNode> children;
public ProcessorNode(String processorId, String processorType, String status,
@@ -48,7 +49,8 @@ public final class ProcessorNode {
String errorType, String errorCategory,
String rootCauseType, String rootCauseMessage,
String errorHandlerType, String circuitBreakerState,
Boolean fallbackTriggered) {
Boolean fallbackTriggered,
boolean hasTraceData) {
this.processorId = processorId;
this.processorType = processorType;
this.status = status;
@@ -71,6 +73,7 @@ public final class ProcessorNode {
this.errorHandlerType = errorHandlerType;
this.circuitBreakerState = circuitBreakerState;
this.fallbackTriggered = fallbackTriggered;
this.hasTraceData = hasTraceData;
this.children = new ArrayList<>();
}
@@ -100,5 +103,6 @@ public final class ProcessorNode {
public String getErrorHandlerType() { return errorHandlerType; }
public String getCircuitBreakerState() { return circuitBreakerState; }
public Boolean getFallbackTriggered() { return fallbackTriggered; }
public boolean isHasTraceData() { return hasTraceData; }
public List<ProcessorNode> getChildren() { return List.copyOf(children); }
}

View File

@@ -79,7 +79,7 @@ public class SearchIndexer implements SearchIndexerStats {
exec.status(), exec.correlationId(), exec.exchangeId(),
exec.startTime(), exec.endTime(), exec.durationMs(),
exec.errorMessage(), exec.errorStacktrace(), processorDocs,
exec.attributes()));
exec.attributes(), exec.hasTraceData()));
indexedCount.incrementAndGet();
lastIndexedAt = Instant.now();

View File

@@ -100,6 +100,8 @@ public class IngestionService {
outputHeaders = toJson(outputSnapshot.getHeaders());
}
boolean hasTraceData = hasAnyTraceData(exec.getProcessors());
return new ExecutionRecord(
exec.getExchangeId(), exec.getRouteId(), agentId, applicationName,
exec.getStatus() != null ? exec.getStatus().name() : "RUNNING",
@@ -114,10 +116,20 @@ public class IngestionService {
exec.getErrorType(), exec.getErrorCategory(),
exec.getRootCauseType(), exec.getRootCauseMessage(),
exec.getTraceId(), exec.getSpanId(),
toJsonObject(exec.getProcessors())
toJsonObject(exec.getProcessors()),
hasTraceData
);
}
private static boolean hasAnyTraceData(List<ProcessorExecution> processors) {
if (processors == null) return false;
for (ProcessorExecution p : processors) {
if (p.getInputBody() != null || p.getOutputBody() != null) return true;
if (hasAnyTraceData(p.getChildren())) return true;
}
return false;
}
private List<ProcessorRecord> flattenProcessors(
List<ProcessorExecution> processors, String executionId,
java.time.Instant execStartTime, String applicationName, String routeId,

View File

@@ -33,6 +33,7 @@ public record ExecutionSummary(
String errorMessage,
String diagramContentHash,
String highlight,
Map<String, String> attributes
Map<String, String> attributes,
boolean hasTraceData
) {
}

View File

@@ -29,7 +29,8 @@ public interface ExecutionStore {
String errorType, String errorCategory,
String rootCauseType, String rootCauseMessage,
String traceId, String spanId,
String processorsJson
String processorsJson,
boolean hasTraceData
) {}
record ProcessorRecord(

View File

@@ -9,7 +9,8 @@ public record ExecutionDocument(
Instant startTime, Instant endTime, Long durationMs,
String errorMessage, String errorStacktrace,
List<ProcessorDoc> processors,
String attributes
String attributes,
boolean hasTraceData
) {
public record ProcessorDoc(
String processorId, String processorType, String status,