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);