feat: persist and display exchange properties from agent
Add support for exchange properties sent by the agent alongside headers. Properties flow through the same pipeline as headers: ClickHouse columns (input_properties, output_properties) on both executions and processor_executions tables, MergedExecution record, ChunkAccumulator extraction, DetailService snapshot, and REST API response. UI adds a Properties tab next to Headers in the process diagram detail panel, with the same input/output split table layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ public class DetailService {
|
||||
exec.diagramContentHash(), processors,
|
||||
exec.inputBody(), exec.outputBody(),
|
||||
exec.inputHeaders(), exec.outputHeaders(),
|
||||
exec.inputProperties(), exec.outputProperties(),
|
||||
parseAttributes(exec.attributes()),
|
||||
exec.errorType(), exec.errorCategory(),
|
||||
exec.rootCauseType(), exec.rootCauseMessage(),
|
||||
@@ -54,26 +55,23 @@ public class DetailService {
|
||||
|
||||
public Optional<Map<String, String>> getProcessorSnapshot(String executionId, String processorId) {
|
||||
return executionStore.findProcessorById(executionId, processorId)
|
||||
.map(p -> {
|
||||
Map<String, String> snapshot = new LinkedHashMap<>();
|
||||
if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody());
|
||||
if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody());
|
||||
if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders());
|
||||
if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders());
|
||||
return snapshot;
|
||||
});
|
||||
.map(DetailService::snapshotFromRecord);
|
||||
}
|
||||
|
||||
public Optional<Map<String, String>> getProcessorSnapshotBySeq(String executionId, int seq) {
|
||||
return executionStore.findProcessorBySeq(executionId, seq)
|
||||
.map(p -> {
|
||||
Map<String, String> snapshot = new LinkedHashMap<>();
|
||||
if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody());
|
||||
if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody());
|
||||
if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders());
|
||||
if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders());
|
||||
return snapshot;
|
||||
});
|
||||
.map(DetailService::snapshotFromRecord);
|
||||
}
|
||||
|
||||
private static Map<String, String> snapshotFromRecord(ProcessorRecord p) {
|
||||
Map<String, String> snapshot = new LinkedHashMap<>();
|
||||
if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody());
|
||||
if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody());
|
||||
if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders());
|
||||
if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders());
|
||||
if (p.inputProperties() != null) snapshot.put("inputProperties", p.inputProperties());
|
||||
if (p.outputProperties() != null) snapshot.put("outputProperties", p.outputProperties());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/** Parse the raw processor tree JSON stored alongside the execution. */
|
||||
|
||||
@@ -47,6 +47,8 @@ public record ExecutionDetail(
|
||||
String outputBody,
|
||||
String inputHeaders,
|
||||
String outputHeaders,
|
||||
String inputProperties,
|
||||
String outputProperties,
|
||||
Map<String, String> attributes,
|
||||
String errorType,
|
||||
String errorCategory,
|
||||
|
||||
@@ -201,6 +201,8 @@ public class ChunkAccumulator {
|
||||
extractBody(envelope.getOutputSnapshot()),
|
||||
extractHeaders(envelope.getInputSnapshot()),
|
||||
extractHeaders(envelope.getOutputSnapshot()),
|
||||
extractProperties(envelope.getInputSnapshot()),
|
||||
extractProperties(envelope.getOutputSnapshot()),
|
||||
serializeAttributes(envelope.getAttributes()),
|
||||
envelope.getTraceId(),
|
||||
envelope.getSpanId(),
|
||||
@@ -226,6 +228,16 @@ public class ChunkAccumulator {
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractProperties(ExchangeSnapshot snapshot) {
|
||||
if (snapshot == null || snapshot.getProperties() == null) return "";
|
||||
try {
|
||||
return MAPPER.writeValueAsString(snapshot.getProperties());
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Failed to serialize snapshot properties", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String serializeAttributes(Map<String, String> attributes) {
|
||||
if (attributes == null || attributes.isEmpty()) {
|
||||
return "{}";
|
||||
|
||||
@@ -87,17 +87,21 @@ public class IngestionService {
|
||||
String outputBody = null;
|
||||
String inputHeaders = null;
|
||||
String outputHeaders = null;
|
||||
String inputProperties = null;
|
||||
String outputProperties = null;
|
||||
|
||||
ExchangeSnapshot inputSnapshot = exec.getInputSnapshot();
|
||||
if (inputSnapshot != null) {
|
||||
inputBody = truncateBody(inputSnapshot.getBody());
|
||||
inputHeaders = toJson(inputSnapshot.getHeaders());
|
||||
inputProperties = toJson(inputSnapshot.getProperties());
|
||||
}
|
||||
|
||||
ExchangeSnapshot outputSnapshot = exec.getOutputSnapshot();
|
||||
if (outputSnapshot != null) {
|
||||
outputBody = truncateBody(outputSnapshot.getBody());
|
||||
outputHeaders = toJson(outputSnapshot.getHeaders());
|
||||
outputProperties = toJson(outputSnapshot.getProperties());
|
||||
}
|
||||
|
||||
boolean hasTraceData = hasAnyTraceData(exec.getProcessors());
|
||||
@@ -118,6 +122,7 @@ public class IngestionService {
|
||||
diagramHash,
|
||||
exec.getEngineLevel(),
|
||||
inputBody, outputBody, inputHeaders, outputHeaders,
|
||||
inputProperties, outputProperties,
|
||||
toJson(exec.getAttributes()),
|
||||
exec.getErrorType(), exec.getErrorCategory(),
|
||||
exec.getRootCauseType(), exec.getRootCauseMessage(),
|
||||
@@ -153,6 +158,7 @@ public class IngestionService {
|
||||
p.getErrorMessage(), p.getErrorStackTrace(),
|
||||
truncateBody(p.getInputBody()), truncateBody(p.getOutputBody()),
|
||||
toJson(p.getInputHeaders()), toJson(p.getOutputHeaders()),
|
||||
null, null, // inputProperties, outputProperties (not on ProcessorExecution)
|
||||
toJson(p.getAttributes()),
|
||||
null, null, null, null, null,
|
||||
p.getResolvedEndpointUri(),
|
||||
|
||||
@@ -32,6 +32,8 @@ public record MergedExecution(
|
||||
String outputBody,
|
||||
String inputHeaders,
|
||||
String outputHeaders,
|
||||
String inputProperties,
|
||||
String outputProperties,
|
||||
String attributes,
|
||||
String traceId,
|
||||
String spanId,
|
||||
|
||||
@@ -25,6 +25,7 @@ public interface ExecutionStore {
|
||||
String errorMessage, String errorStacktrace, String diagramContentHash,
|
||||
String engineLevel,
|
||||
String inputBody, String outputBody, String inputHeaders, String outputHeaders,
|
||||
String inputProperties, String outputProperties,
|
||||
String attributes,
|
||||
String errorType, String errorCategory,
|
||||
String rootCauseType, String rootCauseMessage,
|
||||
@@ -41,6 +42,7 @@ public interface ExecutionStore {
|
||||
Instant startTime, Instant endTime, Long durationMs,
|
||||
String errorMessage, String errorStacktrace,
|
||||
String inputBody, String outputBody, String inputHeaders, String outputHeaders,
|
||||
String inputProperties, String outputProperties,
|
||||
String attributes,
|
||||
Integer loopIndex, Integer loopSize,
|
||||
Integer splitIndex, Integer splitSize,
|
||||
|
||||
@@ -27,7 +27,7 @@ class TreeReconstructionTest {
|
||||
"exec-1", id, type,
|
||||
"default", "route1", depth, parentId,
|
||||
status, NOW, NOW, 10L,
|
||||
null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null
|
||||
@@ -118,7 +118,7 @@ class TreeReconstructionTest {
|
||||
return new ProcessorRecord(
|
||||
"exec-1", id, type, "app", "route1",
|
||||
0, null, status, NOW, NOW, 10L,
|
||||
null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
seq, parentSeq, iteration, iterationSize, null, null
|
||||
|
||||
Reference in New Issue
Block a user