Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master fe4d62518 -> 7a58d4610


Zeppelin-540: Notebook versioning control, commit from frontend notebook menu

### What is this PR for?
This PR improves the front-end experience to use git versioning on notebooks.
Any community feedback is welcome.

### What type of PR is it?
Improvement

### Todos
* [x] - front-end and back-end changes
* [x]  - add `checkpoint` interface
* [x] - ~~error propagation to front-end~~ in a separate PR
* [x] - tests
* [x] - UI feedback?

### Is there a relevant Jira issue?
[ZEPPELIN-540](https://issues.apache.org/jira/browse/ZEPPELIN-540)

### How should this be tested?
1. Uncomment `GitNotebookRepo` as a storage in `/conf/zeppelin-site.xml`
2. Start Zeppelin
3. Make changes to some notebook
4. Use `git` menu to commit
5. To check whether successfully committed, do `git log` in `/notebooks` folder

### Screenshots (if appropriate)
![zeppelin commit 
notebook](https://cloud.githubusercontent.com/assets/1642088/12534987/3a0e72c2-c2b5-11e5-827e-33ba36826637.gif)

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

Author: Khalid Huseynov <[email protected]>

Closes #577 from khalidhuseynov/feat/git-commit-frontend-access and squashes 
the following commits:

4f04af0 [Khalid Huseynov] remove initial commit at start
64111e0 [Khalid Huseynov] Merge branch 'master' into feat/commit-frontend-access
5f10b46 [Khalid Huseynov] backend gitCommit -> commit
ef3a8fb [Khalid Huseynov] Merge branch 'master' into feat/commit-frontend-access
8b5766f [Khalid Huseynov] change icon  git -> file-code-o
74db747 [Khalid Huseynov] remove git from wording
a03a953 [Khalid Huseynov] add repoSync checkpoint test for 1 storage
33f6e0f [Khalid Huseynov] add gitNotebookRepo checkpoint test
b0fa219 [Khalid Huseynov] only commit message
339c9fe [Khalid Huseynov] Merge  'master' into feat/git-commit-frontend-access
39c2873 [Khalid Huseynov] use sinqle quotes
b8fc035 [Khalid Huseynov] empty input field after commit message is hit
c9192cb [Khalid Huseynov] temp fix for padding issue
b69f2ff [Khalid Huseynov] change div to button
39c8e00 [Khalid Huseynov] fix formatting for checkstyle
32e403c [Khalid Huseynov] propagate changes to frontend
bdeb0ed [Khalid Huseynov] add checkpoint interface and propagate to backend
de7bdd6 [Khalid Huseynov] add checkpoint interface
a1894c2 [Khalid Huseynov] backend handling of git notebook commit
8069ccd [Khalid Huseynov] add frontend handler via websocket
2ebe628 [Khalid Huseynov] add button and git menu


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

Branch: refs/heads/master
Commit: 7a58d46103d8db4b81d4b2e2a18e9524a57cbd28
Parents: fe4d625
Author: Khalid Huseynov <[email protected]>
Authored: Tue Feb 9 02:38:46 2016 +0900
Committer: Lee moon soo <[email protected]>
Committed: Sat Feb 13 14:01:16 2016 +0900

----------------------------------------------------------------------
 .../org/apache/zeppelin/socket/Message.java     |  9 +++-
 .../apache/zeppelin/socket/NotebookServer.java  | 10 ++++
 .../src/app/notebook/notebook-actionBar.html    | 26 +++++++++++
 .../src/app/notebook/notebook.controller.js     | 15 ++++++
 .../websocketEvents/websocketMsg.service.js     | 10 ++++
 .../org/apache/zeppelin/notebook/Notebook.java  |  4 ++
 .../zeppelin/notebook/repo/GitNotebookRepo.java | 21 ++++++---
 .../zeppelin/notebook/repo/NotebookRepo.java    |  5 ++
 .../notebook/repo/NotebookRepoSync.java         | 26 ++++++++++-
 .../zeppelin/notebook/repo/S3NotebookRepo.java  |  6 +++
 .../zeppelin/notebook/repo/VFSNotebookRepo.java |  6 +++
 .../notebook/repo/GitNotebookRepoTest.java      | 48 +++++++++++++++++++-
 .../notebook/repo/NotebookRepoSyncTest.java     | 43 +++++++++++++++++-
 13 files changed, 217 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
index 2a8e06d..54d9ab2 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/Message.java
@@ -99,12 +99,17 @@ public class Message {
 
     ANGULAR_OBJECT_UPDATE,  // [s-c] add/update angular object
     ANGULAR_OBJECT_REMOVE,  // [s-c] add angular object del
-
+    
     ANGULAR_OBJECT_UPDATED,  // [c-s] angular object value updated,
 
     LIST_CONFIGURATIONS, // [c-s] ask all key/value pairs of configurations
-    CONFIGURATIONS_INFO // [s-c] all key/value pairs of configurations
+    CONFIGURATIONS_INFO, // [s-c] all key/value pairs of configurations
                   // @param settings serialized Map<String, String> object
+
+    CHECKPOINT_NOTEBOOK     // [c-s] checkpoint notebook to storage repository
+                            // @param noteId
+                            // @param checkpointName
+
   }
 
   public OP op;

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/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 9a4a378..5ae4ad7 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
@@ -171,6 +171,9 @@ public class NotebookServer extends WebSocketServlet 
implements
           case LIST_CONFIGURATIONS:
             sendAllConfigurations(conn, notebook);
             break;
+          case CHECKPOINT_NOTEBOOK:
+            checkpointNotebook(conn, notebook, messagereceived);
+            break;
           default:
             broadcastNoteList();
             break;
@@ -736,6 +739,13 @@ public class NotebookServer extends WebSocketServlet 
implements
         .put("configurations", configurations)));
   }
 
