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

voidmatcha 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 2cc438b725 [ZEPPELIN-5235] Add Cancel all paragraphs button to the new 
UI
2cc438b725 is described below

commit 2cc438b72501a202635f064f084d53cad9c04f5f
Author: HwangRock <[email protected]>
AuthorDate: Tue Aug 18 23:01:29 2026 +0900

    [ZEPPELIN-5235] Add Cancel all paragraphs button to the new UI
    
    ### What is this PR for?
    The note action bar has a run-all button but no way to stop a note from the 
UI. Stopping a running
    note is only reachable through `DELETE /api/notebook/job/{noteId}`, so a 
user who starts a long note has
    to leave the page and issue a REST call, or cancel each paragraph one by 
one.
    
    This adds a `Cancel all paragraphs` button next to run-all in the new UI. 
The button sends a new
    `CANCEL_ALL_PARAGRAPHS` websocket message, and `cancelAllParagraphs()` in 
`NotebookService` aborts
    every paragraph of the note that has not terminated yet.
    
    The abort loop already existed inline in `stopNoteJobs()` in 
`NotebookRestApi`. It is extracted to
    `Note.abortAll()` so the REST endpoint and the websocket handler share one 
implementation instead of
    drifting apart. Permission handling follows the existing run path: the 
service checks
    `Permission.RUNNER` the same way `cancelParagraph()` does, and the note is 
reached through
    `notebook.processNote()` so the note lock is held while paragraphs are 
aborted.
    
    No new cancellation mechanism is introduced. `Note.abortAll()` calls the 
existing `Paragraph.abort()`,
    which delegates to `interpreter.cancel()` exactly as single-paragraph 
cancel does today. The button is
    disabled in revision view and while no paragraph is running.
    
    ### What type of PR is it?
    Improvement
    
    ### Todos
    * [x] - Extract `Note.abortAll()` and reuse it from `stopNoteJobs()`
    * [x] - Add `CANCEL_ALL_PARAGRAPHS` op and 
`NotebookService.cancelAllParagraphs()`
    * [x] - Add the button to the new UI action bar and the message to the SDK
    * [x] - Unit tests for the notebook, service and socket layers
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/ZEPPELIN-5235
    
    ### How should this be tested?
    `NotebookServiceTest` covers the success path, the forbidden path for a 
non-runner, and an unknown
    note id. `NotebookServerTest` covers the op routing. `NotebookTest` covers 
`Note.abortAll()` leaving
    terminated paragraphs alone.
    
    Manually: create a note with six python paragraphs where the first two 
finish within a few seconds and
    the rest sleep for minutes, run all, then press the button while a 
paragraph is running. The running
    paragraph moves to ABORT, the finished ones keep FINISHED, and paragraphs 
that never started stay
    untouched.
    
    ### Screenshots (if appropriate)
    
https://github.com/user-attachments/assets/ebd5e205-ad18-4340-9995-2fcfd10f5d9b
    
    ### Questions:
    * Does the license files need to update? No.
    * Is there breaking changes for older versions? No. The REST behaviour is 
unchanged and the new op is additive.
    * Does this needs documentation? No.
    
    
    Closes #5425 from HwangRock/ZEPPELIN-5235-cancel-all.
    
    Signed-off-by: YONGJAE LEE <[email protected]>
---
 .../java/org/apache/zeppelin/common/Message.java   |  1 +
 .../java/org/apache/zeppelin/notebook/Note.java    | 11 ++++
 .../org/apache/zeppelin/rest/NotebookRestApi.java  |  6 +--
 .../apache/zeppelin/service/NotebookService.java   | 20 +++++++
 .../org/apache/zeppelin/socket/NotebookServer.java |  8 +++
 .../org/apache/zeppelin/notebook/NotebookTest.java | 30 +++++++++++
 .../zeppelin/service/NotebookServiceTest.java      | 61 ++++++++++++++++++++++
 .../apache/zeppelin/socket/NotebookServerTest.java | 46 ++++++++++++++++
 .../e2e/models/notebook-action-bar-page.ts         |  2 +
 .../action-bar/action-bar-functionality.spec.ts    |  8 +++
 .../interfaces/message-data-type-map.interface.ts  |  2 +
 .../src/interfaces/message-operator.interface.ts   |  6 +++
 .../src/interfaces/message-paragraph.interface.ts  |  4 ++
 .../projects/zeppelin-sdk/src/message.ts           |  4 ++
 .../notebook/action-bar/action-bar.component.html  | 11 ++++
 .../notebook/action-bar/action-bar.component.ts    |  4 ++
 .../src/app/services/message.service.ts            |  4 ++
 17 files changed, 223 insertions(+), 5 deletions(-)

