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

@@ -63,7 +63,7 @@ export function DetailPanel({
? !!selectedProcessor.errorMessage
: !!executionDetail.errorMessage;
// Fetch snapshot for body tabs when a processor is selected
// Fetch snapshot for body/headers tabs when a processor is selected
const snapshotQuery = useProcessorSnapshotById(
selectedProcessor ? executionId : null,
selectedProcessor?.processorId ?? null,
@@ -72,15 +72,33 @@ export function DetailPanel({
// Determine body content for Input/Output tabs
let inputBody: string | undefined;
let outputBody: string | undefined;
let hasHeaders = false;
if (selectedProcessor && snapshotQuery.data) {
inputBody = snapshotQuery.data.inputBody;
outputBody = snapshotQuery.data.outputBody;
hasHeaders = !!(snapshotQuery.data.inputHeaders || snapshotQuery.data.outputHeaders);
} else if (selectedProcessor && snapshotQuery.isLoading) {
// Still loading — keep tabs enabled
hasHeaders = true;
inputBody = undefined;
outputBody = undefined;
} else if (!selectedProcessor) {
inputBody = executionDetail.inputBody;
outputBody = executionDetail.outputBody;
hasHeaders = !!(executionDetail.inputHeaders || executionDetail.outputHeaders);
}
const hasInput = !!inputBody;
const hasOutput = !!outputBody;
// If active tab becomes disabled, fall back to info
useEffect(() => {
if (activeTab === 'headers' && !hasHeaders) setActiveTab('info');
if (activeTab === 'input' && !hasInput) setActiveTab('info');
if (activeTab === 'output' && !hasOutput) setActiveTab('info');
}, [hasHeaders, hasInput, hasOutput, activeTab]);
// Header display
const headerName = selectedProcessor ? selectedProcessor.processorType : 'Exchange';
const headerStatus = selectedProcessor ? selectedProcessor.status : executionDetail.status;
@@ -103,7 +121,10 @@ export function DetailPanel({
<div className={styles.tabBar}>
{TABS.map((tab) => {
const isActive = activeTab === tab.key;
const isDisabled = tab.key === 'config';
const isDisabled = tab.key === 'config'
|| (tab.key === 'headers' && !hasHeaders)
|| (tab.key === 'input' && !hasInput)
|| (tab.key === 'output' && !hasOutput);
const isError = tab.key === 'error' && hasError;
const isErrorGrayed = tab.key === 'error' && !hasError;

View File

@@ -59,7 +59,7 @@ function buildOverlay(
status: proc.status as 'COMPLETED' | 'FAILED',
durationMs: proc.durationMs ?? 0,
subRouteFailed: subRouteFailed || undefined,
hasTraceData: true,
hasTraceData: !!proc.hasTraceData,
});
// Recurse into children