+  private void checkpointNotebook(NotebookSocket conn, Notebook notebook,
+      Message fromMessage) throws IOException {
+    String noteId = (String) fromMessage.get("noteId");
+    String commitMessage = (String) fromMessage.get("commitMessage");
+    notebook.checkpointNote(noteId, commitMessage);
+  }
+
   /**
    * This callback is for the paragraph that runs on ZeppelinServer
    * @param noteId

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/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 0340e28..f69a087 100644
--- a/zeppelin-web/src/app/notebook/notebook-actionBar.html
+++ b/zeppelin-web/src/app/notebook/notebook-actionBar.html
@@ -61,6 +61,32 @@ limitations under the License.
               tooltip-placement="bottom" tooltip="Export the notebook">
         <i class="fa fa-download"></i>
       </button>
+        <ul class="dropdown-menu" role="menu" style="width:250px">
+          <li>
+            <div class="cron-preset-container">
+              <div>
+                <input type="text"
+                       dropdown-input
+                       placeholder="commit message"
+                       id="note.checkpoint.message"
+                       ng-model="note.checkpoint.message"/>
+                <button type="button"
+                  class="btn btn-default btn-xs"
+                  ng-hide="viewOnly"
+                  ng-click="checkpointNotebook(note.checkpoint.message)"
+                  tooltip-placement="bottom" tooltip="Commit the 
notebook">Commit
+                </button>
+              </div>
+            </div>
+          </li>
+        </ul>
+        <button type="button"
+                class="btn btn-default btn-xs dropdown-toggle"
+                ng-hide="viewOnly"
+                data-toggle="dropdown"
+                tooltip-placement="bottom" tooltip="Version control">
+          <i class="fa fa-file-code-o"></i>
+        </button>
     </span>
 
 <!-- put the delete action by itself for your protection -->

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/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 5919e4e..859ea33 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -154,6 +154,21 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl',
     });
   };
 
+  // checkpoint/commit notebook
+  $scope.checkpointNotebook = function(commitMessage) {
+    BootstrapDialog.confirm({
+      closable: true,
+      title: '',
+      message: 'Commit notebook to current repository?',
+      callback: function(result) {
+        if (result) {
+          websocketMsgSrv.checkpointNotebook($routeParams.noteId, 
commitMessage);
+        }
+      }
+    });
+    document.getElementById('note.checkpoint.message').value='';
+  };
+
   $scope.runNote = function() {
     BootstrapDialog.confirm({
       closable: true,

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js 
b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
index df44010..245ab77 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js
@@ -128,6 +128,16 @@ 
angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope,
       });
     },
 
+    checkpointNotebook: function(noteId, commitMessage) {
+      websocketEvents.sendNewEvent({
+        op: 'CHECKPOINT_NOTEBOOK',
+        data: {
+          noteId: noteId,
+          commitMessage: commitMessage
+        }
+      });
+    },
+
     isConnected: function(){
       return websocketEvents.isConnected();
     }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/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 df80cd0..14d2fd8 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
@@ -307,6 +307,10 @@ public class Notebook {
     }
   }
 
+  public void checkpointNote(String noteId, String checkpointMessage) throws 
IOException {
+    notebookRepo.checkpoint(noteId, checkpointMessage);
+  }
+
   @SuppressWarnings("rawtypes")
   private Note loadNoteFromRepo(String id) {
     Note note = null;

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java
index fe49753..85a534e 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/GitNotebookRepo.java
@@ -25,6 +25,7 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.notebook.Note;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoHeadException;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
@@ -61,28 +62,33 @@ public class GitNotebookRepo extends VFSNotebookRepo 
implements NotebookRepoVers
       localRepo.create();
     }
     git = new Git(localRepo);
-    maybeAddAndCommit(".");
   }
 
   @Override
   public synchronized void save(Note note) throws IOException {
     super.save(note);
-    maybeAddAndCommit(note.getId());
   }
 
-  private void maybeAddAndCommit(String pattern) {
+  /* implemented as git add+commit
+   * @param pattern is the noteId
+   * @param commitMessage is a commit message (checkpoint name)
+   * (non-Javadoc)
+   * @see org.apache.zeppelin.notebook.repo.VFSNotebookRepo#checkpoint(String, 
String)
+   */
+  @Override
+  public void checkpoint(String pattern, String commitMessage) {
     try {
       List<DiffEntry> gitDiff = git.diff().call();
       if (!gitDiff.isEmpty()) {
         LOG.debug("Changes found for pattern '{}': {}", pattern, gitDiff);
         DirCache added = git.add().addFilepattern(pattern).call();
         LOG.debug("{} changes are about to be commited", 
added.getEntryCount());
-        git.commit().setMessage("Updated " + pattern).call();
+        git.commit().setMessage(commitMessage).call();
       } else {
         LOG.debug("No changes found {}", pattern);
       }
     } catch (GitAPIException e) {
-      LOG.error("Faild to add+comit {} to Git", pattern, e);
+      LOG.error("Failed to add+comit {} to Git", pattern, e);
     }
   }
 
