Repository: zeppelin
Updated Branches:
  refs/heads/master 71a019e8b -> 7241348cf


[ZEPPELIN-2813] revisions comparator

### What is this PR for?
Sometimes need to see the difference between versions and to switch to another 
version and look for changes are not convenient (the page reloaded). This 
feature allows you to compare any two versions of the notebook.

### What type of PR is it?
Feature

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-2813

### How should this be tested?
1 make some commits. Сchange the contents of paragraphs (delete, add, edit)
2 open Revisions comparator
3 compare revisions and check diff

### Screenshots (if appropriate)
![comparator](https://user-images.githubusercontent.com/25951039/28702781-cf1cedce-7378-11e7-9034-7036f4440bf3.gif)

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

Author: tinkoff-dwh <[email protected]>
Author: Tinkoff DWH <[email protected]>

Closes #2506 from tinkoff-dwh/ZEPPELIN-2813 and squashes the following commits:

acc624f [tinkoff-dwh] Merge remote-tracking branch 'upstream/master' into 
ZEPPELIN-2813
2fd89a8 [tinkoff-dwh] Merge remote-tracking branch 'origin/master' into 
ZEPPELIN-2813
8b8afcc [tinkoff-dwh] Docs edit
efa7ce2 [tinkoff-dwh] Merge branch 'master' into ZEPPELIN-2813
f530524 [tinkoff-dwh] zep-2813 anim off
0e866b2 [tinkoff-dwh] zep_2813 color change
310760e [tinkoff-dwh] zep_2813 UI for REVISIONS COMPARATOR.
3d4f86c [tinkoff-dwh] Merge branch 'master' into ZEPPELIN-2813_refactoring_2
dc67f8f [tinkoff-dwh] [ZEPPELIN-2813] refactoring
514b3f5 [tinkoff-dwh] small fixes, added documentation
4ce5286 [tinkoff-dwh] Merge remote-tracking branch 'origin/master' into 
ZEPPELIN-2813
b949814 [Tinkoff DWH] [ZEPPELIN-2813] license
a192b95 [Tinkoff DWH] [ZEPPELIN-2813] revisions comparator for note


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

Branch: refs/heads/master
Commit: 7241348cff4ef061fbc716f836d2d2d503477008
Parents: 71a019e
Author: tinkoff-dwh <[email protected]>
Authored: Wed Nov 1 11:34:41 2017 +0300
Committer: Lee moon soo <[email protected]>
Committed: Thu Nov 16 15:51:57 2017 -0800

----------------------------------------------------------------------
 LICENSE                                         |   2 +
 docs/_includes/themes/zeppelin/_navigation.html |   1 +
 .../revisions-comparator-comboboxes.png         | Bin 0 -> 2596 bytes
 .../img/docs-img/revisions-comparator-table.png | Bin 0 -> 16509 bytes
 .../docs-img/revisions_comparator_button.png    | Bin 0 -> 19092 bytes
 .../img/docs-img/revisions_comparator_diff.png  | Bin 0 -> 61883 bytes
 .../docs-img/revisions_comparator_paragraph.png | Bin 0 -> 66939 bytes
 docs/index.md                                   |   1 +
 docs/usage/other_features/notebook_actions.md   |  59 ++++
 licenses/LICENSE-jsdiff-3.3.0                   |  31 +++
 .../apache/zeppelin/socket/NotebookServer.java  |  28 +-
 zeppelin-web/bower.json                         |   3 +-
 zeppelin-web/karma.conf.js                      |   1 +
 .../src/app/notebook/notebook-actionBar.html    |   6 +
 .../src/app/notebook/notebook.controller.js     |  48 +++-
 zeppelin-web/src/app/notebook/notebook.html     |   8 +
 .../revisions-comparator.component.js           | 181 ++++++++++++
 .../revisions-comparator.css                    | 276 +++++++++++++++++++
 .../revisions-comparator.html                   | 124 +++++++++
 .../websocket/websocket-event.factory.js        |   2 +
 .../websocket/websocket-message.service.js      |  11 +
 zeppelin-web/src/index.html                     |   1 +
 zeppelin-web/src/index.js                       |   1 +
 .../zeppelin/notebook/socket/Message.java       |   4 +
 24 files changed, 778 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index a6c02de..142bd49 100644
--- a/LICENSE
+++ b/LICENSE
@@ -269,6 +269,8 @@ The following components are provided under the BSD 
3-Clause license.  See file
   (BSD 3 Clause) portions of Scala (http://www.scala-lang.org/download) - 
http://www.scala-lang.org/download/#License
    r/src/main/scala/scala/Console.scala
 
+  (BSD 3 Clause) diff.js (https://github.com/kpdecker/jsdiff)
+
 ========================================================================
 BSD 2-Clause licenses
 ========================================================================

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html 
b/docs/_includes/themes/zeppelin/_navigation.html
index 215c944..bccb5b4 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -61,6 +61,7 @@
                 <li><a 
href="{{BASE_PATH}}/usage/other_features/publishing_paragraphs.html">Publishing 
Paragraphs</a></li>
                 <li><a 
href="{{BASE_PATH}}/usage/other_features/personalized_mode.html">Personalized 
Mode</a></li>
                 <li><a 
href="{{BASE_PATH}}/usage/other_features/customizing_homepage.html">Customizing 
Zeppelin Homepage</a></li>
+                <li><a 
href="{{BASE_PATH}}/usage/other_features/notebook_actions.html">Notebook 
Actions</a></li>
                 <li role="separator" class="divider"></li>
                 <li class="title"><span>REST API</span></li>
                 <li><a 
href="{{BASE_PATH}}/usage/rest_api/interpreter.html">Interpreter API</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-comboboxes.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-comboboxes.png 
b/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-comboboxes.png
new file mode 100644
index 0000000..7eae6a5
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-comboboxes.png 
differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-table.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-table.png 
b/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-table.png
new file mode 100644
index 0000000..6c7b8e6
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/revisions-comparator-table.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_button.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_button.png 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_button.png
new file mode 100644
index 0000000..168809c
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_button.png 
differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_diff.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_diff.png 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_diff.png
new file mode 100644
index 0000000..c1092e9
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_diff.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_paragraph.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_paragraph.png 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_paragraph.png
new file mode 100644
index 0000000..c559c45
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/revisions_comparator_paragraph.png 
differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/index.md
----------------------------------------------------------------------
diff --git a/docs/index.md b/docs/index.md
index dbec040..8f3b551 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -72,6 +72,7 @@ limitations under the License.
   * [Publishing Paragraphs](./usage/other_features/publishing_paragraphs.html) 
results into your external website
   * [Personalized Mode](./usage/other_features/personalized_mode.html) 
   * [Customizing Zeppelin 
Homepage](./usage/other_features/customizing_homepage.html) with one of your 
notebooks
+  * [Notebook actions](./usage/other_features/notebook_actions.html)
 * REST API: available REST API list in Apache Zeppelin
   * [Interpreter API](./usage/rest_api/interpreter.html)
   * [Zeppelin Server API](./usage/rest_api/zeppelin_server.html)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/docs/usage/other_features/notebook_actions.md
----------------------------------------------------------------------
diff --git a/docs/usage/other_features/notebook_actions.md 
b/docs/usage/other_features/notebook_actions.md
new file mode 100644
index 0000000..36cbe9b
--- /dev/null
+++ b/docs/usage/other_features/notebook_actions.md
@@ -0,0 +1,59 @@
+---
+layout: page
+title: "Notebook Actions"
+description: "Description of some actions for notebooks"
+group: usage/other_features
+---
+<!--
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+{% include JB/setup %}
+
+# Revisions comparator
+
+<div id="toc"></div>
+
+Apache Zeppelin allows you to compare revisions of notebook.
+To see which paragraphs have been changed, removed or added.
+This action becomes available if your notebook has more than one revision.
+
+<center><img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/revisions-comparator-button.png"
 height="90%" width="90%"></center>
+
+## How to compare two revisions
+
+For compare two revisions need open dialog of comparator (by click button) and 
click on any revision in the table.
+
+<center><img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/revisions-comparator-table.png"
 height="90%" width="90%"></center>
+
+Or choose two revisions into comboboxes.
+
+<center><img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/revisions-comparator-comboboxes.png"
 height="90%" width="90%"></center>
+
+After click on any revision in the table or selecting the second revision will 
see the result of the comparison.
+
+<center><img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/revisions-comparator-diff.png"
 height="90%" width="90%"></center>
+
+## How to read the result of the comparison
+
+Result it is list of paragraphs which was in both revisions. If paragraph was 
added in second revision ("Head")
+then so it will be marked as <i style="color: green">added</i>, if was deleted 
then it will be marked as
+<i style="color: red">deleted</i>. If paragraph exists in both revisions then 
it marked as <i style="color: orange">there are differences</i>.
+To view the comparison click on the section.
+
+<center><img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/revisions-comparator-paragraph.png"
 height="90%" width="90%"></center>
+
+Сhanges in the text of the paragraph are highlighted in green and red. Red it 
is line (block of lines) which was deleted, green it is line (block of lines) 
which was added).
+
+
+
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/licenses/LICENSE-jsdiff-3.3.0
----------------------------------------------------------------------
diff --git a/licenses/LICENSE-jsdiff-3.3.0 b/licenses/LICENSE-jsdiff-3.3.0
new file mode 100644
index 0000000..4e7146e
--- /dev/null
+++ b/licenses/LICENSE-jsdiff-3.3.0
@@ -0,0 +1,31 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2009-2015, Kevin Decker <[email protected]>
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or 
without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of Kevin Decker nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/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 e3fb004..a3e8714 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
@@ -29,8 +29,6 @@ import java.util.regex.Pattern;
 
 import javax.servlet.http.HttpServletRequest;
 
-import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.vfs2.FileSystemException;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
@@ -45,8 +43,8 @@ import org.apache.zeppelin.interpreter.*;
 import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
 import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
-import org.apache.zeppelin.notebook.JobListenerFactory;
 import org.apache.zeppelin.notebook.Folder;
+import org.apache.zeppelin.notebook.JobListenerFactory;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.Notebook;
 import org.apache.zeppelin.notebook.NotebookAuthorization;
@@ -77,7 +75,9 @@ import org.quartz.SchedulerException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Queues;
+import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
@@ -332,6 +332,9 @@ public class NotebookServer extends WebSocketServlet
         case NOTE_REVISION:
           getNoteByRevision(conn, notebook, messagereceived);
           break;
+        case NOTE_REVISION_FOR_COMPARE:
+          getNoteByRevisionForCompare(conn, notebook, messagereceived);
+          break;
         case LIST_NOTE_JOBS:
           unicastNoteJobInfo(conn, messagereceived);
           break;
@@ -1943,6 +1946,25 @@ public class NotebookServer extends WebSocketServlet
             .put("note", revisionNote)));
   }
 
