From d9c88166476af914d81f0517ee5a5c6f43727b62 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:29:07 +0100 Subject: [PATCH] feat: add OpenSearch highlight snippets to search results - Add highlight field to ExecutionSummary record - Request highlight fragments from OpenSearch when full-text search is active - Pass matchContext to command palette for display Co-Authored-By: Claude Opus 4.6 (1M context) --- .../server/app/search/OpenSearchIndex.java | 28 ++++++++++++++++++- .../server/core/search/ExecutionSummary.java | 3 +- ui/src/components/LayoutShell.tsx | 1 + 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java index ebd48008..2ad0046a 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java @@ -125,6 +125,10 @@ public class OpenSearchIndex implements SearchIndex { } } + private static final List HIGHLIGHT_FIELDS = List.of( + "error_message", "processors.input_body", "processors.output_body", + "processors.input_headers", "processors.output_headers"); + private org.opensearch.client.opensearch.core.SearchRequest buildSearchRequest( SearchRequest request, int size) { return org.opensearch.client.opensearch.core.SearchRequest.of(b -> { @@ -137,6 +141,17 @@ public class OpenSearchIndex implements SearchIndex { .field(request.sortColumn()) .order("asc".equalsIgnoreCase(request.sortDir()) ? SortOrder.Asc : SortOrder.Desc))); + // Add highlight when full-text search is active + if (request.text() != null && !request.text().isBlank()) { + b.highlight(h -> { + for (String field : HIGHLIGHT_FIELDS) { + h.fields(field, hf -> hf + .fragmentSize(120) + .numberOfFragments(1)); + } + return h; + }); + } return b; }); } @@ -332,7 +347,18 @@ public class OpenSearchIndex implements SearchIndex { src.get("duration_ms") != null ? ((Number) src.get("duration_ms")).longValue() : 0L, (String) src.get("correlation_id"), (String) src.get("error_message"), - null // diagramContentHash not stored in index + null, // diagramContentHash not stored in index + extractHighlight(hit) ); } + + private String extractHighlight(Hit hit) { + if (hit.highlight() == null || hit.highlight().isEmpty()) return null; + for (List fragments : hit.highlight().values()) { + if (fragments != null && !fragments.isEmpty()) { + return fragments.get(0); + } + } + return null; + } } diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java index 04d118fe..f8cd55c0 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java @@ -30,6 +30,7 @@ public record ExecutionSummary( long durationMs, String correlationId, String errorMessage, - String diagramContentHash + String diagramContentHash, + String highlight ) { } diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index 15afcf06..39627f62 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -137,6 +137,7 @@ function LayoutContent() { meta: `${e.routeId} · ${e.applicationName ?? ''} · ${formatDuration(e.durationMs)}`, path: `/exchanges/${e.executionId}`, serverFiltered: true, + matchContext: e.highlight ?? undefined, })); return [...catalogData, ...exchangeItems]; }, [catalogData, exchangeResults]);