You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by zj...@apache.org on 2018/12/05 01:43:09 UTC

zeppelin git commit: [ZEPPELIN-3095] fix UI when paragraphs run sequential

Repository: zeppelin
Updated Branches:
  refs/heads/master eb941056b -> dda5a1452


[ZEPPELIN-3095] fix UI when paragraphs run sequential

### What is this PR for?
This PR fixes the incorrect behavior of UI during the execution of the RunAllParagraphs.
Including: allows you to copy the note at runtime (before that, the new note was created only after the end of runAllParagraph), blocks some interface elements.
The following items are blocked:

**Paragraphs Control :**
* Move up
* Move down
* Insert new
* Clone paragraph
* Run all above
* Run all below
* Disable run
* Remove

**Note :**
* Insert new paragraph

### What type of PR is it?
Improvement

### What is the Jira issue?
[Zeppelin-3095](https://issues.apache.org/jira/browse/ZEPPELIN-3095)
[ZP-25]

### GIF
![peek 2018-11-12 16-20](https://user-images.githubusercontent.com/30798933/48350794-511c6400-e699-11e8-952a-76a4dcc9e7b0.gif)

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no

Author: Savalek <de...@mail.ru>

Closes #3226 from Savalek/ZP-25 and squashes the following commits:

cf9229bce [Savalek] [ZEPPELIN-3095] fix UI when paragraphs run sequential


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/dda5a145
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/dda5a145
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/dda5a145

Branch: refs/heads/master
Commit: dda5a145249538eb5a49e452e34f9c5779e0ad87
Parents: eb94105
Author: Savalek <de...@mail.ru>
Authored: Fri Nov 30 11:05:42 2018 +0300
Committer: Jeff Zhang <zj...@apache.org>
Committed: Wed Dec 5 09:42:53 2018 +0800

----------------------------------------------------------------------
 .../integration/ParagraphActionsIT.java         |  2 +
 .../zeppelin/service/NotebookService.java       | 33 +++++++-----
 .../apache/zeppelin/socket/NotebookServer.java  | 16 ++++++
 .../zeppelin/rest/AbstractTestRestApi.java      |  1 +
 .../zeppelin/rest/ZeppelinRestApiTest.java      |  6 +--
 .../src/app/notebook/notebook-actionBar.html    |  2 +
 zeppelin-web/src/app/notebook/notebook.css      |  5 ++
 zeppelin-web/src/app/notebook/notebook.html     |  4 +-
 .../notebook/paragraph/paragraph-control.html   | 27 ++++++----
 .../notebook/paragraph/paragraph.controller.js  | 47 +++++++++++++++--
 .../src/app/notebook/paragraph/paragraph.css    |  5 ++
 .../websocket/websocket-event.factory.js        |  2 +
 .../java/org/apache/zeppelin/notebook/Note.java | 55 +++++++++++---------
 .../org/apache/zeppelin/notebook/Notebook.java  | 18 ++++++-
 .../zeppelin/notebook/ParagraphJobListener.java |  3 ++
 .../zeppelin/notebook/socket/Message.java       | 32 ++++++++++--
 .../apache/zeppelin/notebook/NotebookTest.java  | 13 +++--
 17 files changed, 205 insertions(+), 66 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-integration/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-integration/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java b/zeppelin-integration/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
index 416d44e..93cafc9 100644
--- a/zeppelin-integration/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
+++ b/zeppelin-integration/src/test/java/org/apache/zeppelin/integration/ParagraphActionsIT.java
@@ -229,6 +229,8 @@ public class ParagraphActionsIT extends AbstractZeppelinIT {
           getParagraphStatus(1), CoreMatchers.equalTo("READY")
       );
 
+      driver.navigate().refresh();
+      ZeppelinITUtils.sleep(3000, false);
       deleteTestNotebook(driver);
 
     } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
----------------------------------------------------------------------
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 641e799..14b8e23 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
@@ -387,21 +387,26 @@ public class NotebookService {
       return;
     }
 
-    for (Map<String, Object> raw : paragraphs) {
-      String paragraphId = (String) raw.get("id");
-      if (paragraphId == null) {
-        continue;
-      }
-      String text = (String) raw.get("paragraph");
-      String title = (String) raw.get("title");
-      Map<String, Object> params = (Map<String, Object>) raw.get("params");
-      Map<String, Object> config = (Map<String, Object>) raw.get("config");
-
-      if (!runParagraph(noteId, paragraphId, title, text, params, config, false, true,
-          context, callback)) {
-        // stop execution when one paragraph fails.
-        break;
+    note.setRunning(true);
+    try {
+      for (Map<String, Object> raw : paragraphs) {
+        String paragraphId = (String) raw.get("id");
+        if (paragraphId == null) {
+          continue;
+        }
+        String text = (String) raw.get("paragraph");
+        String title = (String) raw.get("title");
+        Map<String, Object> params = (Map<String, Object>) raw.get("params");
+        Map<String, Object> config = (Map<String, Object>) raw.get("config");
+
+        if (!runParagraph(noteId, paragraphId, title, text, params, config, false, true,
+                context, callback)) {
+          // stop execution when one paragraph fails.
+          break;
+        }
       }
+    } finally {
+      note.setRunning(false);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
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 1c40d17..ac167d1 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
@@ -240,6 +240,14 @@ public class NotebookServer extends WebSocketServlet
         throw new Exception("Anonymous access not allowed ");
       }
 
+      if (Message.isDisabledForRunningNotes(messagereceived.op)) {
+        Note note = notebook.getNote((String) messagereceived.get("noteId"));
+        if (note != null && note.isRunning()) {
+          throw new Exception("Note is now running sequentially. Can not be performed: " +
+                  messagereceived.op);
+        }
+      }
+
       if (StringUtils.isEmpty(conn.getUser())) {
         connectionManager.addUserConnection(messagereceived.principal, conn);
       }
@@ -1644,6 +1652,14 @@ public class NotebookServer extends WebSocketServlet
     // TODO
   }
 
+  @Override
+  public void noteRunningStatusChange(String noteId, boolean newStatus) {
+    connectionManager.broadcast(
+        noteId,
+        new Message(OP.NOTE_RUNNING_STATUS
+        ).put("status", newStatus));
+  }
+
   private void sendAllAngularObjects(Note note, String user, NotebookSocket conn)
       throws IOException {
     List<InterpreterSetting> settings =

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
index b003dd3..e4d161f 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
@@ -246,6 +246,7 @@ public abstract class AbstractTestRestApi {
       if (started == false) {
         throw new RuntimeException("Can not start Zeppelin server");
       }
+      ZeppelinServer.notebook.setParagraphJobListener(ZeppelinServer.notebookWsServer);
       LOG.info("Test Zeppelin stared.");
     }
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
----------------------------------------------------------------------
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 6993418..2e5a024 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
@@ -393,7 +393,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     ZeppelinServer.notebook.saveNote(note, anonymous);
     String noteId = note.getId();
 
-    note.runAll();
+    note.runAll(anonymous, true);
     // wait until job is finished or timeout.
     int timeout = 1;
     while (!paragraph.isTerminated()) {
@@ -449,7 +449,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     ZeppelinServer.notebook.saveNote(note, anonymous);
     String noteId = note.getId();
 
-    note.runAll();
+    note.runAll(anonymous, true);
     // assume that status of the paragraph is running
     GetMethod get = httpGet("/notebook/job/" + noteId);
     assertThat("test get note job: ", get, isAllowed());
@@ -496,7 +496,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     ZeppelinServer.notebook.saveNote(note, anonymous);
     String noteId = note.getId();
 
-    note.runAll();
+    note.runAll(anonymous, true);
 
     // Call Run paragraph REST API
     PostMethod postParagraph = httpPost("/notebook/job/" + noteId + "/" + paragraph.getId(),

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/notebook-actionBar.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook-actionBar.html b/zeppelin-web/src/app/notebook/notebook-actionBar.html
index cb49786..668697b 100644
--- a/zeppelin-web/src/app/notebook/notebook-actionBar.html
+++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html
@@ -232,6 +232,7 @@ limitations under the License.
                 class="btn btn-default btn-xs"
                 ng-click="removeNote(note.id)"
                 ng-hide="viewOnly"
+                ng-class="{'disabled':isNoteRunning()}"
                 tooltip-placement="bottom" uib-tooltip="Remove this note permanently"
                 ng-disabled="revisionView">
           <i class="icon-trash"></i>
@@ -242,6 +243,7 @@ limitations under the License.
                 class="btn btn-default btn-xs"
                 ng-click="moveNoteToTrash(note.id)"
                 ng-hide="viewOnly"
+                ng-class="{'disabled':isNoteRunning()}"
                 tooltip-placement="bottom" uib-tooltip="Move this note to trash"
                 ng-disabled="revisionView">
           <i class="icon-trash"></i>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/notebook.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.css b/zeppelin-web/src/app/notebook/notebook.css
index 2f6ff9a..0b8e48a 100644
--- a/zeppelin-web/src/app/notebook/notebook.css
+++ b/zeppelin-web/src/app/notebook/notebook.css
@@ -106,6 +106,11 @@
   display: block;
 }
 
+.new-paragraph-disable{
+  color: lightgray !important;
+  cursor: default !important;
+}
+
 .plus-sign{
   display: none;
   height: 7px;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/notebook.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.html b/zeppelin-web/src/app/notebook/notebook.html
index 179192b..ae23f76 100644
--- a/zeppelin-web/src/app/notebook/notebook.html
+++ b/zeppelin-web/src/app/notebook/notebook.html
@@ -167,7 +167,7 @@ limitations under the License.
          ng-click="insertNew('above')"
          ng-hide="viewOnly || asIframe || revisionView"
          ng-class="{'first-paragraph': $first}">
-      <h4 class="plus-sign">&#43; Add Paragraph</h4>
+      <h4 class="plus-sign" ng-class="{'new-paragraph-disable': isNoteRunning}">&#43; Add Paragraph</h4>
     </div>
     <div id="{{currentParagraph.id}}_paragraphColumn"
          ng-include src="'app/notebook/paragraph/paragraph.html'"
@@ -177,7 +177,7 @@ limitations under the License.
          ng-dblclick="paragraphOnDoubleClick(currentParagraph.id);">
     </div>
     <div class="new-paragraph last-paragraph" ng-click="insertNew('below');" ng-hide="!$last || viewOnly || asIframe || revisionView">
-      <h4 class="plus-sign">&#43; Add Paragraph</h4>
+      <h4 class="plus-sign" ng-class="{'new-paragraph-disable': isNoteRunning}">&#43; Add Paragraph</h4>
     </div>
   </div>
   <div style="clear:both;height:10px"></div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
index 0b4ca1e..4ef430f 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
@@ -47,10 +47,12 @@ limitations under the License.
     <span class="icon-control-play" style="cursor:pointer;color:#3071A9"
           tooltip-placement="top" uib-tooltip="Run this paragraph (Shift+Enter)"
           ng-click="runParagraphFromButton(getEditorValue())"
+          ng-class="{'item-disable' : isNoteRunning}"
           ng-show="paragraph.status!='RUNNING' && paragraph.status!='PENDING' && paragraph.config.enabled"></span>
     <span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C"
           tooltip-placement="top" uib-tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+C)"
           ng-click="cancelParagraph(paragraph)"
+          ng-class="{'item-disable' : isNoteRunning}"
           ng-show="paragraph.status=='RUNNING' || paragraph.status=='PENDING'"></span>
     <span ng-show="paragraph.runtimeInfos.jobUrl.length == 1">
       <a href="{{paragraph.runtimeInfos.jobUrl[0]}}" target="_blank"><span class="fa fa-tasks"></span> Spark job </a>
@@ -123,25 +125,28 @@ limitations under the License.
           </a>
         </li>
         <li>
-          <a ng-click="moveUp(paragraph)" ng-hide="$first"><span class="icon-arrow-up shortcut-icon"></span>
+          <a ng-click="moveUp(paragraph)" ng-hide="$first" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="icon-arrow-up shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+K</span>
             Move up
           </a>
         </li>
         <li>
-          <a ng-click="moveDown(paragraph)" ng-hide="$last"><span class="icon-arrow-down shortcut-icon"></span>
+          <a ng-click="moveDown(paragraph)" ng-hide="$last" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="icon-arrow-down shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+J</span>
             Move down
           </a>
         </li>
         <li>
-          <a ng-click="insertNew('below')"><span class="icon-plus shortcut-icon"></span>
+          <a ng-click="insertNew('below')" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="icon-plus shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+B</span>
             Insert new
           </a>
         </li>
         <li>
-          <a ng-click="runAllToThis(paragraph)" ng-hide="$first">
+          <a ng-click="runAllToThis(paragraph)" ng-hide="$first" ng-class="{'item-disable' : isNoteRunning}">
             <span class="icon-action-redo shortcut-icon"
                   style="position: relative; transform: rotate(-90deg); left: -4px;">
             </span>
@@ -150,7 +155,7 @@ limitations under the License.
           </a>
         </li>
         <li>
-          <a ng-click="runAllFromThis(paragraph)" ng-hide="$last">
+          <a ng-click="runAllFromThis(paragraph)" ng-hide="$last" ng-class="{'item-disable' : isNoteRunning}">
             <span class="icon-action-undo shortcut-icon"
                   style="position: relative; transform: rotate(-90deg); left: -4px;">
             </span>
@@ -159,7 +164,8 @@ limitations under the License.
           </a>
         </li>
         <li>
-          <a ng-click="copyParagraph(getEditorValue())"><span class="fa fa-copy shortcut-icon"></span>
+          <a ng-click="copyParagraph(getEditorValue())" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="fa fa-copy shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+Shift+C</span>
             Clone paragraph
           </a>
@@ -190,7 +196,8 @@ limitations under the License.
           </a>
         </li>
         <li>
-          <a ng-click="toggleEnableDisable(paragraph)"><span class="icon-control-play shortcut-icon"></span>
+          <a ng-click="toggleEnableDisable(paragraph)" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="icon-control-play shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+ {{ isMac ? 'Option' : 'Alt'}}+R</span>
             {{paragraph.config.enabled ? "Disable" : "Enable"}} run
           </a>
@@ -202,14 +209,16 @@ limitations under the License.
           </a>
         </li>
         <li>
-          <a ng-click="clearParagraphOutput(paragraph)"><span class="fa fa-eraser shortcut-icon"></span>
+          <a ng-click="clearParagraphOutput(paragraph)" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="fa fa-eraser shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+L</span>
             Clear output
           </a>
         </li>
         <li>
           <!-- remove paragraph -->
-          <a ng-click="removeParagraph(paragraph)" ng-if="note.paragraphs.length > 1"><span class="fa fa-times shortcut-icon"></span>
+          <a ng-click="removeParagraph(paragraph)" ng-if="note.paragraphs.length > 1" ng-class="{'item-disable' : isNoteRunning}">
+            <span class="fa fa-times shortcut-icon"></span>
             <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+D</span>
             Remove
           </a>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index b332b59..5f01c7e 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -45,6 +45,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   $scope.editor = null;
   $scope.cursorPosition = null;
   $scope.diffMatchPatch = new DiffMatchPatch();
+  $scope.isNoteRunning = false;
 
   // transactional info for spell execution
   $scope.spellTransaction = {
@@ -147,11 +148,20 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
       $scope.paragraph.config = {};
     }
 
+    $scope.isNoteRunning = !!(note && note.hasOwnProperty('info') &&
+      note.info.hasOwnProperty('isRunning')
+      && note.info.isRunning === true);
+
     noteVarShareService.put($scope.paragraph.id + '_paragraphScope', paragraphScope);
 
     initializeDefault($scope.paragraph.config);
   };
 
+  $scope.$on('noteRunningStatus', function(event, status) {
+    $scope.isNoteRunning = status;
+    $scope.editor.setReadOnly(status);
+  });
+
   const initializeDefault = function(config) {
     let forms = $scope.paragraph.settings.forms;
 
@@ -250,7 +260,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
       console.log('editor isnt loaded yet, returning');
       return;
     }
-    if ($scope.revisionView === true) {
+    if ($scope.revisionView === true || $scope.isNoteRunning === true) {
       $scope.editor.setReadOnly(true);
     } else {
       $scope.editor.setReadOnly(false);
@@ -266,6 +276,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   };
 
   $scope.cancelParagraph = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     console.log('Cancel %o', paragraph.id);
     websocketMsgSrv.cancelParagraphRun(paragraph.id);
   };
@@ -430,6 +443,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   };
 
   $scope.toggleEnableDisable = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     paragraph.config.enabled = !paragraph.config.enabled;
     commitParagraph(paragraph);
   };
@@ -472,15 +488,24 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   };
 
   $scope.runParagraphFromButton = function() {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     // we come here from the view, so we don't need to call `$digest()`
     $scope.runParagraph($scope.getEditorValue(), false, false);
   };
 
   $scope.runAllToThis = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     $scope.$emit('runAllAbove', paragraph, true);
   };
 
   $scope.runAllFromThis = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     $scope.$emit('runAllBelowAndCurrent', paragraph, true);
   };
 
