This is an automated email from the ASF dual-hosted git repository.

jongyoul pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 3c3c30331e [MINOR] Refresh search indexes after paragraph edits
3c3c30331e is described below

commit 3c3c30331e93eb7f4bcaa01f6f26758726540c77
Author: Kalyan <[email protected]>
AuthorDate: Thu Sep 10 00:35:03 2026 -0700

    [MINOR] Refresh search indexes after paragraph edits
    
    ### What is this PR for?
    As of now, search index (lucene and embedding) are not updated on paragraph 
edits.
    This change fires `fireParagraphUpdateEvent` after each successful save, 
allowing both Lucene and embedding search implementations to refresh the 
affected paragraph immediately.
    Paragraph edits made through `NotebookService.updateParagraph` or the 
paragraph REST `PUT` endpoint are saved but do not fire a paragraph update 
event. As a result, `SearchService` implementations keep the old paragraph text 
until a full index rebuild or server restart.
    
    
    ### What type of PR is it?
    Bug Fix
    
    ### Todos
    * [x] Refresh search indexes after service-layer paragraph edits
    * [x] Refresh search indexes after REST paragraph edits
    * [x] Add service coverage with a real Lucene search listener
    * [x] Add REST-to-search integration coverage
    
    ### What is the Jira issue?
    No Jira issue has been created yet. This is currently submitted as a 
`[MINOR]` bug fix.
    
    ### How should this be tested?
    Automated tests:
    
    ```bash
    ./mvnw -pl zeppelin-server -Dtest=NotebookServiceTest,LuceneSearchTest test
    ./mvnw -pl zeppelin-server -Dtest=ZeppelinRestApiTest#testUpdateParagraph 
test
    ```
    
    The tests update paragraph text through both supported paths and verify 
that the new text is returned by Lucene search.
    
    ### Screenshots (if appropriate)
    N/A
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No; this restores expected search-index 
consistency.
    
    Closes #5463 from kkalyan/fix/search-index-paragraph-updates.
    
    Signed-off-by:  <>
---
 .../org/apache/zeppelin/rest/NotebookRestApi.java   |  1 +
 .../apache/zeppelin/service/NotebookService.java    |  1 +
 .../apache/zeppelin/rest/ZeppelinRestApiTest.java   | 21 ++++++++++++++++++---
 .../zeppelin/service/NotebookServiceTest.java       | 17 +++++++++++++----
 4 files changed, 33 insertions(+), 7 deletions(-)

diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index 0df06056e8..184e01f080 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -705,6 +705,7 @@ public class NotebookRestApi extends AbstractRestApi {
 
         AuthenticationInfo subject = new AuthenticationInfo(user);
         notebook.saveNote(note, subject);
+        note.fireParagraphUpdateEvent(p);
         notebookServer.broadcastParagraph(note, p, MSG_ID_NOT_DEFINED);
         return new JsonResponse<>(Status.OK, "").build();
       });
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
index a0e7eb592f..dd3c6ac7ca 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
@@ -814,6 +814,7 @@ public class NotebookService {
           p.setText(text);
         }
         notebook.saveNote(note, context.getAutheInfo());
+        note.fireParagraphUpdateEvent(p);
         callback.onSuccess(p, context);
         return null;
       });
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
index 385050ba77..64321e9048 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
@@ -828,7 +828,7 @@ class ZeppelinRestApiTest extends AbstractTestRestApi {
   }
 
   @Test
-  void testUpdateParagraph() throws IOException {
+  void testUpdateParagraph() throws IOException, InterruptedException {
     String noteId = null;
     try {
       noteId = notebook.createNote("note1_testUpdateParagraph", anonymous);
@@ -862,7 +862,9 @@ class ZeppelinRestApiTest extends AbstractTestRestApi {
           return null;
         });
 
-      String updateBothRequest = "{\"title\": \"updated title\", \"text\" : 
\"updated text 2\" }";
+      String restSearchToken = "restSearchUpdatedToken";
+      String updateBothRequest = "{\"title\": \"updated title\", \"text\" : 
\"" +
+          restSearchToken + "\" }";
       CloseableHttpResponse updatePut = httpPut("/notebook/" + noteId + 
"/paragraph/" + newParagraphId,
               updateBothRequest);
       updatePut.close();
@@ -871,9 +873,22 @@ class ZeppelinRestApiTest extends AbstractTestRestApi {
         noteP -> {
           Paragraph updatedBothParagraph = noteP.getParagraph(newParagraphId);
           assertEquals("updated title", updatedBothParagraph.getTitle());
-          assertEquals("updated text 2", updatedBothParagraph.getText());
+          assertEquals(restSearchToken, updatedBothParagraph.getText());
           return null;
         });
+
+      // SearchService handles paragraph events asynchronously.
+      Thread.sleep(1000);
+      CloseableHttpResponse search = httpGet("/notebook/search?q=" + 
restSearchToken);
+      Map<String, Object> searchResponse = gson.fromJson(
+          EntityUtils.toString(search.getEntity(), StandardCharsets.UTF_8),
+          new TypeToken<Map<String, Object>>() {}.getType());
+      search.close();
+      List<Map<String, String>> searchResults =
+          (List<Map<String, String>>) searchResponse.get("body");
+      String expectedResultId = noteId + "/paragraph/" + newParagraphId;
+      assertTrue(searchResults.stream().anyMatch(result ->
+          result.get("id").startsWith(expectedResultId)));
     } finally {
       //cleanup
       if (null != noteId) {
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
index 549b6299b8..dd40f8af83 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
@@ -580,7 +580,7 @@ class NotebookServiceTest {
 
 
   @Test
-  void testParagraphOperations() throws IOException {
+  void testParagraphOperations() throws IOException, InterruptedException {
     // create note
     String note1Id = notebookService.createNote("note1", "python", false, 
context, callback);
     notebook.processNote(note1Id,
@@ -604,12 +604,21 @@ class NotebookServiceTest {
         return null;
       });
 
-    // update paragraph
+    // update paragraph and verify the asynchronous search listener sees the 
new text
     reset(callback);
-    notebookService.updateParagraph(note1Id, p.getId(), "my_title", "my_text",
+    String serviceSearchToken = "serviceSearchUpdatedToken";
+    notebookService.updateParagraph(note1Id, p.getId(), "my_title", 
serviceSearchToken,
         new HashMap<>(), new HashMap<>(), context, callback);
     assertEquals("my_title", p.getTitle());
-    assertEquals("my_text", p.getText());
+    assertEquals(serviceSearchToken, p.getText());
+    while (!searchService.isEventQueueEmpty()) {
+      Thread.sleep(10);
+    }
+    // The queue may be empty while its worker is finishing the current event.
+    Thread.sleep(100);
+    List<Map<String, String>> searchResults = 
searchService.query(serviceSearchToken);
+    assertTrue(searchResults.stream().anyMatch(result ->
+        result.get("id").startsWith(note1Id)));
 
     // move paragraph
     reset(callback);

Reply via email to