+  private void getNoteByRevisionForCompare(NotebookSocket conn, Notebook 
notebook,
+      Message fromMessage) throws IOException {
+    String noteId = (String) fromMessage.get("noteId");
+    String revisionId = (String) fromMessage.get("revisionId");
+
+    String position = (String) fromMessage.get("position");
+    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
+    Note revisionNote;
+    if (revisionId.equals("Head")) {
+      revisionNote = notebook.getNote(noteId);
+    } else {
+      revisionNote = notebook.getNoteByRevision(noteId, revisionId, subject);
+    }
+
+    conn.send(serializeMessage(
+        new Message(OP.NOTE_REVISION_FOR_COMPARE).put("noteId", noteId)
+            .put("revisionId", revisionId).put("position", 
position).put("note", revisionNote)));
+  }
+
   /**
    * This callback is for the paragraph that runs on ZeppelinServer
    *

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/bower.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index a68c2e9..2b5135f 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -32,7 +32,8 @@
     "bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
     "select2": "^4.0.3",
     "MathJax": "2.7.0",
-    "ngclipboard": "^1.1.1"
+    "ngclipboard": "^1.1.1",
+    "jsdiff": "3.3.0"
   },
   "devDependencies": {
     "angular-mocks": "1.5.7"

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/karma.conf.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/karma.conf.js b/zeppelin-web/karma.conf.js
index 1c79346..8a03bec 100644
--- a/zeppelin-web/karma.conf.js
+++ b/zeppelin-web/karma.conf.js
@@ -86,6 +86,7 @@ module.exports = function(config) {
       'bower_components/MathJax/MathJax.js',
       'bower_components/clipboard/dist/clipboard.js',
       'bower_components/ngclipboard/dist/ngclipboard.js',
+      'bower_components/jsdiff/diff.js',
       'bower_components/angular-mocks/angular-mocks.js',
       // endbower
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/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 b121fee..0db4ff0 100644
--- a/zeppelin-web/src/app/notebook/notebook-actionBar.html
+++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html
@@ -143,6 +143,12 @@ limitations under the License.
             </div>
           </li>
         </ul>
+        <button type="button"
+                class="btn btn-default btn-xs"
+                ng-click="toggleRevisionsComparator()"
+                tooltip-placement="bottom" uib-tooltip="Compare revisions">
+          <i class="fa fa-exchange"></i>
+        </button>
       </div>
       <div class="btn-group" role="group">
         <button type="button" class="btn btn-default btn-xs revisionName" 
title="{{currentRevision}}">

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/app/notebook/notebook.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js 
b/zeppelin-web/src/app/notebook/notebook.controller.js
index bdd47c2..456e463 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -31,6 +31,7 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
   $scope.tableToggled = false
   $scope.viewOnly = false
   $scope.showSetting = false
+  $scope.showRevisionsComparator = false
   $scope.looknfeelOption = ['default', 'simple', 'report']
   $scope.cronOption = [
     {name: 'None', value: undefined},
@@ -247,13 +248,24 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
     })
   }
 
+  $scope.preVisibleRevisionsComparator = function() {
+    $scope.mergeNoteRevisionsForCompare = null
+    $scope.firstNoteRevisionForCompare = null
+    $scope.secondNoteRevisionForCompare = null
+    $scope.currentFirstRevisionForCompare = 'Choose...'
+    $scope.currentSecondRevisionForCompare = 'Choose...'
+    $scope.$apply()
+  }
+
   $scope.$on('listRevisionHistory', function (event, data) {
     console.debug('received list of revisions %o', data)
     $scope.noteRevisions = data.revisionList
-    $scope.noteRevisions.splice(0, 0, {
-      id: 'Head',
-      message: 'Head'
-    })
+    if ($scope.noteRevisions.length === 0 || $scope.noteRevisions[0].id !== 
'Head') {
+      $scope.noteRevisions.splice(0, 0, {
+        id: 'Head',
+        message: 'Head'
+      })
+    }
     if ($routeParams.revisionId) {
       let index = _.findIndex($scope.noteRevisions, {'id': 
$routeParams.revisionId})
       if (index > -1) {
@@ -579,6 +591,12 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
     orderChanged: function (event) {}
   }
 
+  $scope.closeAdditionalBoards = function() {
+    $scope.closeSetting()
+    $scope.closePermissions()
+    $scope.closeRevisionsComparator()
+  }
+
   $scope.openSetting = function () {
     $scope.showSetting = true
     getInterpreterBindings()
@@ -628,8 +646,26 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
     if ($scope.showSetting) {
       $scope.closeSetting()
     } else {
+      $scope.closeAdditionalBoards()
       $scope.openSetting()
-      $scope.closePermissions()
+      angular.element('html, body').animate({ scrollTop: 0 }, 'slow')
+    }
+  }
+
+  $scope.openRevisionsComparator = function () {
+    $scope.showRevisionsComparator = true
+  }
+
+  $scope.closeRevisionsComparator = function () {
+    $scope.showRevisionsComparator = false
+  }
+
+  $scope.toggleRevisionsComparator = function () {
+    if ($scope.showRevisionsComparator) {
+      $scope.closeRevisionsComparator()
+    } else {
+      $scope.closeAdditionalBoards()
+      $scope.openRevisionsComparator()
       angular.element('html, body').animate({ scrollTop: 0 }, 'slow')
     }
   }
@@ -1064,8 +1100,8 @@ function NotebookCtrl ($scope, $route, $routeParams, 
$location, $rootScope,
         angular.element('#selectRunners').select2({})
         angular.element('#selectWriters').select2({})
       } else {
+        $scope.closeAdditionalBoards()
         $scope.openPermissions()
-        $scope.closeSetting()
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/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 4b1c0b9..9441f6e 100644
--- a/zeppelin-web/src/app/notebook/notebook.html
+++ b/zeppelin-web/src/app/notebook/notebook.html
@@ -14,6 +14,14 @@ limitations under the License.
 <!-- Here the controller <NotebookCtrl> is not needed because explicitly set 
in the app.js (route) -->
 <div id="actionbar" ng-include 
src="'app/notebook/notebook-actionBar.html'"></div>
 <div id="content" class="notebookContent">
+  <!-- revisions comparator-->
+  <div ng-if="showRevisionsComparator" class="revisions-comparator">
+    <div>
+      <h4>Revisions comparator</h4>
+    </div>
+    <hr />
+    <revisions-comparator 
note-revisions="noteRevisions"></revisions-comparator>
+  </div>
   <!-- settings -->
   <div ng-if="showSetting" class="setting">
     <div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.component.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.component.js
 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.component.js
new file mode 100644
index 0000000..45db38a
--- /dev/null
+++ 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.component.js
@@ -0,0 +1,181 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import revisionsComparatorTemplate from './revisions-comparator.html'
+import './revisions-comparator.css'
+import moment from 'moment'
+
+function RevisionsComparatorController($scope, websocketMsgSrv, $routeParams) {
+  'ngInject'
+
+  $scope.firstNoteRevisionForCompare = null
+  $scope.secondNoteRevisionForCompare = null
+  $scope.mergeNoteRevisionsForCompare = null
+  $scope.currentParagraphDiffDisplay = null
+  $scope.currentFirstRevisionForCompare = 'Choose...'
+  $scope.currentSecondRevisionForCompare = 'Choose...'
+
+  $scope.getNoteRevisionForReview = function (revision, position) {
+    if (position) {
+      if (position === 'first') {
+        $scope.currentFirstRevisionForCompare = revision.message
+      } else {
+        $scope.currentSecondRevisionForCompare = revision.message
+      }
+      websocketMsgSrv.getNoteByRevisionForCompare($routeParams.noteId, 
revision.id, position)
+    }
+  }
+
+  // compare revisions
+  $scope.compareRevisions = function () {
+    if ($scope.firstNoteRevisionForCompare && 
$scope.secondNoteRevisionForCompare) {
+      let paragraphs1 = $scope.firstNoteRevisionForCompare.note.paragraphs
+      let paragraphs2 = $scope.secondNoteRevisionForCompare.note.paragraphs
+      let added = 'added'
+      let deleted = 'deleted'
+      let compared = 'compared'
+      let merge = []
+      for (let p1 of paragraphs1) {
+        let p2 = null
+        for (let p of paragraphs2) {
+          if (p1.id === p.id) {
+            p2 = p
+            break
+          }
+        }
+        if (p2 === null) {
+          merge.push({paragraph: p1, firstString: (p1.text || 
'').split('\n')[0], type: deleted})
+        } else {
+          let colorClass = ''
+          let span = null
+          let text1 = p1.text || ''
+          let text2 = p2.text || ''
+
+          let diff = window.JsDiff.diffLines(text1, text2)
+          let diffHtml = document.createDocumentFragment()
+          let identical = true
+          let identicalClass = 'color-black'
+
+          diff.forEach(function (part) {
+            colorClass = part.added ? 'color-green-row' : part.removed ? 
'color-red-row' : identicalClass
+            span = document.createElement('span')
+            span.className = colorClass
+            if (identical && colorClass !== identicalClass) {
+              identical = false
+            }
+
+            let str = part.value
+
+            if (str[str.length - 1] !== '\n') {
+              str = str + '\n'
+            }
+
+            span.appendChild(document.createTextNode(str))
+            diffHtml.appendChild(span)
+          })
+
+          let pre = document.createElement('pre')
+          pre.appendChild(diffHtml)
+
+          merge.push(
+            {
+              paragraph: p1,
+              diff: pre.innerHTML,
+              identical: identical,
+              firstString: (p1.text || '').split('\n')[0],
+              type: compared
+            })
+        }
+      }
+
+      for (let p2 of paragraphs2) {
+        let p1 = null
+        for (let p of paragraphs1) {
+          if (p2.id === p.id) {
+            p1 = p
+            break
+          }
+        }
+        if (p1 === null) {
+          merge.push({paragraph: p2, firstString: (p2.text || 
'').split('\n')[0], type: added})
+        }
+      }
+
+      merge.sort(function (a, b) {
+        if (a.type === added) {
+          return -1
+        }
+        if (a.type === compared) {
+          return 1
+        }
+        if (a.type === deleted) {
+          if (b.type === compared) {
+            return -1
+          } else {
+            return 1
+          }
+        }
+      })
+
+      $scope.mergeNoteRevisionsForCompare = merge
+
+      if ($scope.currentParagraphDiffDisplay !== null) {
+        
$scope.changeCurrentParagraphDiffDisplay($scope.currentParagraphDiffDisplay.paragraph.id)
+      }
+    }
+  }
+
+  $scope.$on('noteRevisionForCompare', function (event, data) {
+    console.debug('received note revision for compare %o', data)
+    if (data.note && data.position) {
+      if (data.position === 'first') {
+        $scope.firstNoteRevisionForCompare = data
+      } else {
+        $scope.secondNoteRevisionForCompare = data
+      }
+
+      if ($scope.firstNoteRevisionForCompare !== null && 
$scope.secondNoteRevisionForCompare !== null &&
+        $scope.firstNoteRevisionForCompare.revisionId !== 
$scope.secondNoteRevisionForCompare.revisionId) {
+        $scope.compareRevisions()
+      }
+    }
+  })
+
+  $scope.formatRevisionDate = function (date) {
+    return moment.unix(date).format('MMMM Do YYYY, h:mm:ss a')
+  }
+
+  $scope.changeCurrentParagraphDiffDisplay = function (paragraphId) {
+    for (let p of $scope.mergeNoteRevisionsForCompare) {
+      if (p.paragraph.id === paragraphId) {
+        $scope.currentParagraphDiffDisplay = p
+        return
+      }
+    }
+    $scope.currentParagraphDiffDisplay = null
+  }
+}
+
+export const RevisionsComparatorComponent = {
+  template: revisionsComparatorTemplate,
+  controller: RevisionsComparatorController,
+  bindings: {
+    noteRevisions: '<'
+  }
+}
+
+export const RevisionsComparatorModule = angular
+  .module('zeppelinWebApp')
+  .component('revisionsComparator', RevisionsComparatorComponent)
+  .name

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.css
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.css 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.css
new file mode 100644
index 0000000..3ec60c4
--- /dev/null
+++ 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.css
@@ -0,0 +1,276 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.revisions-comparator {
+  background: white;
+  padding: 10px 15px 15px 15px;
+  margin-left: -10px;
+  margin-right: -10px;
+  font-family: 'Roboto', sans-serif;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+  border-bottom: 1px solid #E5E5E5;
+}
+
+.revisions-comparator-panel {
+  transition-property: border, left, background-color;
+  transition-duration: 250ms, 500ms, 200ms;
+  transition-timing-function: ease-out;
+  position: relative;
+  left: 0;
+  width: 95%;
+  background-color: rgba(255, 255, 0, 0.10);
+  border: 1px solid rgba(120, 129, 82, 0.08);
+  margin-right: 15px;
+  min-width: 270px;
+}
+
+.revisions-comparator-panel:first-child {
+  margin-top: 5px;
+}
+
+.revisions-comparator-panel:last-child {
+  margin-bottom: 5px;
+}
+
+.revisions-comparator-panel:hover {
+  border: 1px solid rgba(55, 54, 35, 0.35);
+  background-color: rgba(255, 204, 0, 0.15);
+}
+
+.revisions-comparator-panel-selected {
+  background-color: rgba(252, 255, 0, 0.53) !important;
+  border: 1px solid rgba(55, 54, 35, 0.49)
+}
+
+.revisions-comparator-panel-heading {
+  padding: 10px;
+  cursor: pointer;
+}
+
+.revisions-comparator-panel.ng-enter,
+.revisions-comparator-panel.ng-enter.ng-enter-active ,
+.revisions-comparator-panel.ng-leave,
+.revisions-comparator-panel.ng-leave-active {
+  transition-duration: 0s !important;
+}
+
+.cursor-hand {
+  cursor: pointer;
+}
+
+.paragraphs-div {
+  overflow: auto;
+  height: 35vh;
+}
+
+.paragraphs-div-border {
+  border: 1px solid black;
+  border-radius: 10px;
+  padding: 10px;
+}
+
+.commit-tree {
+  width: 100%;
+  margin-right: 0;
+  margin-bottom: 10px;
+  border: 2px solid grey;
+  border-radius: 5px !important;
+}
+
+.commit-rows {
+  height: 30vh;
+  overflow: auto;
+  display: block;
+  width: 100%;
+}
+
+.commit-rows tr:nth-child(even) {
+  background-color: rgba(128, 128, 128, 0.06);
+}
+
+.commit-tree tr:hover {
+  background-color: rgba(48, 113, 169, 0.21);
+}
+
+.selected-revision {
+  background-color: rgba(48, 113, 169, 0.47) !important;
+}
+
+.commit-tree table {
+  border-collapse: collapse;
+  table-layout: fixed;
+  padding: 2px;
+  margin-bottom: 0px;
+}
+
+.commit-tree tr {
+  width: 100%;
+}
+
+.commit-tree td:nth-child(1),
+.commit-tree th:nth-child(1) {
+  width: 10%;
+}
+
+.commit-tree td:nth-child(2),
+.commit-tree th:nth-child(2) {
+  width: 20%;
+}
+
+.commit-tree th{
+  font-weight: normal;
+  font-size: 1.2em;
+  background-color: #317bb4;
+  color: rgb(255, 255, 255);
+  text-align: center;
+}
+
+.commit-tree .commit-rows td{
+  padding-left: 15px;
+}
+
+.commit-tree thead tr {
+  display: block;
+  position: relative;
+}
+
+.empty-code-panel {
+  text-align: center;
+  padding-top: 25%;
+  font-size: 30px;
+  color: grey;
+}
+
+.empty-paragraph-message {
+  font-size: 2em;
+  color: grey;
+  margin: 0 auto;
+  text-align: center;
+  display: table-cell;
+  vertical-align: middle;
+}
+
+.revisions-comparator-bar {
+  margin-left: 25px;
+
+}
+
+.revisions-comparator-bar .btn-group {
+  margin: 0 20px;
+}
+
+.revisions-comparator-code-panel {
+  display: block;
+  clear: both;
+  width: 95%;
+  float: left;
+  height: 70vh;
+  overflow-y: auto;
+}
+
+.revisions-comparator-code-panel-title {
+  width: 50%;
+  float: left;
+  font-size: 14px;
+  padding: 5px;
+}
+
+.revisions-comparator-bar {
+  width: 400px;
+  padding-bottom: 10px;
+}
+
+.revisions-comparator-status {
+  font-size: 12px;
+  padding-left: 10px;
+}
+
+#diffPanel {
+  height: 100%;
+  padding-left: 10px;
+  border: 2px solid grey;
+  border-radius: 5px !important;
+}
+
+#diffPanel .panel-group {
+  height: inherit;
+  overflow: auto;
+}
+
+.revision-name-for-compare {
+  cursor: default;
+  overflow: hidden;
+  vertical-align: bottom;
+  display: inline-block;
+  max-width: 100px;
+  padding: 1px 5px;
+}
+
+.revisions-comparator-caret {
+  padding-bottom: 5px;
+}
+
+.revisions-comparator-link, .revisions-comparator-link:hover,
+.revisions-comparator-link:visited, .revisions-comparator-link:focus {
+  text-decoration: none;
+  outline: none;
+  color: #000;
+}
+
+.revisions-comparator-first-string {
+  display: block;
+  height: 2em;
+  overflow: hidden;
+  padding-top: 6px;
+  padding-left: 5px;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  font-size: 12px;
+  color: grey;
+}
+
+.revisions-comparator-dropdown {
+  padding: 5px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+
+.color-green-row {
+  background-color: rgba(0, 226, 0, 0.22);
+  display: block;
+  color: green;
+}
+
+.color-red-row {
+  background-color: rgba(226, 0, 0, 0.22);
+  display: block;
+  color: red;
+}
+
+.color-green {
+  color: green;
+}
+
+.color-red {
+  color: red;
+}
+
+.color-black {
+  color: black;
+}
+
+.color-orange {
+  color: orange;
+}
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.html
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.html 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.html
new file mode 100644
index 0000000..37cdc5f
--- /dev/null
+++ 
b/zeppelin-web/src/app/notebook/revisions-comparator/revisions-comparator.html
@@ -0,0 +1,124 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<div class="col-md-4">
+
+  <div class="commit-tree">
+    <table class="table">
+      <thead>
+      <tr>
+        <th>Revision name</th>
+        <th>Date</th>
+      </tr>
+      </thead>
+      <tbody class="commit-rows">
+      <tr ng-repeat="revision in $ctrl.noteRevisions | orderBy:'time':true"
+          ng-class="{'cursor-hand': !$last, 'selected-revision': 
revision.message === currentSecondRevisionForCompare}"
+          ng-click="getNoteRevisionForReview($ctrl.noteRevisions[$index + 1], 
'first'); getNoteRevisionForReview(revision, 'second')">
+        <td>{{revision.message}}</td>
+        <td>{{formatRevisionDate(revision.time)}}</td>
+      </tr>
+      </tbody>
+    </table>
+  </div>
+
+  <div class="revisions-comparator-bar">
+    <div class="btn-group">
+      <button type="button" ng-if="$ctrl.noteRevisions.length > 0"
+              class="btn btn-default revisions-comparator-dropdown 
dropdown-toggle"
+              data-toggle="dropdown" id="firstRevisionDropdown" 
title="{{currentFirstRevisionForCompare}}">
+        <div 
class="revision-name-for-compare">{{currentFirstRevisionForCompare}}</div>
+        <span class="caret revisions-comparator-caret"></span>
+      </button>
+      <ul class="dropdown-menu dropdown-menu-left" 
aria-labelledby="firstRevisionDropdown">
+        <li></li>
+        <li ng-repeat="revision in $ctrl.noteRevisions | orderBy:'time':true" 
class="revision">
+          <a style="cursor:pointer" 
ng-click="getNoteRevisionForReview(revision, 'first')">
+          <span style="display: block;">
+            <strong>{{revision.message}}</strong>
+          </span>
+            <span class="revisionDate">
+            <em>{{formatRevisionDate(revision.time)}}</em>
+          </span>
+          </a>
+        </li>
+      </ul>
+    </div>
+    <span>compare with</span>
+    <div class="btn-group">
+      <button type="button" ng-if="$ctrl.noteRevisions.length > 0"
+              class="btn btn-default revisions-comparator-dropdown 
dropdown-toggle"
+              ng-disabled="firstNoteRevisionForCompare === null"
+              data-toggle="dropdown" id="secondRevisionDropdown" 
title="{{currentSecondRevisionForCompare}}">
+        <div 
class="revision-name-for-compare">{{currentSecondRevisionForCompare}}</div>
+        <span class="caret revisions-comparator-caret"></span>
+      </button>
+      <ul class="dropdown-menu dropdown-menu-left" 
aria-labelledby="secondRevisionDropdown">
+        <li ng-repeat="revision in $ctrl.noteRevisions | orderBy:'time':true" 
class="revision">
+          <a style="cursor:pointer" 
ng-click="getNoteRevisionForReview(revision, 'second')">
+          <span style="display: block;">
+            <strong>{{revision.message}}</strong>
+          </span>
+            <span class="revisionDate">
+            <em>{{formatRevisionDate(revision.time)}}</em>
+          </span>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+  <div id="diffPanel">
+    <div class="panel-group" style="margin-bottom: 0px">
+      <div class="paragraphs-div">
+        <div class="panel revisions-comparator-panel" data-ng-repeat="p in 
mergeNoteRevisionsForCompare | orderBy:'time':true"
+             ng-class="{'revisions-comparator-panel-selected' : 
currentParagraphDiffDisplay.paragraph.id === p.paragraph.id}">
+          <div class="revisions-comparator-panel-heading"
+               ng-click="changeCurrentParagraphDiffDisplay(p.paragraph.id)">
+            <h4 class="panel-title">
+              {{p.paragraph.id}}<strong style="padding: 5px;" 
ng-if="p.paragraph.title">({{p.paragraph.title}})</strong>
+              <i ng-if="p.type === 'added'" class="revisions-comparator-status 
color-green">added</i>
+              <i ng-if="p.type === 'deleted'" 
class="revisions-comparator-status color-red">deleted</i>
+              <i ng-if="p.type === 'compared' && !(p.identical)" 
class="revisions-comparator-status color-orange">there
+                are differences</i>
+              <i ng-if="p.type === 'compared' && p.identical" 
class="revisions-comparator-status">contents are
+                identical</i>
+              <i 
class="revisions-comparator-first-string">{{p.firstString}}</i>
+            </h4>
+          </div>
+        </div>
+        <div style="display: table; width: 100%; height: 100%"
+             ng-if="currentSecondRevisionForCompare === 'Choose...'">
+          <div class="empty-paragraph-message">
+            Please select a revision
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div id="revisions-comparator-code-panel-id center-block" class="panel 
col-md-8">
+  <span
+    class="revisions-comparator-code-panel-title">Revision: 
<strong>{{currentFirstRevisionForCompare}} --> 
{{currentSecondRevisionForCompare}}</strong></span>
+  <pre ng-if="currentParagraphDiffDisplay.type === 'added'"
+       class="revisions-comparator-code-panel 
color-green-row">{{currentParagraphDiffDisplay.paragraph.text}}</pre>
+  <pre ng-if="currentParagraphDiffDisplay.type === 'deleted'"
+       class="revisions-comparator-code-panel 
color-red-row">{{currentParagraphDiffDisplay.paragraph.text}}</pre>
+  <pre ng-if="currentParagraphDiffDisplay.type === 'compared'"
+       class="revisions-comparator-code-panel" 
ng-bind-html="currentParagraphDiffDisplay.diff"></pre>
+  <pre ng-if="currentParagraphDiffDisplay === null"
+       class="revisions-comparator-code-panel empty-code-panel"><div>Nothing 
to display</div></pre>
+</div>
+
+<div class="clearfix"></div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/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 10cfd9c..d4bfadf 100644
--- a/zeppelin-web/src/components/websocket/websocket-event.factory.js
+++ b/zeppelin-web/src/components/websocket/websocket-event.factory.js
@@ -138,6 +138,8 @@ function WebsocketEventFactory ($rootScope, $websocket, 
$location, baseUrlSrv) {
       $rootScope.$broadcast('listRevisionHistory', data)
     } else if (op === 'NOTE_REVISION') {
       $rootScope.$broadcast('noteRevision', data)
+    } else if (op === 'NOTE_REVISION_FOR_COMPARE') {
+      $rootScope.$broadcast('noteRevisionForCompare', data)
     } else if (op === 'INTERPRETER_BINDINGS') {
       $rootScope.$broadcast('interpreterBindings', data)
     } else if (op === 'ERROR_INFO') {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/components/websocket/websocket-message.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocket/websocket-message.service.js 
b/zeppelin-web/src/components/websocket/websocket-message.service.js
index cafc61b..ab97fa8 100644
--- a/zeppelin-web/src/components/websocket/websocket-message.service.js
+++ b/zeppelin-web/src/components/websocket/websocket-message.service.js
@@ -295,6 +295,17 @@ function WebsocketMessageService ($rootScope, 
websocketEvents) {
       })
     },
 
+    getNoteByRevisionForCompare: function (noteId, revisionId, position) {
+      websocketEvents.sendNewEvent({
+        op: 'NOTE_REVISION_FOR_COMPARE',
+        data: {
+          noteId: noteId,
+          revisionId: revisionId,
+          position: position
+        }
+      })
+    },
+
     getEditorSetting: function (paragraphId, replName) {
       websocketEvents.sendNewEvent({
         op: 'EDITOR_SETTING',

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 4b43179..9a126f1 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -165,6 +165,7 @@ limitations under the License.
     <script src="bower_components/MathJax/MathJax.js"></script>
     <script src="bower_components/clipboard/dist/clipboard.js"></script>
     <script src="bower_components/ngclipboard/dist/ngclipboard.js"></script>
+    <script src="bower_components/jsdiff/diff.js"></script>
     <!-- endbower -->
     <!-- endbuild -->
   </body>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/zeppelin-web/src/index.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.js b/zeppelin-web/src/index.js
index 3cf052b..ed8f1d8 100644
--- a/zeppelin-web/src/index.js
+++ b/zeppelin-web/src/index.js
@@ -42,6 +42,7 @@ import './app/interpreter/interpreter-item.directive.js'
 import './app/interpreter/widget/number-widget.directive.js'
 import './app/credential/credential.controller.js'
 import './app/configuration/configuration.controller.js'
+import './app/notebook/revisions-comparator/revisions-comparator.component.js'
 import './app/notebook/paragraph/paragraph.controller.js'
 import './app/notebook/paragraph/clipboard.controller.js'
 import './app/notebook/paragraph/resizable.directive.js'

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7241348c/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 06c83e1..d99bd59 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
@@ -152,6 +152,10 @@ public class Message implements JsonSerializable {
     SET_NOTE_REVISION,            // [c-s] set current notebook head to this 
revision
                                   // @param noteId
                                   // @param revisionId
+    NOTE_REVISION_FOR_COMPARE,    // [c-s] get certain revision of note for 
compare
+                                  // @param noteId
+                                  // @param revisionId
+                                  // @param position
     APP_APPEND_OUTPUT,            // [s-c] append output
     APP_UPDATE_OUTPUT,            // [s-c] update (replace) output
     APP_LOAD,                     // [s-c] on app load

Reply via email to