@@ -519,18 +544,30 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   };
 
   $scope.moveUp = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     $scope.$emit('moveParagraphUp', paragraph);
   };
 
   $scope.moveDown = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     $scope.$emit('moveParagraphDown', paragraph);
   };
 
   $scope.insertNew = function(position) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     $scope.$emit('insertParagraph', $scope.paragraph.id, position);
   };
 
   $scope.copyPara = function(position) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     let editorValue = $scope.getEditorValue();
     if (editorValue) {
       $scope.copyParagraph(editorValue, position);
@@ -563,6 +600,9 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
   };
 
   $scope.removeParagraph = function(paragraph) {
+    if ($scope.isNoteRunning) {
+      return;
+    }
     if ($scope.note.paragraphs.length === 1) {
       BootstrapDialog.alert({
         closable: true,
@@ -738,7 +778,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
       $scope.editor.setHighlightActiveLine(false);
       $scope.editor.getSession().setUseWrapMode(true);
       $scope.editor.setTheme('ace/theme/chrome');
-      $scope.editor.setReadOnly($scope.isRunning($scope.paragraph));
+      $scope.editor.setReadOnly($scope.isRunning($scope.paragraph) || $scope.isNoteRunning);
       $scope.editor.setHighlightActiveLine($scope.paragraphFocused);
 
       if ($scope.paragraphFocused) {
@@ -1488,7 +1528,8 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
     $scope.paragraph.settings = newPara.settings;
     $scope.paragraph.runtimeInfos = newPara.runtimeInfos;
     if ($scope.editor) {
-      $scope.editor.setReadOnly($scope.isRunning(newPara));
+      let isReadOnly = $scope.isRunning(newPara) || $scope.isNoteRunning;
+      $scope.editor.setReadOnly(isReadOnly);
     }
 
     if (!$scope.asIframe) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/app/notebook/paragraph/paragraph.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
index b17272b..5135562 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
@@ -212,6 +212,11 @@
   float: left;
 }
 
+.item-disable {
+  color: rgba(128, 128, 128, 0.61) !important;
+  cursor: default !important;
+}
+
 .dropdown-menu .shortcut-keys {
   float: right;
   color: #999;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-web/src/components/websocket/websocket-event.factory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocket/websocket-event.factory.js b/zeppelin-web/src/components/websocket/websocket-event.factory.js
index 25a3336..ef255e7 100644
--- a/zeppelin-web/src/components/websocket/websocket-event.factory.js
+++ b/zeppelin-web/src/components/websocket/websocket-event.factory.js
@@ -65,6 +65,8 @@ function WebsocketEventFactory($rootScope, $websocket, $location, baseUrlSrv, ng
       $location.path('/notebook/' + data.note.id);
     } else if (op === 'NOTES_INFO') {
       $rootScope.$broadcast('setNoteMenu', data.notes);
+    } else if (op === 'NOTE_RUNNING_STATUS') {
+      $rootScope.$broadcast('noteRunningStatus', data.status);
     } else if (op === 'LIST_NOTE_JOBS') {
       $rootScope.$emit('jobmanager:set-jobs', data.noteJobs);
     } else if (op === 'LIST_UPDATE_NOTE_JOBS') {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
index 74b08b2..a2ba100 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
@@ -604,32 +604,21 @@ public class Note implements JsonSerializable {
     }
   }
 
-  /**
-   * Run all paragraphs sequentially. Only used for CronJob
-   */
-  public synchronized void runAll() {
-    String cronExecutingUser = (String) getConfig().get("cronExecutingUser");
-    String cronExecutingRoles = (String) getConfig().get("cronExecutingRoles");
-    if (null == cronExecutingUser) {
-      cronExecutingUser = "anonymous";
-    }
-    AuthenticationInfo authenticationInfo = new AuthenticationInfo(
-        cronExecutingUser,
-        StringUtils.isEmpty(cronExecutingRoles) ? null : cronExecutingRoles,
-        null);
-    runAll(authenticationInfo, true);
-  }
-
   public void runAll(AuthenticationInfo authenticationInfo, boolean blocking) {
-    for (Paragraph p : getParagraphs()) {
-      if (!p.isEnabled()) {
-        continue;
-      }
-      p.setAuthenticationInfo(authenticationInfo);
-      if (!run(p.getId(), blocking)) {
-        logger.warn("Skip running the remain notes because paragraph {} fails", p.getId());
-        break;
+    setRunning(true);
+    try {
+      for (Paragraph p : getParagraphs()) {
+        if (!p.isEnabled()) {
+          continue;
+        }
+        p.setAuthenticationInfo(authenticationInfo);
+        if (!run(p.getId(), blocking)) {
+          logger.warn("Skip running the remain notes because paragraph {} fails", p.getId());
+          break;
+        }
       }
+    } finally {
+      setRunning(false);
     }
   }
 
@@ -651,7 +640,7 @@ public class Note implements JsonSerializable {
   /**
    * Return true if there is a running or pending paragraph
    */
-  boolean isRunningOrPending() {
+  boolean haveRunningOrPendingParagraphs() {
     synchronized (paragraphs) {
       for (Paragraph p : paragraphs) {
         Status status = p.getStatus();
@@ -787,6 +776,21 @@ public class Note implements JsonSerializable {
     this.info = info;
   }
 
+  public void setRunning(boolean runStatus) {
+    Map<String, Object> infoMap = getInfo();
+    boolean oldStatus = (boolean) infoMap.getOrDefault("isRunning", false);
+    if (oldStatus != runStatus) {
+      infoMap.put("isRunning", runStatus);
+      if (paragraphJobListener != null) {
+        paragraphJobListener.noteRunningStatusChange(this.id, runStatus);
+      }
+    }
+  }
+
+  public boolean isRunning() {
+    return (boolean) getInfo().getOrDefault("isRunning", false);
+  }
+
   @Override
   public String toString() {
     if (this.path != null) {
@@ -804,6 +808,7 @@ public class Note implements JsonSerializable {
   public static Note fromJson(String json) {
     Note note = gson.fromJson(json, Note.class);
     convertOldInput(note);
+    note.info.remove("isRunning");
     note.postProcessParagraphs();
     return note;
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
index 9e7eb35..671a0cb 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
@@ -18,6 +18,7 @@
 package org.apache.zeppelin.notebook;
 
 import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObject;
@@ -549,7 +550,7 @@ public class Notebook {
 
       String noteId = context.getJobDetail().getJobDataMap().getString("noteId");
       Note note = notebook.getNote(noteId);
-      if (note.isRunningOrPending()) {
+      if (note.haveRunningOrPendingParagraphs()) {
         LOGGER.warn("execution of the cron job is skipped because there is a running or pending " +
             "paragraph (note id: {})", noteId);
         return;
@@ -560,7 +561,7 @@ public class Notebook {
         return;
       }
 
-      note.runAll();
+      runAll(note);
 
       boolean releaseResource = false;
       String cronExecutingUser = null;
@@ -587,6 +588,19 @@ public class Notebook {
         }
       }
     }
+
+    void runAll(Note note) {
+      String cronExecutingUser = (String) note.getConfig().get("cronExecutingUser");
+      String cronExecutingRoles = (String) note.getConfig().get("cronExecutingRoles");
+      if (null == cronExecutingUser) {
+        cronExecutingUser = "anonymous";
+      }
+      AuthenticationInfo authenticationInfo = new AuthenticationInfo(
+          cronExecutingUser,
+          StringUtils.isEmpty(cronExecutingRoles) ? null : cronExecutingRoles,
+          null);
+      note.runAll(authenticationInfo, true);
+    }
   }
 
   public void refreshCron(String id) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphJobListener.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphJobListener.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphJobListener.java
index 8743fb7..1d49eff 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphJobListener.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/ParagraphJobListener.java
@@ -31,4 +31,7 @@ public interface ParagraphJobListener extends JobListener<Paragraph> {
   void onOutputAppend(Paragraph paragraph, int idx, String output);
   void onOutputUpdate(Paragraph paragraph, int idx, InterpreterResultMessage msg);
   void onOutputUpdateAll(Paragraph paragraph, List<InterpreterResultMessage> msgs);
+
+  //TODO(savalek) Temporary solution. Need to refactor cron to be able to notify frontend directly.
+  void noteRunningStatusChange(String noteId, boolean newStatus);
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
index 06499f3..f9894c8 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
@@ -18,12 +18,14 @@
 package org.apache.zeppelin.notebook.socket;
 
 import com.google.gson.Gson;
-import org.apache.zeppelin.common.JsonSerializable;
-import org.slf4j.Logger;
-
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-
+import java.util.Set;
+import org.apache.zeppelin.common.JsonSerializable;
+import org.slf4j.Logger;
 /**
  * Zeppelin websocket massage template class.
  */
@@ -188,9 +190,27 @@ public class Message implements JsonSerializable {
     INTERPRETER_INSTALL_RESULT,   // [s-c] Status of an interpreter installation
     COLLABORATIVE_MODE_STATUS,    // [s-c] collaborative mode status
     PATCH_PARAGRAPH,              // [c-s][s-c] patch editor text
+    NOTE_RUNNING_STATUS,        // [s-c] sequential run status will be change
     NOTICE                        // [s-c] Notice
   }
 
+  // these messages will be ignored during the sequential run of the note
+  private static final Set<OP> disabledForRunningNoteMessages = Collections
+      .unmodifiableSet(new HashSet<>(Arrays.asList(
+          OP.COMMIT_PARAGRAPH,
+          OP.RUN_PARAGRAPH,
+          OP.RUN_PARAGRAPH_USING_SPELL,
+          OP.RUN_ALL_PARAGRAPHS,
+          OP.PARAGRAPH_CLEAR_OUTPUT,
+          OP.PARAGRAPH_CLEAR_ALL_OUTPUT,
+          OP.INSERT_PARAGRAPH,
+          OP.MOVE_PARAGRAPH,
+          OP.COPY_PARAGRAPH,
+          OP.PARAGRAPH_REMOVE,
+          OP.MOVE_NOTE_TO_TRASH,
+          OP.DEL_NOTE,
+          OP.PATCH_PARAGRAPH)));
+
   private static final Gson gson = new Gson();
   public static final Message EMPTY = new Message(null);
   
@@ -213,6 +233,10 @@ public class Message implements JsonSerializable {
     return data.get(k);
   }
 
+  public static boolean isDisabledForRunningNotes(OP eventType) {
+    return disabledForRunningNoteMessages.contains(eventType);
+  }
+
   public <T> T getType(String key) {
     return (T) data.get(key);
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/dda5a145/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
index 147b0c8..4e2d95a 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
@@ -451,7 +451,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo
     p3.setText("%mock1 p3");
 
     // when
-    note.runAll();
+    note.runAll(anonymous, true);
 
     assertEquals("repl1: p1", p1.getReturn().message().get(0).getData());
     assertNull(p2.getReturn());
@@ -809,7 +809,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo
     String simpleText = "hello world";
     p.setText(simpleText);
 
-    note.runAll();
+    note.runAll(anonymous, true);
 
     String exportedNoteJson = notebook.exportNote(note.getId());
 
@@ -841,7 +841,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo
 
     final Paragraph p = note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
     p.setText("hello world");
-    note.runAll();
+    note.runAll(anonymous, true);
 
     p.setStatus(Status.RUNNING);
     Note cloneNote = notebook.cloneNote(note.getId(), "clone note", anonymous);
@@ -877,7 +877,7 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo
     for (InterpreterGroup intpGroup : interpreterSettingManager.getAllInterpreterGroup()) {
       intpGroup.setResourcePool(new LocalResourcePool(intpGroup.getId()));
     }
-    note.runAll();
+    note.runAll(anonymous, true);
 
     assertEquals(2, interpreterSettingManager.getAllResources().size());
 
@@ -1491,6 +1491,11 @@ public class NotebookTest extends AbstractInterpreterTest implements ParagraphJo
   }
 
   @Override
+  public void noteRunningStatusChange(String noteId, boolean newStatus) {
+
+  }
+
+  @Override
   public void onProgressUpdate(Paragraph paragraph, int progress) {
   }