diff --git 
a/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java 
b/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
index 6ec66e63db..126855bcff 100644
--- a/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
+++ b/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
@@ -192,6 +192,7 @@ public class Message implements JsonSerializable {
     PARAGRAPH_MOVED,              // [s-c] paragraph moved
     NOTE_UPDATED,                 // [s-c] paragraph updated(name, config)
     RUN_ALL_PARAGRAPHS,           // [c-s] run all paragraphs
+    CANCEL_ALL_PARAGRAPHS,        // [c-s] cancel(abort) all paragraphs
     PARAGRAPH_EXECUTED_BY_SPELL,  // [c-s] paragraph was executed by spell
     RUN_PARAGRAPH_USING_SPELL,    // [s-c] run paragraph using spell
     PARAS_INFO,                   // [s-c] paragraph runtime infos
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Note.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Note.java
index 278fa43ecc..ccebad12dc 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Note.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Note.java
@@ -815,6 +815,17 @@ public class Note implements JsonSerializable {
     return this.paragraphs;
   }
 
+  /**
+   * Abort all the paragraphs which are not terminated yet.
+   */
+  public void abortAll() {
+    for (Paragraph p : getParagraphs()) {
+      if (!p.isTerminated()) {
+        p.abort();
+      }
+    }
+  }
+
   // TODO(zjffdu) how does this used ?
   private void snapshotAngularObjectRegistry(String user) {
     angularObjects = new HashMap<>();
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 192cd5056c..3c09a612f4 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
@@ -887,11 +887,7 @@ public class NotebookRestApi extends AbstractRestApi {
       note -> {
         checkIfNoteIsNotNull(note, noteId);
         checkIfUserCanRun(noteId, "Insufficient privileges you cannot stop 
this job for this note");
-        for (Paragraph p : note.getParagraphs()) {
-          if (!p.isTerminated()) {
-            p.abort();
-          }
-        }
+        note.abortAll();
         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 38ce280ad6..9e5e31aa1c 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
@@ -602,6 +602,26 @@ public class NotebookService {
 
   }
 
+  public void cancelAllParagraphs(String noteId,
+                                  ServiceContext context,
+                                  ServiceCallback<Paragraph> callback) throws 
IOException {
+    if (!checkPermission(noteId, Permission.RUNNER, 
Message.OP.CANCEL_ALL_PARAGRAPHS, context,
+        callback)) {
+      return;
+    }
+
+    notebook.processNote(noteId,
+      note -> {
+        if (note == null) {
+          throw new NoteNotFoundException(noteId);
+        }
+        note.abortAll();
+        callback.onSuccess(null, context);
+        return null;
+      });
+
+  }
+
   public void moveParagraph(String noteId,
                             String paragraphId,
                             int newIndex,
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 2d78ae7fb5..555d22ffd3 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -387,6 +387,9 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
         case CANCEL_PARAGRAPH:
           cancelParagraph(conn, context, receivedMessage);
           break;
+        case CANCEL_ALL_PARAGRAPHS:
+          cancelAllParagraphs(conn, context, receivedMessage);
+          break;
         case MOVE_PARAGRAPH:
           moveParagraph(conn, context, receivedMessage);
           break;
@@ -1482,6 +1485,11 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
     getNotebookService().cancelParagraph(noteId, paragraphId, context, new 
WebSocketServiceCallback<>(conn));
   }
 
+  private void cancelAllParagraphs(NotebookSocket conn, ServiceContext 
context, Message fromMessage) throws IOException {
+    final String noteId = (String) fromMessage.get("noteId");
+    getNotebookService().cancelAllParagraphs(noteId, context, new 
WebSocketServiceCallback<>(conn));
+  }
+
   private void runAllParagraphs(NotebookSocket conn,
                                 ServiceContext context,
                                 Message fromMessage) throws IOException {
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
index 39e6ec70e3..a9a879795e 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
@@ -616,6 +616,36 @@ class NotebookTest extends AbstractInterpreterTest 
implements ParagraphJobListen
     notebook.removeNote(noteId, anonymous);
   }
 
+  @Test
+  void testAbortAll() throws IOException {
+    String noteId = notebook.createNote("note1", anonymous);
+    notebook.processNote(noteId,
+      note -> {
+        Paragraph p1 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
+        p1.setText("p1");
+        p1.setStatus(Status.RUNNING);
+
+        Paragraph p2 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
+        p2.setText("p2");
+        p2.setStatus(Status.PENDING);
+
+        Paragraph p3 = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
+        p3.setText("p3");
+        p3.setStatus(Status.FINISHED);
+
+        // when
+        note.abortAll();
+
+        // then
+        assertTrue(p1.isAborted());
+        assertTrue(p2.isAborted());
+        assertFalse(p3.isAborted());
+        assertEquals(Status.FINISHED, p3.getStatus());
+        return null;
+      });
+    notebook.removeNote(noteId, anonymous);
+  }
+
   @Test
   void testSchedule() throws InterruptedException, IOException {
     // create a note and a paragraph
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 2d53f34dec..2eeb0f650c 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
@@ -19,8 +19,10 @@
 package org.apache.zeppelin.service;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -67,6 +69,9 @@ 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.rest.exception.ForbiddenException;
+import org.apache.zeppelin.rest.exception.NoteNotFoundException;
+import org.apache.zeppelin.scheduler.Job.Status;
 import org.apache.zeppelin.search.LuceneSearch;
 import org.apache.zeppelin.search.SearchService;
 import org.apache.zeppelin.storage.ConfigStorage;
@@ -671,6 +676,62 @@ class NotebookServiceTest {
       });
   }
 
+  @Test
+  void testCancelAllParagraphs() throws IOException {
+    String note1Id = notebookService.createNote("note_cancel_all", "python", 
false, context, callback);
+    Paragraph p1 = notebook.processNote(note1Id,
+      note1 -> {
+        Paragraph p = note1.addNewParagraph(context.getAutheInfo());
+        p.setText("p1");
+        p.setStatus(Status.RUNNING);
+        return p;
+      });
+    Paragraph p2 = notebook.processNote(note1Id,
+      note1 -> {
+        Paragraph p = note1.addNewParagraph(context.getAutheInfo());
+        p.setText("p2");
+        p.setStatus(Status.FINISHED);
+        return p;
+      });
+
+    reset(callback);
+    notebookService.cancelAllParagraphs(note1Id, context, callback);
+
+    assertTrue(p1.isAborted());
+    assertFalse(p2.isAborted());
+    verify(callback).onSuccess(any(), eq(context));
+  }
+
+  @Test
+  void testCancelAllParagraphsForbidden() throws IOException {
+    String note1Id = notebookService.createNote("note_cancel_all_forbidden", 
"python", false, context, callback);
+    Paragraph p1 = notebook.processNote(note1Id,
+      note1 -> {
+        Paragraph p = note1.addNewParagraph(context.getAutheInfo());
+        p.setText("p1");
+        p.setStatus(Status.RUNNING);
+        return p;
+      });
+
+    HashSet<String> otherUser = new HashSet<>();
+    otherUser.add("other_user");
+    authorizationService.setOwners(note1Id, otherUser);
+    authorizationService.setWriters(note1Id, otherUser);
+    authorizationService.setRunners(note1Id, otherUser);
+
+    reset(callback);
+    notebookService.cancelAllParagraphs(note1Id, context, callback);
+
+    assertFalse(p1.isAborted());
+    verify(callback).onFailure(any(ForbiddenException.class), eq(context));
+  }
+
+  @Test
+  void testCancelAllParagraphsNoteNotFound() {
+    assertThrows(NoteNotFoundException.class,
+        () -> notebookService.cancelAllParagraphs("non_existing_note_id", 
context, callback));
+  }
+
   @Test
   void testNormalizeNotePath() throws IOException {
     assertEquals("/Untitled Note", notebookService.normalizeNotePath(" "));
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
index d982d46a33..ef6eb6d5ec 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
@@ -237,6 +237,52 @@ class NotebookServerTest extends AbstractTestRestApi {
     }
   }
 
+  @Test
+  void testCancelAllParagraphsNotDisabledForRunningNotes() {
+    assertFalse(Message.isDisabledForRunningNotes(OP.CANCEL_ALL_PARAGRAPHS));
+  }
+
+  @Test
+  void testCancelAllParagraphsWebSocket() throws IOException {
+    NotebookSocket sock1 = createWebSocket();
+
+    String noteName = "Note with millis " + System.currentTimeMillis();
+    notebookServer.onMessage(sock1, new Message(OP.NEW_NOTE).put("name", 
noteName).toJson());
+    NoteInfo createdNoteInfo = null;
+    for (NoteInfo noteInfo : notebook.getNotesInfo()) {
+      if (notebook.processNote(noteInfo.getId(), 
Note::getName).equals(noteName)) {
+        createdNoteInfo = noteInfo;
+        break;
+      }
+    }
+    String noteId = createdNoteInfo.getId();
+
+    notebookServer.onMessage(sock1, new Message(OP.GET_NOTE).put("id", 
noteId).toJson());
+
+    Paragraph paragraph = notebook.processNote(noteId,
+      note -> {
+        Paragraph p = note.getParagraphs().get(0);
+        p.setStatus(Status.RUNNING);
+        // simulate a sequential run in progress
+        note.setRunning(true);
+        return p;
+      });
+
+    try {
+      notebookServer.onMessage(sock1,
+          new Message(OP.CANCEL_ALL_PARAGRAPHS).put("noteId", 
noteId).toJson());
+
+      assertTrue(paragraph.isAborted());
+    } finally {
+      notebook.processNote(noteId,
+        note -> {
+          note.setRunning(false);
+          return null;
+        });
+      notebook.removeNote(noteId, anonymous);
+    }
+  }
+
   @Test
   void testCollaborativeEditing() throws IOException {
     if 
(!zepServer.getZeppelinConfiguration().isZeppelinNotebookCollaborativeModeEnable())
 {
diff --git a/zeppelin-web-angular/e2e/models/notebook-action-bar-page.ts 
b/zeppelin-web-angular/e2e/models/notebook-action-bar-page.ts
index 1ecc33bd2c..3ffe207727 100644
--- a/zeppelin-web-angular/e2e/models/notebook-action-bar-page.ts
+++ b/zeppelin-web-angular/e2e/models/notebook-action-bar-page.ts
@@ -16,6 +16,7 @@ import { BasePage } from './base-page';
 export class NotebookActionBarPage extends BasePage {
   readonly titleEditor: Locator;
   readonly runAllButton: Locator;
+  readonly cancelAllButton: Locator;
   readonly showHideCodeButton: Locator;
   readonly showHideOutputButton: Locator;
   readonly clearOutputButton: Locator;
@@ -40,6 +41,7 @@ export class NotebookActionBarPage extends BasePage {
     super(page);
     this.titleEditor = page.locator('zeppelin-elastic-input');
     this.runAllButton = page.locator('button[nzTooltipTitle="Run all 
paragraphs"]');
+    this.cancelAllButton = page.locator('button[nzTooltipTitle="Cancel all 
paragraphs"]');
     this.showHideCodeButton = page.locator('button[nzTooltipTitle="Show/hide 
the code"]');
     this.showHideOutputButton = page.locator('button[nzTooltipTitle="Show/hide 
the output"]');
     this.clearOutputButton = page.locator('button[nzTooltipTitle="Clear all 
output"]');
diff --git 
a/zeppelin-web-angular/e2e/tests/notebook/action-bar/action-bar-functionality.spec.ts
 
b/zeppelin-web-angular/e2e/tests/notebook/action-bar/action-bar-functionality.spec.ts
index 5825e1d31b..0ef046d3c1 100644
--- 
a/zeppelin-web-angular/e2e/tests/notebook/action-bar/action-bar-functionality.spec.ts
+++ 
b/zeppelin-web-angular/e2e/tests/notebook/action-bar/action-bar-functionality.spec.ts
@@ -68,6 +68,14 @@ test.describe('Notebook Action Bar Functionality', () => {
     await expect(confirmButton).not.toBeVisible();
   });
 
+  test('should display cancel all button as disabled when note is idle', async 
() => {
+    await expect(actionBarPage.cancelAllButton).toBeVisible();
+
+    // Given: an idle note (no paragraph running), Cancel all is disabled and 
Run all is enabled — the two buttons are mutually exclusive
+    await expect(actionBarPage.cancelAllButton).toBeDisabled();
+    await expect(actionBarPage.runAllButton).toBeEnabled();
+  });
+
   test('should toggle code visibility', async () => {
     await expect(actionBarPage.showHideCodeButton).toBeVisible();
     await expect(actionBarPage.showHideCodeButton).toBeEnabled();
diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
index 6c6088c73a..5fba20a132 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
@@ -59,6 +59,7 @@ import {
   AngularObjectRemove,
   AngularObjectUpdate,
   AngularObjectUpdated,
+  CancelAllParagraphs,
   CancelParagraph,
   CommitParagraph,
   Completion,
@@ -154,6 +155,7 @@ export interface MessageSendDataTypeMap {
   [OP.PARAGRAPH_EXECUTED_BY_SPELL]: ParagraphExecutedBySpell;
   [OP.RUN_PARAGRAPH]: RunParagraph;
   [OP.RUN_ALL_PARAGRAPHS]: RunAllParagraphs;
+  [OP.CANCEL_ALL_PARAGRAPHS]: CancelAllParagraphs;
   [OP.PARAGRAPH_REMOVE]: ParagraphRemove;
   [OP.PARAGRAPH_CLEAR_OUTPUT]: ParagraphClearOutput;
   [OP.PARAGRAPH_CLEAR_ALL_OUTPUT]: ParagraphClearAllOutput;
diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
index 1f8036b393..322fb8f388 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
@@ -443,6 +443,12 @@ export enum OP {
    */
   RUN_ALL_PARAGRAPHS = 'RUN_ALL_PARAGRAPHS',
 
+  /**
+   * [c-s]
+   * cancel all paragraphs
+   */
+  CANCEL_ALL_PARAGRAPHS = 'CANCEL_ALL_PARAGRAPHS',
+
   /**
    * [c-s]
    * paragraph was executed by spell
diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
index 2ea3916ea1..f75cd1f5f3 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
@@ -178,6 +178,10 @@ export interface RunAllParagraphs {
   paragraphs: string;
 }
 
+export interface CancelAllParagraphs {
+  noteId: string;
+}
+
 export interface InsertParagraph {
   index: number;
 }
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts
index 6262bff26a..110af36f27 100644
--- a/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts
@@ -415,6 +415,10 @@ export class Message {
     });
   }
 
+  cancelAllParagraphs(noteId: string): void {
+    this.send<OP.CANCEL_ALL_PARAGRAPHS>(OP.CANCEL_ALL_PARAGRAPHS, { noteId });
+  }
+
   paragraphRemove(paragraphId: string): void {
     this.send<OP.PARAGRAPH_REMOVE>(OP.PARAGRAPH_REMOVE, { id: paragraphId });
   }
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
index f53f5292da..c45438e872 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
@@ -33,6 +33,17 @@
       >
         <i nz-icon nzType="play-circle" nzTheme="outline"></i>
       </button>
+      <button
+        nz-button
+        nz-popconfirm
+        nzPopconfirmTitle="Cancel all paragraphs?"
+        nz-tooltip
+        nzTooltipTitle="Cancel all paragraphs"
+        (nzOnConfirm)="cancelAllParagraphs()"
+        [disabled]="revisionView || !isNoteParagraphRunning"
+      >
+        <i nz-icon nzType="pause-circle" nzTheme="outline"></i>
+      </button>
       @if (!viewOnly) {
         <button
           nz-button
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts
 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts
index cab6895b9b..a0b92d2c72 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts
@@ -154,6 +154,10 @@ export class NotebookActionBarComponent extends 
MessageListenersManager implemen
     );
   }
 
+  cancelAllParagraphs() {
+    this.messageService.cancelAllParagraphs(this.note.id);
+  }
+
   clearAllParagraphOutput() {
     this.messageService.paragraphClearAllOutput(this.note.id);
   }
diff --git a/zeppelin-web-angular/src/app/services/message.service.ts 
b/zeppelin-web-angular/src/app/services/message.service.ts
index 9b86a7d42d..1c61fb052b 100644
--- a/zeppelin-web-angular/src/app/services/message.service.ts
+++ b/zeppelin-web-angular/src/app/services/message.service.ts
@@ -277,6 +277,10 @@ export class MessageService extends Message implements 
OnDestroy {
     super.runAllParagraphs(noteId, paragraphs);
   }
 
+  cancelAllParagraphs(noteId: string): void {
+    super.cancelAllParagraphs(noteId);
+  }
+
   paragraphRemove(paragraphId: string): void {
     super.paragraphRemove(paragraphId);
   }

Reply via email to