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

tbonelee 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 78161e34de [ZEPPELIN-6600] Refresh the cron scheduler only when the 
expression changes
78161e34de is described below

commit 78161e34de0f96cb035b573edceec1203d46c516
Author: renechoi <[email protected]>
AuthorDate: Sat Sep 5 00:14:14 2026 +0900

    [ZEPPELIN-6600] Refresh the cron scheduler only when the expression changes
    
    ### What is this PR for?
    
    `NotebookService.isCronUpdated` reports an update whenever either config 
carries a cron
    expression, so saving an unrelated setting on a scheduled note unregisters 
and re-registers its
    Quartz job. The equality branch sets the flag it should clear. Comparing 
the values directly
    leaves the scheduler alone unless the expression changed.
    
    ### What type of PR is it?
    
    Bug Fix
    
    ### What is the Jira issue?
    
    https://issues.apache.org/jira/browse/ZEPPELIN-6600
    
    ### How should this be tested?
    
    `NotebookServiceTest#testCronRefreshedOnlyWhenTheExpressionChanges` walks a 
note through add,
    unrelated edit, change and removal. Master fails at that edit.
    
    ### Questions:
    
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    
    Closes #5442 from renechoi/ZEPPELIN-6600.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../apache/zeppelin/service/NotebookService.java   | 12 +++--
 .../zeppelin/service/NotebookServiceTest.java      | 51 ++++++++++++++++++++--
 2 files changed, 53 insertions(+), 10 deletions(-)

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 9e5e31aa1c..eacd970eb0 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
@@ -991,15 +991,13 @@ public class NotebookService {
 
 
   private boolean isCronUpdated(Map<String, Object> configA, Map<String, 
Object> configB) {
-    boolean cronUpdated = false;
-    if (configA.get("cron") != null && configB.get("cron") != null && 
configA.get("cron")
-        .equals(configB.get("cron"))) {
-      cronUpdated = true;
-    } else if (configA.get("cron") != null || configB.get("cron") != null) {
-      cronUpdated = true;
+    Object cronA = configA.get("cron");
+    Object cronB = configB.get("cron");
+    if (cronA == null) {
+      return cronB != null;
     }
 
-    return cronUpdated;
+    return !cronA.equals(cronB);
   }
 
   public void saveNoteForms(String 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 2eeb0f650c..1c4f554d50 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
@@ -69,6 +69,7 @@ import 
org.apache.zeppelin.notebook.exception.NotePathAlreadyExistsException;
 import org.apache.zeppelin.notebook.repo.NotebookRepo;
 import org.apache.zeppelin.notebook.repo.VFSNotebookRepo;
 import org.apache.zeppelin.notebook.scheduler.QuartzSchedulerService;
+import org.apache.zeppelin.notebook.scheduler.SchedulerService;
 import org.apache.zeppelin.rest.exception.ForbiddenException;
 import org.apache.zeppelin.rest.exception.NoteNotFoundException;
 import org.apache.zeppelin.scheduler.Job.Status;
@@ -95,6 +96,7 @@ class NotebookServiceTest {
   private SearchService searchService;
   private Notebook notebook;
   private AuthorizationService authorizationService;
+  private ZeppelinConfiguration zConf;
   private ServiceContext context =
       new ServiceContext(AuthenticationInfo.ANONYMOUS, new HashSet<>());
 
@@ -106,11 +108,12 @@ class NotebookServiceTest {
   @BeforeEach
   void setUp(TestInfo testInfo) throws Exception {
     notebookDir = 
Files.createTempDirectory("notebookDir").toAbsolutePath().toFile();
-    ZeppelinConfiguration zConf = ZeppelinConfiguration.load();
+    zConf = ZeppelinConfiguration.load();
     
zConf.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_DIR.getVarName(),
         notebookDir.getAbsolutePath());
-    // enable cron for testNoteUpdate method
-    if ("testNoteUpdate()".equals(testInfo.getDisplayName())){
+    // enable cron for the tests that update a note's cron settings
+    if ("testNoteUpdate()".equals(testInfo.getDisplayName())
+        || 
"testCronRefreshedOnlyWhenTheExpressionChanges()".equals(testInfo.getDisplayName()))
 {
       confDir = Files.createTempDirectory("confDir").toAbsolutePath().toFile();
       
zConf.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_CONF_DIR.getVarName(),
             confDir.getAbsolutePath());
@@ -474,6 +477,48 @@ class NotebookServiceTest {
             });
   }
 
+  @Test
+  void testCronRefreshedOnlyWhenTheExpressionChanges() throws IOException {
+    SchedulerService schedulerService = mock(SchedulerService.class);
+    NotebookService service =
+        new NotebookService(notebook, authorizationService, zConf, 
schedulerService);
+    String noteId = service.createNote("/folder_cron/note_test_cron", "test", 
true, context,
+        callback);
+
+    Map<String, Object> config = new HashMap<>();
+    config.put("isZeppelinNotebookCronEnable", true);
+    config.put("looknfeel", "looknfeel");
+    config.put("cron", "0 0/5 * * * ?");
+    config.put("cronExecutingRoles", "[\"test\"]");
+    config.put("cronExecutingUser", "test");
+
+    // adding a cron expression schedules the note
+    service.updateNote(noteId, "note_test_cron", new HashMap<>(config), 
context, callback);
+    verify(schedulerService).refreshCron(noteId);
+
+    // an unrelated change that keeps the same expression leaves the scheduler 
alone.
+    // onSuccess proves the update ran to the end, since updateNote has 
earlier exits that
+    // would satisfy never() without ever reaching the cron decision
+    reset(schedulerService);
+    reset(callback);
+    config.put("looknfeel", "simple");
+    service.updateNote(noteId, "note_test_cron", new HashMap<>(config), 
context, callback);
+    verify(callback).onSuccess(any(Note.class), any(ServiceContext.class));
+    verify(schedulerService, never()).refreshCron(noteId);
+
+    // changing the expression schedules it again
+    reset(schedulerService);
+    config.put("cron", "0 0 0/1 * * ?");
+    service.updateNote(noteId, "note_test_cron", new HashMap<>(config), 
context, callback);
+    verify(schedulerService).refreshCron(noteId);
+
+    // removing the expression unschedules it
+    reset(schedulerService);
+    config.remove("cron");
+    service.updateNote(noteId, "note_test_cron", new HashMap<>(config), 
context, callback);
+    verify(schedulerService).refreshCron(noteId);
+  }
+
   @Test
   void testRenameNoteRejectsDuplicate() throws IOException {
     String note1Id = notebookService.createNote("/folder/note1", "test", true, 
context, callback);

Reply via email to