@@ -100,8 +106,11 @@ public class GitNotebookRepo extends VFSNotebookRepo 
implements NotebookRepoVers
       Iterable<RevCommit> logs = git.log().addPath(noteId).call();
       for (RevCommit log: logs) {
         history.add(new Rev(log.getName(), log.getCommitTime()));
-        LOG.debug(" - ({},{})", log.getName(), log.getCommitTime());
+        LOG.debug(" - ({},{},{})", log.getName(), log.getCommitTime(), 
log.getFullMessage());
       }
+    } catch (NoHeadException e) {
+      //when no initial commit exists
+      LOG.warn("No Head found for {}, {}", noteId, e.getMessage());
     } catch (GitAPIException e) {
       LOG.error("Failed to get logs for {}", noteId, e);
     }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
index f8e0b57..d135032 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
@@ -36,4 +36,9 @@ public interface NotebookRepo {
    * Release any underlying resources
    */
   public void close();
+
+  /**
+   * chekpoint (versioning) for notebooks (optional)
+   */
+  public void checkpoint(String noteId, String checkPointName) throws 
IOException;
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
index 55cdac2..88399d0 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
@@ -224,7 +224,8 @@ public class NotebookRepoSync implements NotebookRepo {
 
   NotebookRepo getRepo(int repoIndex) throws IOException {
     if (repoIndex < 0 || repoIndex >= getRepoCount()) {
-      throw new IOException("Storage repo index is out of range");
+      throw new IOException("Requested storage index " + repoIndex
+          + " isn't initialized," + " repository count is " + getRepoCount());
     }
     return repos.get(repoIndex);
   }
@@ -351,4 +352,27 @@ public class NotebookRepoSync implements NotebookRepo {
     }
   }
 
+  //checkpoint to all available storages
+  @Override
+  public void checkpoint(String noteId, String checkPointName) throws 
IOException {
+    int repoCount = getRepoCount();
+    int errorCount = 0;
+    String errorMessage = "";
+    for (int i = 0; i < Math.min(repoCount, getMaxRepoNum()); i++) {
+      try {
+        getRepo(i).checkpoint(noteId, checkPointName);
+      } catch (IOException e) {
+        LOG.warn("Couldn't checkpoint in {} storage with index {} for note 
{}", 
+          getRepo(i).getClass().toString(), i, noteId);
+        errorMessage += "Error on storage class " + 
getRepo(i).getClass().toString() + 
+          " with index " + i + " : " + e.getMessage() + "\n";
+        errorCount++;
+      }
+    }
+    // throw exception if failed to commit for all initialized repos
+    if (errorCount == Math.min(repoCount, getMaxRepoNum())) {
+      throw new IOException(errorMessage);
+    }
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
index 870aa86..a78dcb6 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
@@ -189,4 +189,10 @@ public class S3NotebookRepo implements NotebookRepo {
   public void close() {
     //no-op
   }
+
+  @Override
+  public void checkpoint(String noteId, String checkPointName) throws 
IOException {
+    // no-op
+    LOG.info("Checkpoint feature isn't suported in {}", 
this.getClass().toString());
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
index 67f9b64..5f3676a 100644
--- 
a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
+++ 
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
@@ -244,4 +244,10 @@ public class VFSNotebookRepo implements NotebookRepo {
     //no-op    
   }
 
+  @Override
+  public void checkpoint(String noteId, String checkPointName) throws 
IOException {
+    // no-op
+    logger.info("Checkpoint feature isn't suported in {}", 
this.getClass().toString());
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java
 
b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java
index b92c7a9..879b1ad 100644
--- 
a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java
+++ 
b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/GitNotebookRepoTest.java
@@ -22,12 +22,16 @@ import static com.google.common.truth.Truth.assertThat;
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
 import org.apache.zeppelin.interpreter.mock.MockInterpreter2;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.notebook.NoteInfo;
+import org.apache.zeppelin.notebook.Paragraph;
 import org.apache.zeppelin.notebook.repo.NotebookRepoVersioned.Rev;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -96,7 +100,8 @@ public class GitNotebookRepoTest {
     assertThat(notebookRepo.list()).isNotEmpty();
 
     List<DiffEntry> diff = git.diff().call();
-    assertThat(diff).isEmpty();
+    // no commit, diff isn't empty
+    assertThat(diff).isNotEmpty();
   }
 
   @Test
@@ -109,7 +114,46 @@ public class GitNotebookRepoTest {
     List<Rev> testNotebookHistory = notebookRepo.history(TEST_NOTE_ID);
 
     //then
-    assertThat(testNotebookHistory).isNotEmpty();
+    //no initial commit, empty history
+    assertThat(testNotebookHistory).isEmpty();
   }
 
+  @Test
+  public void addCheckpoint() throws IOException {
+    // initial checks
+    notebookRepo = new GitNotebookRepo(conf);
+    assertThat(notebookRepo.list()).isNotEmpty();
+    assertThat(containsNote(notebookRepo.list(), TEST_NOTE_ID)).isTrue();
+    assertThat(notebookRepo.history(TEST_NOTE_ID)).isEmpty();
+
+    notebookRepo.checkpoint(TEST_NOTE_ID, "first commit");
+    List<Rev> notebookHistoryBefore = notebookRepo.history(TEST_NOTE_ID);
+    assertThat(notebookRepo.history(TEST_NOTE_ID)).isNotEmpty();
+    int initialCount = notebookHistoryBefore.size();
+    
+    // add changes to note
+    Note note = notebookRepo.get(TEST_NOTE_ID);
+    Paragraph p = note.addParagraph();
+    Map<String, Object> config = p.getConfig();
+    config.put("enabled", true);
+    p.setConfig(config);
+    p.setText("%md checkpoint test text");
+    
+    // save and checkpoint note
+    notebookRepo.save(note);
+    notebookRepo.checkpoint(TEST_NOTE_ID, "second commit");
+    
+    // see if commit is added
+    List<Rev> notebookHistoryAfter = notebookRepo.history(TEST_NOTE_ID);
+    assertThat(notebookHistoryAfter.size()).isEqualTo(initialCount + 1);
+  }
+  
+  private boolean containsNote(List<NoteInfo> notes, String noteId) {
+    for (NoteInfo note: notes) {
+      if (note.getId().equals(noteId)) {
+        return true;
+      }
+    }
+    return false;
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7a58d461/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
 
b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
index 753fab2..b162c88 100644
--- 
a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
+++ 
b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.zeppelin.notebook.repo;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -44,6 +45,7 @@ import org.apache.zeppelin.search.LuceneSearch;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.quartz.SchedulerException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,6 +61,7 @@ public class NotebookRepoSyncTest implements 
JobListenerFactory {
   private NotebookRepoSync notebookRepoSync;
   private InterpreterFactory factory;
   private DependencyResolver depResolver;
+  private SearchService search;
   private static final Logger LOG = 
LoggerFactory.getLogger(NotebookRepoSyncTest.class);
   
   @Before
@@ -90,7 +93,7 @@ public class NotebookRepoSyncTest implements 
JobListenerFactory {
     depResolver = new DependencyResolver(mainZepDir.getAbsolutePath() + 
"/local-repo");
     factory = new InterpreterFactory(conf, new InterpreterOption(false), null, 
null, depResolver);
     
-    SearchService search = mock(SearchService.class);
+    search = mock(SearchService.class);
     notebookRepoSync = new NotebookRepoSync(conf);
     notebookSync = new Notebook(conf, notebookRepoSync, schedulerFactory, 
factory, this, search);
   }
@@ -211,6 +214,44 @@ public class NotebookRepoSyncTest implements 
JobListenerFactory {
     assertEquals(1, notebookRepoSync.list(1).size());
   }
 
+  @Test
+  public void testCheckpointOneStorage() throws IOException, 
SchedulerException {
+    System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_STORAGE.getVarName(), 
"org.apache.zeppelin.notebook.repo.GitNotebookRepo");
+    ZeppelinConfiguration vConf = ZeppelinConfiguration.create();
+
+    NotebookRepoSync vRepoSync = new NotebookRepoSync(vConf);
+    Notebook vNotebookSync = new Notebook(vConf, vRepoSync, schedulerFactory, 
factory, this, search);
+
+    // one git versioned storage initialized
+    assertThat(vRepoSync.getRepoCount()).isEqualTo(1);
+    assertThat(vRepoSync.getRepo(0)).isInstanceOf(GitNotebookRepo.class);
+    
+    GitNotebookRepo gitRepo = (GitNotebookRepo) vRepoSync.getRepo(0);
+    
+    // no notes
+    assertThat(vRepoSync.list().size()).isEqualTo(0);
+    // create note
+    Note note = vNotebookSync.createNote();
+    assertThat(vRepoSync.list().size()).isEqualTo(1);
+    
+    String noteId = vRepoSync.list().get(0).getId();
+    // first checkpoint
+    vRepoSync.checkpoint(noteId, "checkpoint message");
+    int vCount = gitRepo.history(noteId).size();
+    assertThat(vCount).isEqualTo(1);
+    
+    Paragraph p = note.addParagraph();
+    Map<String, Object> config = p.getConfig();
+    config.put("enabled", true);
+    p.setConfig(config);
+    p.setText("%md checkpoint test");
+    
+    // save and checkpoint again
+    vRepoSync.save(note);
+    vRepoSync.checkpoint(noteId, "checkpoint message 2");
+    assertThat(gitRepo.history(noteId).size()).isEqualTo(vCount + 1);
+  }
+  
   static void delete(File file){
     if(file.isFile()) file.delete();
       else if(file.isDirectory()){

Reply via email to